9333320d0716fcef294df9a8cdb455a84619b769
[xboard.git] / backend.c
1 /*
2  * backend.c -- Common back end for X and Windows NT versions of
3  *
4  * Copyright 1991 by Digital Equipment Corporation, Maynard,
5  * Massachusetts.
6  *
7  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8  * 2007, 2008, 2009, 2010, 2011, 2012 Free Software Foundation, Inc.
9  *
10  * Enhancements Copyright 2005 Alessandro Scotti
11  *
12  * The following terms apply to Digital Equipment Corporation's copyright
13  * interest in XBoard:
14  * ------------------------------------------------------------------------
15  * All Rights Reserved
16  *
17  * Permission to use, copy, modify, and distribute this software and its
18  * documentation for any purpose and without fee is hereby granted,
19  * provided that the above copyright notice appear in all copies and that
20  * both that copyright notice and this permission notice appear in
21  * supporting documentation, and that the name of Digital not be
22  * used in advertising or publicity pertaining to distribution of the
23  * software without specific, written prior permission.
24  *
25  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
31  * SOFTWARE.
32  * ------------------------------------------------------------------------
33  *
34  * The following terms apply to the enhanced version of XBoard
35  * distributed by the Free Software Foundation:
36  * ------------------------------------------------------------------------
37  *
38  * GNU XBoard is free software: you can redistribute it and/or modify
39  * it under the terms of the GNU General Public License as published by
40  * the Free Software Foundation, either version 3 of the License, or (at
41  * your option) any later version.
42  *
43  * GNU XBoard is distributed in the hope that it will be useful, but
44  * WITHOUT ANY WARRANTY; without even the implied warranty of
45  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46  * General Public License for more details.
47  *
48  * You should have received a copy of the GNU General Public License
49  * along with this program. If not, see http://www.gnu.org/licenses/.  *
50  *
51  *------------------------------------------------------------------------
52  ** See the file ChangeLog for a revision history.  */
53
54 /* [AS] Also useful here for debugging */
55 #ifdef WIN32
56 #include <windows.h>
57
58 int flock(int f, int code);
59 #define LOCK_EX 2
60 #define SLASH '\\'
61
62 #else
63
64 #include <sys/file.h>
65 #define SLASH '/'
66
67 #endif
68
69 #include "config.h"
70
71 #include <assert.h>
72 #include <stdio.h>
73 #include <ctype.h>
74 #include <errno.h>
75 #include <sys/types.h>
76 #include <sys/stat.h>
77 #include <math.h>
78 #include <ctype.h>
79
80 #if STDC_HEADERS
81 # include <stdlib.h>
82 # include <string.h>
83 # include <stdarg.h>
84 #else /* not STDC_HEADERS */
85 # if HAVE_STRING_H
86 #  include <string.h>
87 # else /* not HAVE_STRING_H */
88 #  include <strings.h>
89 # endif /* not HAVE_STRING_H */
90 #endif /* not STDC_HEADERS */
91
92 #if HAVE_SYS_FCNTL_H
93 # include <sys/fcntl.h>
94 #else /* not HAVE_SYS_FCNTL_H */
95 # if HAVE_FCNTL_H
96 #  include <fcntl.h>
97 # endif /* HAVE_FCNTL_H */
98 #endif /* not HAVE_SYS_FCNTL_H */
99
100 #if TIME_WITH_SYS_TIME
101 # include <sys/time.h>
102 # include <time.h>
103 #else
104 # if HAVE_SYS_TIME_H
105 #  include <sys/time.h>
106 # else
107 #  include <time.h>
108 # endif
109 #endif
110
111 #if defined(_amigados) && !defined(__GNUC__)
112 struct timezone {
113     int tz_minuteswest;
114     int tz_dsttime;
115 };
116 extern int gettimeofday(struct timeval *, struct timezone *);
117 #endif
118
119 #if HAVE_UNISTD_H
120 # include <unistd.h>
121 #endif
122
123 #include "common.h"
124 #include "frontend.h"
125 #include "backend.h"
126 #include "parser.h"
127 #include "moves.h"
128 #if ZIPPY
129 # include "zippy.h"
130 #endif
131 #include "backendz.h"
132 #include "gettext.h"
133
134 #ifdef ENABLE_NLS
135 # define _(s) gettext (s)
136 # define N_(s) gettext_noop (s)
137 # define T_(s) gettext(s)
138 #else
139 # ifdef WIN32
140 #   define _(s) T_(s)
141 #   define N_(s) s
142 # else
143 #   define _(s) (s)
144 #   define N_(s) s
145 #   define T_(s) s
146 # endif
147 #endif
148
149
150 int establish P((void));
151 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
152                          char *buf, int count, int error));
153 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
154                       char *buf, int count, int error));
155 void SendToICS P((char *s));
156 void SendToICSDelayed P((char *s, long msdelay));
157 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
158 void HandleMachineMove P((char *message, ChessProgramState *cps));
159 int AutoPlayOneMove P((void));
160 int LoadGameOneMove P((ChessMove readAhead));
161 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
162 int LoadPositionFromFile P((char *filename, int n, char *title));
163 int SavePositionToFile P((char *filename));
164 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
165 void ShowMove P((int fromX, int fromY, int toX, int toY));
166 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
167                    /*char*/int promoChar));
168 void BackwardInner P((int target));
169 void ForwardInner P((int target));
170 int Adjudicate P((ChessProgramState *cps));
171 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
172 void EditPositionDone P((Boolean fakeRights));
173 void PrintOpponents P((FILE *fp));
174 void PrintPosition P((FILE *fp, int move));
175 void StartChessProgram P((ChessProgramState *cps));
176 void SendToProgram P((char *message, ChessProgramState *cps));
177 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
178 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
179                            char *buf, int count, int error));
180 void SendTimeControl P((ChessProgramState *cps,
181                         int mps, long tc, int inc, int sd, int st));
182 char *TimeControlTagValue P((void));
183 void Attention P((ChessProgramState *cps));
184 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
185 int ResurrectChessProgram P((void));
186 void DisplayComment P((int moveNumber, char *text));
187 void DisplayMove P((int moveNumber));
188
189 void ParseGameHistory P((char *game));
190 void ParseBoard12 P((char *string));
191 void KeepAlive P((void));
192 void StartClocks P((void));
193 void SwitchClocks P((int nr));
194 void StopClocks P((void));
195 void ResetClocks P((void));
196 char *PGNDate P((void));
197 void SetGameInfo P((void));
198 int RegisterMove P((void));
199 void MakeRegisteredMove P((void));
200 void TruncateGame P((void));
201 int looking_at P((char *, int *, char *));
202 void CopyPlayerNameIntoFileName P((char **, char *));
203 char *SavePart P((char *));
204 int SaveGameOldStyle P((FILE *));
205 int SaveGamePGN P((FILE *));
206 int CheckFlags P((void));
207 long NextTickLength P((long));
208 void CheckTimeControl P((void));
209 void show_bytes P((FILE *, char *, int));
210 int string_to_rating P((char *str));
211 void ParseFeatures P((char* args, ChessProgramState *cps));
212 void InitBackEnd3 P((void));
213 void FeatureDone P((ChessProgramState* cps, int val));
214 void InitChessProgram P((ChessProgramState *cps, int setup));
215 void OutputKibitz(int window, char *text);
216 int PerpetualChase(int first, int last);
217 int EngineOutputIsUp();
218 void InitDrawingSizes(int x, int y);
219 void NextMatchGame P((void));
220 int NextTourneyGame P((int nr, int *swap));
221 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
222 FILE *WriteTourneyFile P((char *results, FILE *f));
223 void DisplayTwoMachinesTitle P(());
224 static void ExcludeClick P((int index));
225 void ToggleSecond P((void));
226
227 #ifdef WIN32
228        extern void ConsoleCreate();
229 #endif
230
231 ChessProgramState *WhitePlayer();
232 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
233 int VerifyDisplayMode P(());
234
235 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
236 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
237 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
238 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
239 void ics_update_width P((int new_width));
240 extern char installDir[MSG_SIZ];
241 VariantClass startVariant; /* [HGM] nicks: initial variant */
242 Boolean abortMatch;
243
244 extern int tinyLayout, smallLayout;
245 ChessProgramStats programStats;
246 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
247 int endPV = -1;
248 static int exiting = 0; /* [HGM] moved to top */
249 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
250 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
251 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
252 int partnerHighlight[2];
253 Boolean partnerBoardValid = 0;
254 char partnerStatus[MSG_SIZ];
255 Boolean partnerUp;
256 Boolean originalFlip;
257 Boolean twoBoards = 0;
258 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
259 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
260 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
261 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
262 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
263 int opponentKibitzes;
264 int lastSavedGame; /* [HGM] save: ID of game */
265 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
266 extern int chatCount;
267 int chattingPartner;
268 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
269 char lastMsg[MSG_SIZ];
270 ChessSquare pieceSweep = EmptySquare;
271 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
272 int promoDefaultAltered;
273 int keepInfo = 0; /* [HGM] to protect PGN tags in auto-step game analysis */
274
275 /* States for ics_getting_history */
276 #define H_FALSE 0
277 #define H_REQUESTED 1
278 #define H_GOT_REQ_HEADER 2
279 #define H_GOT_UNREQ_HEADER 3
280 #define H_GETTING_MOVES 4
281 #define H_GOT_UNWANTED_HEADER 5
282
283 /* whosays values for GameEnds */
284 #define GE_ICS 0
285 #define GE_ENGINE 1
286 #define GE_PLAYER 2
287 #define GE_FILE 3
288 #define GE_XBOARD 4
289 #define GE_ENGINE1 5
290 #define GE_ENGINE2 6
291
292 /* Maximum number of games in a cmail message */
293 #define CMAIL_MAX_GAMES 20
294
295 /* Different types of move when calling RegisterMove */
296 #define CMAIL_MOVE   0
297 #define CMAIL_RESIGN 1
298 #define CMAIL_DRAW   2
299 #define CMAIL_ACCEPT 3
300
301 /* Different types of result to remember for each game */
302 #define CMAIL_NOT_RESULT 0
303 #define CMAIL_OLD_RESULT 1
304 #define CMAIL_NEW_RESULT 2
305
306 /* Telnet protocol constants */
307 #define TN_WILL 0373
308 #define TN_WONT 0374
309 #define TN_DO   0375
310 #define TN_DONT 0376
311 #define TN_IAC  0377
312 #define TN_ECHO 0001
313 #define TN_SGA  0003
314 #define TN_PORT 23
315
316 char*
317 safeStrCpy (char *dst, const char *src, size_t count)
318 { // [HGM] made safe
319   int i;
320   assert( dst != NULL );
321   assert( src != NULL );
322   assert( count > 0 );
323
324   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
325   if(  i == count && dst[count-1] != NULLCHAR)
326     {
327       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
328       if(appData.debugMode)
329       fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
330     }
331
332   return dst;
333 }
334
335 /* Some compiler can't cast u64 to double
336  * This function do the job for us:
337
338  * We use the highest bit for cast, this only
339  * works if the highest bit is not
340  * in use (This should not happen)
341  *
342  * We used this for all compiler
343  */
344 double
345 u64ToDouble (u64 value)
346 {
347   double r;
348   u64 tmp = value & u64Const(0x7fffffffffffffff);
349   r = (double)(s64)tmp;
350   if (value & u64Const(0x8000000000000000))
351        r +=  9.2233720368547758080e18; /* 2^63 */
352  return r;
353 }
354
355 /* Fake up flags for now, as we aren't keeping track of castling
356    availability yet. [HGM] Change of logic: the flag now only
357    indicates the type of castlings allowed by the rule of the game.
358    The actual rights themselves are maintained in the array
359    castlingRights, as part of the game history, and are not probed
360    by this function.
361  */
362 int
363 PosFlags (index)
364 {
365   int flags = F_ALL_CASTLE_OK;
366   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
367   switch (gameInfo.variant) {
368   case VariantSuicide:
369     flags &= ~F_ALL_CASTLE_OK;
370   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
371     flags |= F_IGNORE_CHECK;
372   case VariantLosers:
373     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
374     break;
375   case VariantAtomic:
376     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
377     break;
378   case VariantKriegspiel:
379     flags |= F_KRIEGSPIEL_CAPTURE;
380     break;
381   case VariantCapaRandom:
382   case VariantFischeRandom:
383     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
384   case VariantNoCastle:
385   case VariantShatranj:
386   case VariantCourier:
387   case VariantMakruk:
388   case VariantGrand:
389     flags &= ~F_ALL_CASTLE_OK;
390     break;
391   default:
392     break;
393   }
394   return flags;
395 }
396
397 FILE *gameFileFP, *debugFP, *serverFP;
398 char *currentDebugFile; // [HGM] debug split: to remember name
399
400 /*
401     [AS] Note: sometimes, the sscanf() function is used to parse the input
402     into a fixed-size buffer. Because of this, we must be prepared to
403     receive strings as long as the size of the input buffer, which is currently
404     set to 4K for Windows and 8K for the rest.
405     So, we must either allocate sufficiently large buffers here, or
406     reduce the size of the input buffer in the input reading part.
407 */
408
409 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
410 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
411 char thinkOutput1[MSG_SIZ*10];
412
413 ChessProgramState first, second, pairing;
414
415 /* premove variables */
416 int premoveToX = 0;
417 int premoveToY = 0;
418 int premoveFromX = 0;
419 int premoveFromY = 0;
420 int premovePromoChar = 0;
421 int gotPremove = 0;
422 Boolean alarmSounded;
423 /* end premove variables */
424
425 char *ics_prefix = "$";
426 enum ICS_TYPE ics_type = ICS_GENERIC;
427
428 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
429 int pauseExamForwardMostMove = 0;
430 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
431 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
432 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
433 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
434 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
435 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
436 int whiteFlag = FALSE, blackFlag = FALSE;
437 int userOfferedDraw = FALSE;
438 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
439 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
440 int cmailMoveType[CMAIL_MAX_GAMES];
441 long ics_clock_paused = 0;
442 ProcRef icsPR = NoProc, cmailPR = NoProc;
443 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
444 GameMode gameMode = BeginningOfGame;
445 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
446 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
447 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
448 int hiddenThinkOutputState = 0; /* [AS] */
449 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
450 int adjudicateLossPlies = 6;
451 char white_holding[64], black_holding[64];
452 TimeMark lastNodeCountTime;
453 long lastNodeCount=0;
454 int shiftKey, controlKey; // [HGM] set by mouse handler
455
456 int have_sent_ICS_logon = 0;
457 int movesPerSession;
458 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
459 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack, activePartnerTime;
460 Boolean adjustedClock;
461 long timeControl_2; /* [AS] Allow separate time controls */
462 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC, activePartner; /* [HGM] secondary TC: merge of MPS, TC and inc */
463 long timeRemaining[2][MAX_MOVES];
464 int matchGame = 0, nextGame = 0, roundNr = 0;
465 Boolean waitingForGame = FALSE;
466 TimeMark programStartTime, pauseStart;
467 char ics_handle[MSG_SIZ];
468 int have_set_title = 0;
469
470 /* animateTraining preserves the state of appData.animate
471  * when Training mode is activated. This allows the
472  * response to be animated when appData.animate == TRUE and
473  * appData.animateDragging == TRUE.
474  */
475 Boolean animateTraining;
476
477 GameInfo gameInfo;
478
479 AppData appData;
480
481 Board boards[MAX_MOVES];
482 /* [HGM] Following 7 needed for accurate legality tests: */
483 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
484 signed char  initialRights[BOARD_FILES];
485 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
486 int   initialRulePlies, FENrulePlies;
487 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
488 int loadFlag = 0;
489 Boolean shuffleOpenings;
490 int mute; // mute all sounds
491
492 // [HGM] vari: next 12 to save and restore variations
493 #define MAX_VARIATIONS 10
494 int framePtr = MAX_MOVES-1; // points to free stack entry
495 int storedGames = 0;
496 int savedFirst[MAX_VARIATIONS];
497 int savedLast[MAX_VARIATIONS];
498 int savedFramePtr[MAX_VARIATIONS];
499 char *savedDetails[MAX_VARIATIONS];
500 ChessMove savedResult[MAX_VARIATIONS];
501
502 void PushTail P((int firstMove, int lastMove));
503 Boolean PopTail P((Boolean annotate));
504 void PushInner P((int firstMove, int lastMove));
505 void PopInner P((Boolean annotate));
506 void CleanupTail P((void));
507
508 ChessSquare  FIDEArray[2][BOARD_FILES] = {
509     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
510         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
511     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
512         BlackKing, BlackBishop, BlackKnight, BlackRook }
513 };
514
515 ChessSquare twoKingsArray[2][BOARD_FILES] = {
516     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
517         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
518     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
519         BlackKing, BlackKing, BlackKnight, BlackRook }
520 };
521
522 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
523     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
524         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
525     { BlackRook, BlackMan, BlackBishop, BlackQueen,
526         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
527 };
528
529 ChessSquare SpartanArray[2][BOARD_FILES] = {
530     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
531         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
532     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
533         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
534 };
535
536 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
537     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
538         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
539     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
540         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
541 };
542
543 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
544     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
545         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
546     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
547         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
548 };
549
550 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
551     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
552         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
553     { BlackRook, BlackKnight, BlackMan, BlackFerz,
554         BlackKing, BlackMan, BlackKnight, BlackRook }
555 };
556
557
558 #if (BOARD_FILES>=10)
559 ChessSquare ShogiArray[2][BOARD_FILES] = {
560     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
561         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
562     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
563         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
564 };
565
566 ChessSquare XiangqiArray[2][BOARD_FILES] = {
567     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
568         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
569     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
570         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
571 };
572
573 ChessSquare CapablancaArray[2][BOARD_FILES] = {
574     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
575         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
576     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
577         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
578 };
579
580 ChessSquare GreatArray[2][BOARD_FILES] = {
581     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
582         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
583     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
584         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
585 };
586
587 ChessSquare JanusArray[2][BOARD_FILES] = {
588     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
589         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
590     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
591         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
592 };
593
594 ChessSquare GrandArray[2][BOARD_FILES] = {
595     { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
596         WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
597     { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
598         BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
599 };
600
601 #ifdef GOTHIC
602 ChessSquare GothicArray[2][BOARD_FILES] = {
603     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
604         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
605     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
606         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
607 };
608 #else // !GOTHIC
609 #define GothicArray CapablancaArray
610 #endif // !GOTHIC
611
612 #ifdef FALCON
613 ChessSquare FalconArray[2][BOARD_FILES] = {
614     { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
615         WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
616     { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
617         BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
618 };
619 #else // !FALCON
620 #define FalconArray CapablancaArray
621 #endif // !FALCON
622
623 #else // !(BOARD_FILES>=10)
624 #define XiangqiPosition FIDEArray
625 #define CapablancaArray FIDEArray
626 #define GothicArray FIDEArray
627 #define GreatArray FIDEArray
628 #endif // !(BOARD_FILES>=10)
629
630 #if (BOARD_FILES>=12)
631 ChessSquare CourierArray[2][BOARD_FILES] = {
632     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
633         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
634     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
635         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
636 };
637 #else // !(BOARD_FILES>=12)
638 #define CourierArray CapablancaArray
639 #endif // !(BOARD_FILES>=12)
640
641
642 Board initialPosition;
643
644
645 /* Convert str to a rating. Checks for special cases of "----",
646
647    "++++", etc. Also strips ()'s */
648 int
649 string_to_rating (char *str)
650 {
651   while(*str && !isdigit(*str)) ++str;
652   if (!*str)
653     return 0;   /* One of the special "no rating" cases */
654   else
655     return atoi(str);
656 }
657
658 void
659 ClearProgramStats ()
660 {
661     /* Init programStats */
662     programStats.movelist[0] = 0;
663     programStats.depth = 0;
664     programStats.nr_moves = 0;
665     programStats.moves_left = 0;
666     programStats.nodes = 0;
667     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
668     programStats.score = 0;
669     programStats.got_only_move = 0;
670     programStats.got_fail = 0;
671     programStats.line_is_book = 0;
672 }
673
674 void
675 CommonEngineInit ()
676 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
677     if (appData.firstPlaysBlack) {
678         first.twoMachinesColor = "black\n";
679         second.twoMachinesColor = "white\n";
680     } else {
681         first.twoMachinesColor = "white\n";
682         second.twoMachinesColor = "black\n";
683     }
684
685     first.other = &second;
686     second.other = &first;
687
688     { float norm = 1;
689         if(appData.timeOddsMode) {
690             norm = appData.timeOdds[0];
691             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
692         }
693         first.timeOdds  = appData.timeOdds[0]/norm;
694         second.timeOdds = appData.timeOdds[1]/norm;
695     }
696
697     if(programVersion) free(programVersion);
698     if (appData.noChessProgram) {
699         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
700         sprintf(programVersion, "%s", PACKAGE_STRING);
701     } else {
702       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
703       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
704       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
705     }
706 }
707
708 void
709 UnloadEngine (ChessProgramState *cps)
710 {
711         /* Kill off first chess program */
712         if (cps->isr != NULL)
713           RemoveInputSource(cps->isr);
714         cps->isr = NULL;
715
716         if (cps->pr != NoProc) {
717             ExitAnalyzeMode();
718             DoSleep( appData.delayBeforeQuit );
719             SendToProgram("quit\n", cps);
720             DoSleep( appData.delayAfterQuit );
721             DestroyChildProcess(cps->pr, cps->useSigterm);
722         }
723         cps->pr = NoProc;
724         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
725 }
726
727 void
728 ClearOptions (ChessProgramState *cps)
729 {
730     int i;
731     cps->nrOptions = cps->comboCnt = 0;
732     for(i=0; i<MAX_OPTIONS; i++) {
733         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
734         cps->option[i].textValue = 0;
735     }
736 }
737
738 char *engineNames[] = {
739   /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
740      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
741 N_("first"),
742   /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
743      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
744 N_("second")
745 };
746
747 void
748 InitEngine (ChessProgramState *cps, int n)
749 {   // [HGM] all engine initialiation put in a function that does one engine
750
751     ClearOptions(cps);
752
753     cps->which = engineNames[n];
754     cps->maybeThinking = FALSE;
755     cps->pr = NoProc;
756     cps->isr = NULL;
757     cps->sendTime = 2;
758     cps->sendDrawOffers = 1;
759
760     cps->program = appData.chessProgram[n];
761     cps->host = appData.host[n];
762     cps->dir = appData.directory[n];
763     cps->initString = appData.engInitString[n];
764     cps->computerString = appData.computerString[n];
765     cps->useSigint  = TRUE;
766     cps->useSigterm = TRUE;
767     cps->reuse = appData.reuse[n];
768     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
769     cps->useSetboard = FALSE;
770     cps->useSAN = FALSE;
771     cps->usePing = FALSE;
772     cps->lastPing = 0;
773     cps->lastPong = 0;
774     cps->usePlayother = FALSE;
775     cps->useColors = TRUE;
776     cps->useUsermove = FALSE;
777     cps->sendICS = FALSE;
778     cps->sendName = appData.icsActive;
779     cps->sdKludge = FALSE;
780     cps->stKludge = FALSE;
781     TidyProgramName(cps->program, cps->host, cps->tidy);
782     cps->matchWins = 0;
783     safeStrCpy(cps->variants, appData.variant, MSG_SIZ);
784     cps->analysisSupport = 2; /* detect */
785     cps->analyzing = FALSE;
786     cps->initDone = FALSE;
787
788     /* New features added by Tord: */
789     cps->useFEN960 = FALSE;
790     cps->useOOCastle = TRUE;
791     /* End of new features added by Tord. */
792     cps->fenOverride  = appData.fenOverride[n];
793
794     /* [HGM] time odds: set factor for each machine */
795     cps->timeOdds  = appData.timeOdds[n];
796
797     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
798     cps->accumulateTC = appData.accumulateTC[n];
799     cps->maxNrOfSessions = 1;
800
801     /* [HGM] debug */
802     cps->debug = FALSE;
803
804     cps->supportsNPS = UNKNOWN;
805     cps->memSize = FALSE;
806     cps->maxCores = FALSE;
807     cps->egtFormats[0] = NULLCHAR;
808
809     /* [HGM] options */
810     cps->optionSettings  = appData.engOptions[n];
811
812     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
813     cps->isUCI = appData.isUCI[n]; /* [AS] */
814     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
815
816     if (appData.protocolVersion[n] > PROTOVER
817         || appData.protocolVersion[n] < 1)
818       {
819         char buf[MSG_SIZ];
820         int len;
821
822         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
823                        appData.protocolVersion[n]);
824         if( (len >= MSG_SIZ) && appData.debugMode )
825           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
826
827         DisplayFatalError(buf, 0, 2);
828       }
829     else
830       {
831         cps->protocolVersion = appData.protocolVersion[n];
832       }
833
834     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
835     ParseFeatures(appData.featureDefaults, cps);
836 }
837
838 ChessProgramState *savCps;
839
840 void
841 LoadEngine ()
842 {
843     int i;
844     if(WaitForEngine(savCps, LoadEngine)) return;
845     CommonEngineInit(); // recalculate time odds
846     if(gameInfo.variant != StringToVariant(appData.variant)) {
847         // we changed variant when loading the engine; this forces us to reset
848         Reset(TRUE, savCps != &first);
849         EditGameEvent(); // for consistency with other path, as Reset changes mode
850     }
851     InitChessProgram(savCps, FALSE);
852     SendToProgram("force\n", savCps);
853     DisplayMessage("", "");
854     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
855     for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
856     ThawUI();
857     SetGNUMode();
858 }
859
860 void
861 ReplaceEngine (ChessProgramState *cps, int n)
862 {
863     EditGameEvent();
864     UnloadEngine(cps);
865     appData.noChessProgram = FALSE;
866     appData.clockMode = TRUE;
867     InitEngine(cps, n);
868     UpdateLogos(TRUE);
869     if(n) return; // only startup first engine immediately; second can wait
870     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
871     LoadEngine();
872 }
873
874 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
875 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
876
877 static char resetOptions[] = 
878         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
879         "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
880         "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 "
881         "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
882
883 void
884 FloatToFront(char **list, char *engineLine)
885 {
886     char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
887     int i=0;
888     if(appData.recentEngines <= 0) return;
889     TidyProgramName(engineLine, "localhost", tidy+1);
890     tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
891     strncpy(buf+1, *list, MSG_SIZ-50);
892     if(p = strstr(buf, tidy)) { // tidy name appears in list
893         q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
894         while(*p++ = *++q); // squeeze out
895     }
896     strcat(tidy, buf+1); // put list behind tidy name
897     p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
898     if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
899     ASSIGN(*list, tidy+1);
900 }
901
902 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
903
904 void
905 Load (ChessProgramState *cps, int i)
906 {
907     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ];
908     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
909         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
910         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
911         ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
912         FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
913         appData.firstProtocolVersion = PROTOVER;
914         ParseArgsFromString(buf);
915         SwapEngines(i);
916         ReplaceEngine(cps, i);
917         FloatToFront(&appData.recentEngineList, engineLine);
918         return;
919     }
920     p = engineName;
921     while(q = strchr(p, SLASH)) p = q+1;
922     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
923     if(engineDir[0] != NULLCHAR) {
924         ASSIGN(appData.directory[i], engineDir); p = engineName;
925     } else if(p != engineName) { // derive directory from engine path, when not given
926         p[-1] = 0;
927         ASSIGN(appData.directory[i], engineName);
928         p[-1] = SLASH;
929         if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
930     } else { ASSIGN(appData.directory[i], "."); }
931     if(params[0]) {
932         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
933         snprintf(command, MSG_SIZ, "%s %s", p, params);
934         p = command;
935     }
936     ASSIGN(appData.chessProgram[i], p);
937     appData.isUCI[i] = isUCI;
938     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
939     appData.hasOwnBookUCI[i] = hasBook;
940     if(!nickName[0]) useNick = FALSE;
941     if(useNick) ASSIGN(appData.pgnName[i], nickName);
942     if(addToList) {
943         int len;
944         char quote;
945         q = firstChessProgramNames;
946         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
947         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
948         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
949                         quote, p, quote, appData.directory[i], 
950                         useNick ? " -fn \"" : "",
951                         useNick ? nickName : "",
952                         useNick ? "\"" : "",
953                         v1 ? " -firstProtocolVersion 1" : "",
954                         hasBook ? "" : " -fNoOwnBookUCI",
955                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
956                         storeVariant ? " -variant " : "",
957                         storeVariant ? VariantName(gameInfo.variant) : "");
958         if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
959         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
960         if(insert != q) insert[-1] = NULLCHAR;
961         snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
962         if(q)   free(q);
963         FloatToFront(&appData.recentEngineList, buf);
964     }
965     ReplaceEngine(cps, i);
966 }
967
968 void
969 InitTimeControls ()
970 {
971     int matched, min, sec;
972     /*
973      * Parse timeControl resource
974      */
975     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
976                           appData.movesPerSession)) {
977         char buf[MSG_SIZ];
978         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
979         DisplayFatalError(buf, 0, 2);
980     }
981
982     /*
983      * Parse searchTime resource
984      */
985     if (*appData.searchTime != NULLCHAR) {
986         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
987         if (matched == 1) {
988             searchTime = min * 60;
989         } else if (matched == 2) {
990             searchTime = min * 60 + sec;
991         } else {
992             char buf[MSG_SIZ];
993             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
994             DisplayFatalError(buf, 0, 2);
995         }
996     }
997 }
998
999 void
1000 InitBackEnd1 ()
1001 {
1002
1003     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1004     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1005
1006     GetTimeMark(&programStartTime);
1007     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1008     appData.seedBase = random() + (random()<<15);
1009     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1010
1011     ClearProgramStats();
1012     programStats.ok_to_send = 1;
1013     programStats.seen_stat = 0;
1014
1015     /*
1016      * Initialize game list
1017      */
1018     ListNew(&gameList);
1019
1020
1021     /*
1022      * Internet chess server status
1023      */
1024     if (appData.icsActive) {
1025         appData.matchMode = FALSE;
1026         appData.matchGames = 0;
1027 #if ZIPPY
1028         appData.noChessProgram = !appData.zippyPlay;
1029 #else
1030         appData.zippyPlay = FALSE;
1031         appData.zippyTalk = FALSE;
1032         appData.noChessProgram = TRUE;
1033 #endif
1034         if (*appData.icsHelper != NULLCHAR) {
1035             appData.useTelnet = TRUE;
1036             appData.telnetProgram = appData.icsHelper;
1037         }
1038     } else {
1039         appData.zippyTalk = appData.zippyPlay = FALSE;
1040     }
1041
1042     /* [AS] Initialize pv info list [HGM] and game state */
1043     {
1044         int i, j;
1045
1046         for( i=0; i<=framePtr; i++ ) {
1047             pvInfoList[i].depth = -1;
1048             boards[i][EP_STATUS] = EP_NONE;
1049             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1050         }
1051     }
1052
1053     InitTimeControls();
1054
1055     /* [AS] Adjudication threshold */
1056     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1057
1058     InitEngine(&first, 0);
1059     InitEngine(&second, 1);
1060     CommonEngineInit();
1061
1062     pairing.which = "pairing"; // pairing engine
1063     pairing.pr = NoProc;
1064     pairing.isr = NULL;
1065     pairing.program = appData.pairingEngine;
1066     pairing.host = "localhost";
1067     pairing.dir = ".";
1068
1069     if (appData.icsActive) {
1070         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1071     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1072         appData.clockMode = FALSE;
1073         first.sendTime = second.sendTime = 0;
1074     }
1075
1076 #if ZIPPY
1077     /* Override some settings from environment variables, for backward
1078        compatibility.  Unfortunately it's not feasible to have the env
1079        vars just set defaults, at least in xboard.  Ugh.
1080     */
1081     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1082       ZippyInit();
1083     }
1084 #endif
1085
1086     if (!appData.icsActive) {
1087       char buf[MSG_SIZ];
1088       int len;
1089
1090       /* Check for variants that are supported only in ICS mode,
1091          or not at all.  Some that are accepted here nevertheless
1092          have bugs; see comments below.
1093       */
1094       VariantClass variant = StringToVariant(appData.variant);
1095       switch (variant) {
1096       case VariantBughouse:     /* need four players and two boards */
1097       case VariantKriegspiel:   /* need to hide pieces and move details */
1098         /* case VariantFischeRandom: (Fabien: moved below) */
1099         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1100         if( (len >= MSG_SIZ) && appData.debugMode )
1101           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1102
1103         DisplayFatalError(buf, 0, 2);
1104         return;
1105
1106       case VariantUnknown:
1107       case VariantLoadable:
1108       case Variant29:
1109       case Variant30:
1110       case Variant31:
1111       case Variant32:
1112       case Variant33:
1113       case Variant34:
1114       case Variant35:
1115       case Variant36:
1116       default:
1117         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1118         if( (len >= MSG_SIZ) && appData.debugMode )
1119           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1120
1121         DisplayFatalError(buf, 0, 2);
1122         return;
1123
1124       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1125       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1126       case VariantGothic:     /* [HGM] should work */
1127       case VariantCapablanca: /* [HGM] should work */
1128       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1129       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1130       case VariantKnightmate: /* [HGM] should work */
1131       case VariantCylinder:   /* [HGM] untested */
1132       case VariantFalcon:     /* [HGM] untested */
1133       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1134                                  offboard interposition not understood */
1135       case VariantNormal:     /* definitely works! */
1136       case VariantWildCastle: /* pieces not automatically shuffled */
1137       case VariantNoCastle:   /* pieces not automatically shuffled */
1138       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1139       case VariantLosers:     /* should work except for win condition,
1140                                  and doesn't know captures are mandatory */
1141       case VariantSuicide:    /* should work except for win condition,
1142                                  and doesn't know captures are mandatory */
1143       case VariantGiveaway:   /* should work except for win condition,
1144                                  and doesn't know captures are mandatory */
1145       case VariantTwoKings:   /* should work */
1146       case VariantAtomic:     /* should work except for win condition */
1147       case Variant3Check:     /* should work except for win condition */
1148       case VariantShatranj:   /* should work except for all win conditions */
1149       case VariantMakruk:     /* should work except for draw countdown */
1150       case VariantBerolina:   /* might work if TestLegality is off */
1151       case VariantCapaRandom: /* should work */
1152       case VariantJanus:      /* should work */
1153       case VariantSuper:      /* experimental */
1154       case VariantGreat:      /* experimental, requires legality testing to be off */
1155       case VariantSChess:     /* S-Chess, should work */
1156       case VariantGrand:      /* should work */
1157       case VariantSpartan:    /* should work */
1158         break;
1159       }
1160     }
1161
1162 }
1163
1164 int
1165 NextIntegerFromString (char ** str, long * value)
1166 {
1167     int result = -1;
1168     char * s = *str;
1169
1170     while( *s == ' ' || *s == '\t' ) {
1171         s++;
1172     }
1173
1174     *value = 0;
1175
1176     if( *s >= '0' && *s <= '9' ) {
1177         while( *s >= '0' && *s <= '9' ) {
1178             *value = *value * 10 + (*s - '0');
1179             s++;
1180         }
1181
1182         result = 0;
1183     }
1184
1185     *str = s;
1186
1187     return result;
1188 }
1189
1190 int
1191 NextTimeControlFromString (char ** str, long * value)
1192 {
1193     long temp;
1194     int result = NextIntegerFromString( str, &temp );
1195
1196     if( result == 0 ) {
1197         *value = temp * 60; /* Minutes */
1198         if( **str == ':' ) {
1199             (*str)++;
1200             result = NextIntegerFromString( str, &temp );
1201             *value += temp; /* Seconds */
1202         }
1203     }
1204
1205     return result;
1206 }
1207
1208 int
1209 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1210 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1211     int result = -1, type = 0; long temp, temp2;
1212
1213     if(**str != ':') return -1; // old params remain in force!
1214     (*str)++;
1215     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1216     if( NextIntegerFromString( str, &temp ) ) return -1;
1217     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1218
1219     if(**str != '/') {
1220         /* time only: incremental or sudden-death time control */
1221         if(**str == '+') { /* increment follows; read it */
1222             (*str)++;
1223             if(**str == '!') type = *(*str)++; // Bronstein TC
1224             if(result = NextIntegerFromString( str, &temp2)) return -1;
1225             *inc = temp2 * 1000;
1226             if(**str == '.') { // read fraction of increment
1227                 char *start = ++(*str);
1228                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1229                 temp2 *= 1000;
1230                 while(start++ < *str) temp2 /= 10;
1231                 *inc += temp2;
1232             }
1233         } else *inc = 0;
1234         *moves = 0; *tc = temp * 1000; *incType = type;
1235         return 0;
1236     }
1237
1238     (*str)++; /* classical time control */
1239     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1240
1241     if(result == 0) {
1242         *moves = temp;
1243         *tc    = temp2 * 1000;
1244         *inc   = 0;
1245         *incType = type;
1246     }
1247     return result;
1248 }
1249
1250 int
1251 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1252 {   /* [HGM] get time to add from the multi-session time-control string */
1253     int incType, moves=1; /* kludge to force reading of first session */
1254     long time, increment;
1255     char *s = tcString;
1256
1257     if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1258     do {
1259         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1260         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1261         if(movenr == -1) return time;    /* last move before new session     */
1262         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1263         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1264         if(!moves) return increment;     /* current session is incremental   */
1265         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1266     } while(movenr >= -1);               /* try again for next session       */
1267
1268     return 0; // no new time quota on this move
1269 }
1270
1271 int
1272 ParseTimeControl (char *tc, float ti, int mps)
1273 {
1274   long tc1;
1275   long tc2;
1276   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1277   int min, sec=0;
1278
1279   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1280   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1281       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1282   if(ti > 0) {
1283
1284     if(mps)
1285       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1286     else 
1287       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1288   } else {
1289     if(mps)
1290       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1291     else 
1292       snprintf(buf, MSG_SIZ, ":%s", mytc);
1293   }
1294   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1295   
1296   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1297     return FALSE;
1298   }
1299
1300   if( *tc == '/' ) {
1301     /* Parse second time control */
1302     tc++;
1303
1304     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1305       return FALSE;
1306     }
1307
1308     if( tc2 == 0 ) {
1309       return FALSE;
1310     }
1311
1312     timeControl_2 = tc2 * 1000;
1313   }
1314   else {
1315     timeControl_2 = 0;
1316   }
1317
1318   if( tc1 == 0 ) {
1319     return FALSE;
1320   }
1321
1322   timeControl = tc1 * 1000;
1323
1324   if (ti >= 0) {
1325     timeIncrement = ti * 1000;  /* convert to ms */
1326     movesPerSession = 0;
1327   } else {
1328     timeIncrement = 0;
1329     movesPerSession = mps;
1330   }
1331   return TRUE;
1332 }
1333
1334 void
1335 InitBackEnd2 ()
1336 {
1337     if (appData.debugMode) {
1338         fprintf(debugFP, "%s\n", programVersion);
1339     }
1340     ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1341
1342     set_cont_sequence(appData.wrapContSeq);
1343     if (appData.matchGames > 0) {
1344         appData.matchMode = TRUE;
1345     } else if (appData.matchMode) {
1346         appData.matchGames = 1;
1347     }
1348     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1349         appData.matchGames = appData.sameColorGames;
1350     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1351         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1352         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1353     }
1354     Reset(TRUE, FALSE);
1355     if (appData.noChessProgram || first.protocolVersion == 1) {
1356       InitBackEnd3();
1357     } else {
1358       /* kludge: allow timeout for initial "feature" commands */
1359       FreezeUI();
1360       DisplayMessage("", _("Starting chess program"));
1361       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1362     }
1363 }
1364
1365 int
1366 CalculateIndex (int index, int gameNr)
1367 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1368     int res;
1369     if(index > 0) return index; // fixed nmber
1370     if(index == 0) return 1;
1371     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1372     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1373     return res;
1374 }
1375
1376 int
1377 LoadGameOrPosition (int gameNr)
1378 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1379     if (*appData.loadGameFile != NULLCHAR) {
1380         if (!LoadGameFromFile(appData.loadGameFile,
1381                 CalculateIndex(appData.loadGameIndex, gameNr),
1382                               appData.loadGameFile, FALSE)) {
1383             DisplayFatalError(_("Bad game file"), 0, 1);
1384             return 0;
1385         }
1386     } else if (*appData.loadPositionFile != NULLCHAR) {
1387         if (!LoadPositionFromFile(appData.loadPositionFile,
1388                 CalculateIndex(appData.loadPositionIndex, gameNr),
1389                                   appData.loadPositionFile)) {
1390             DisplayFatalError(_("Bad position file"), 0, 1);
1391             return 0;
1392         }
1393     }
1394     return 1;
1395 }
1396
1397 void
1398 ReserveGame (int gameNr, char resChar)
1399 {
1400     FILE *tf = fopen(appData.tourneyFile, "r+");
1401     char *p, *q, c, buf[MSG_SIZ];
1402     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1403     safeStrCpy(buf, lastMsg, MSG_SIZ);
1404     DisplayMessage(_("Pick new game"), "");
1405     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1406     ParseArgsFromFile(tf);
1407     p = q = appData.results;
1408     if(appData.debugMode) {
1409       char *r = appData.participants;
1410       fprintf(debugFP, "results = '%s'\n", p);
1411       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1412       fprintf(debugFP, "\n");
1413     }
1414     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1415     nextGame = q - p;
1416     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1417     safeStrCpy(q, p, strlen(p) + 2);
1418     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1419     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1420     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1421         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1422         q[nextGame] = '*';
1423     }
1424     fseek(tf, -(strlen(p)+4), SEEK_END);
1425     c = fgetc(tf);
1426     if(c != '"') // depending on DOS or Unix line endings we can be one off
1427          fseek(tf, -(strlen(p)+2), SEEK_END);
1428     else fseek(tf, -(strlen(p)+3), SEEK_END);
1429     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1430     DisplayMessage(buf, "");
1431     free(p); appData.results = q;
1432     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1433        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1434       int round = appData.defaultMatchGames * appData.tourneyType;
1435       if(gameNr < 0 || appData.tourneyType < 1 ||  // gauntlet engine can always stay loaded as first engine
1436          appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1437         UnloadEngine(&first);  // next game belongs to other pairing;
1438         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1439     }
1440     if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1441 }
1442
1443 void
1444 MatchEvent (int mode)
1445 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1446         int dummy;
1447         if(matchMode) { // already in match mode: switch it off
1448             abortMatch = TRUE;
1449             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1450             return;
1451         }
1452 //      if(gameMode != BeginningOfGame) {
1453 //          DisplayError(_("You can only start a match from the initial position."), 0);
1454 //          return;
1455 //      }
1456         abortMatch = FALSE;
1457         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1458         /* Set up machine vs. machine match */
1459         nextGame = 0;
1460         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1461         if(appData.tourneyFile[0]) {
1462             ReserveGame(-1, 0);
1463             if(nextGame > appData.matchGames) {
1464                 char buf[MSG_SIZ];
1465                 if(strchr(appData.results, '*') == NULL) {
1466                     FILE *f;
1467                     appData.tourneyCycles++;
1468                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1469                         fclose(f);
1470                         NextTourneyGame(-1, &dummy);
1471                         ReserveGame(-1, 0);
1472                         if(nextGame <= appData.matchGames) {
1473                             DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1474                             matchMode = mode;
1475                             ScheduleDelayedEvent(NextMatchGame, 10000);
1476                             return;
1477                         }
1478                     }
1479                 }
1480                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1481                 DisplayError(buf, 0);
1482                 appData.tourneyFile[0] = 0;
1483                 return;
1484             }
1485         } else
1486         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1487             DisplayFatalError(_("Can't have a match with no chess programs"),
1488                               0, 2);
1489             return;
1490         }
1491         matchMode = mode;
1492         matchGame = roundNr = 1;
1493         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1494         NextMatchGame();
1495 }
1496
1497 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1498
1499 void
1500 InitBackEnd3 P((void))
1501 {
1502     GameMode initialMode;
1503     char buf[MSG_SIZ];
1504     int err, len;
1505
1506     InitChessProgram(&first, startedFromSetupPosition);
1507
1508     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1509         free(programVersion);
1510         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1511         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1512         FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1513     }
1514
1515     if (appData.icsActive) {
1516 #ifdef WIN32
1517         /* [DM] Make a console window if needed [HGM] merged ifs */
1518         ConsoleCreate();
1519 #endif
1520         err = establish();
1521         if (err != 0)
1522           {
1523             if (*appData.icsCommPort != NULLCHAR)
1524               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1525                              appData.icsCommPort);
1526             else
1527               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1528                         appData.icsHost, appData.icsPort);
1529
1530             if( (len >= MSG_SIZ) && appData.debugMode )
1531               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1532
1533             DisplayFatalError(buf, err, 1);
1534             return;
1535         }
1536         SetICSMode();
1537         telnetISR =
1538           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1539         fromUserISR =
1540           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1541         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1542             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1543     } else if (appData.noChessProgram) {
1544         SetNCPMode();
1545     } else {
1546         SetGNUMode();
1547     }
1548
1549     if (*appData.cmailGameName != NULLCHAR) {
1550         SetCmailMode();
1551         OpenLoopback(&cmailPR);
1552         cmailISR =
1553           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1554     }
1555
1556     ThawUI();
1557     DisplayMessage("", "");
1558     if (StrCaseCmp(appData.initialMode, "") == 0) {
1559       initialMode = BeginningOfGame;
1560       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1561         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1562         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1563         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1564         ModeHighlight();
1565       }
1566     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1567       initialMode = TwoMachinesPlay;
1568     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1569       initialMode = AnalyzeFile;
1570     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1571       initialMode = AnalyzeMode;
1572     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1573       initialMode = MachinePlaysWhite;
1574     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1575       initialMode = MachinePlaysBlack;
1576     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1577       initialMode = EditGame;
1578     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1579       initialMode = EditPosition;
1580     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1581       initialMode = Training;
1582     } else {
1583       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1584       if( (len >= MSG_SIZ) && appData.debugMode )
1585         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1586
1587       DisplayFatalError(buf, 0, 2);
1588       return;
1589     }
1590
1591     if (appData.matchMode) {
1592         if(appData.tourneyFile[0]) { // start tourney from command line
1593             FILE *f;
1594             if(f = fopen(appData.tourneyFile, "r")) {
1595                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1596                 fclose(f);
1597                 appData.clockMode = TRUE;
1598                 SetGNUMode();
1599             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1600         }
1601         MatchEvent(TRUE);
1602     } else if (*appData.cmailGameName != NULLCHAR) {
1603         /* Set up cmail mode */
1604         ReloadCmailMsgEvent(TRUE);
1605     } else {
1606         /* Set up other modes */
1607         if (initialMode == AnalyzeFile) {
1608           if (*appData.loadGameFile == NULLCHAR) {
1609             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1610             return;
1611           }
1612         }
1613         if (*appData.loadGameFile != NULLCHAR) {
1614             (void) LoadGameFromFile(appData.loadGameFile,
1615                                     appData.loadGameIndex,
1616                                     appData.loadGameFile, TRUE);
1617         } else if (*appData.loadPositionFile != NULLCHAR) {
1618             (void) LoadPositionFromFile(appData.loadPositionFile,
1619                                         appData.loadPositionIndex,
1620                                         appData.loadPositionFile);
1621             /* [HGM] try to make self-starting even after FEN load */
1622             /* to allow automatic setup of fairy variants with wtm */
1623             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1624                 gameMode = BeginningOfGame;
1625                 setboardSpoiledMachineBlack = 1;
1626             }
1627             /* [HGM] loadPos: make that every new game uses the setup */
1628             /* from file as long as we do not switch variant          */
1629             if(!blackPlaysFirst) {
1630                 startedFromPositionFile = TRUE;
1631                 CopyBoard(filePosition, boards[0]);
1632             }
1633         }
1634         if (initialMode == AnalyzeMode) {
1635           if (appData.noChessProgram) {
1636             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1637             return;
1638           }
1639           if (appData.icsActive) {
1640             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1641             return;
1642           }
1643           AnalyzeModeEvent();
1644         } else if (initialMode == AnalyzeFile) {
1645           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1646           ShowThinkingEvent();
1647           AnalyzeFileEvent();
1648           AnalysisPeriodicEvent(1);
1649         } else if (initialMode == MachinePlaysWhite) {
1650           if (appData.noChessProgram) {
1651             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1652                               0, 2);
1653             return;
1654           }
1655           if (appData.icsActive) {
1656             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1657                               0, 2);
1658             return;
1659           }
1660           MachineWhiteEvent();
1661         } else if (initialMode == MachinePlaysBlack) {
1662           if (appData.noChessProgram) {
1663             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1664                               0, 2);
1665             return;
1666           }
1667           if (appData.icsActive) {
1668             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1669                               0, 2);
1670             return;
1671           }
1672           MachineBlackEvent();
1673         } else if (initialMode == TwoMachinesPlay) {
1674           if (appData.noChessProgram) {
1675             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1676                               0, 2);
1677             return;
1678           }
1679           if (appData.icsActive) {
1680             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1681                               0, 2);
1682             return;
1683           }
1684           TwoMachinesEvent();
1685         } else if (initialMode == EditGame) {
1686           EditGameEvent();
1687         } else if (initialMode == EditPosition) {
1688           EditPositionEvent();
1689         } else if (initialMode == Training) {
1690           if (*appData.loadGameFile == NULLCHAR) {
1691             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1692             return;
1693           }
1694           TrainingEvent();
1695         }
1696     }
1697 }
1698
1699 void
1700 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1701 {
1702     DisplayBook(current+1);
1703
1704     MoveHistorySet( movelist, first, last, current, pvInfoList );
1705
1706     EvalGraphSet( first, last, current, pvInfoList );
1707
1708     MakeEngineOutputTitle();
1709 }
1710
1711 /*
1712  * Establish will establish a contact to a remote host.port.
1713  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1714  *  used to talk to the host.
1715  * Returns 0 if okay, error code if not.
1716  */
1717 int
1718 establish ()
1719 {
1720     char buf[MSG_SIZ];
1721
1722     if (*appData.icsCommPort != NULLCHAR) {
1723         /* Talk to the host through a serial comm port */
1724         return OpenCommPort(appData.icsCommPort, &icsPR);
1725
1726     } else if (*appData.gateway != NULLCHAR) {
1727         if (*appData.remoteShell == NULLCHAR) {
1728             /* Use the rcmd protocol to run telnet program on a gateway host */
1729             snprintf(buf, sizeof(buf), "%s %s %s",
1730                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1731             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1732
1733         } else {
1734             /* Use the rsh program to run telnet program on a gateway host */
1735             if (*appData.remoteUser == NULLCHAR) {
1736                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1737                         appData.gateway, appData.telnetProgram,
1738                         appData.icsHost, appData.icsPort);
1739             } else {
1740                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1741                         appData.remoteShell, appData.gateway,
1742                         appData.remoteUser, appData.telnetProgram,
1743                         appData.icsHost, appData.icsPort);
1744             }
1745             return StartChildProcess(buf, "", &icsPR);
1746
1747         }
1748     } else if (appData.useTelnet) {
1749         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1750
1751     } else {
1752         /* TCP socket interface differs somewhat between
1753            Unix and NT; handle details in the front end.
1754            */
1755         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1756     }
1757 }
1758
1759 void
1760 EscapeExpand (char *p, char *q)
1761 {       // [HGM] initstring: routine to shape up string arguments
1762         while(*p++ = *q++) if(p[-1] == '\\')
1763             switch(*q++) {
1764                 case 'n': p[-1] = '\n'; break;
1765                 case 'r': p[-1] = '\r'; break;
1766                 case 't': p[-1] = '\t'; break;
1767                 case '\\': p[-1] = '\\'; break;
1768                 case 0: *p = 0; return;
1769                 default: p[-1] = q[-1]; break;
1770             }
1771 }
1772
1773 void
1774 show_bytes (FILE *fp, char *buf, int count)
1775 {
1776     while (count--) {
1777         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1778             fprintf(fp, "\\%03o", *buf & 0xff);
1779         } else {
1780             putc(*buf, fp);
1781         }
1782         buf++;
1783     }
1784     fflush(fp);
1785 }
1786
1787 /* Returns an errno value */
1788 int
1789 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1790 {
1791     char buf[8192], *p, *q, *buflim;
1792     int left, newcount, outcount;
1793
1794     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1795         *appData.gateway != NULLCHAR) {
1796         if (appData.debugMode) {
1797             fprintf(debugFP, ">ICS: ");
1798             show_bytes(debugFP, message, count);
1799             fprintf(debugFP, "\n");
1800         }
1801         return OutputToProcess(pr, message, count, outError);
1802     }
1803
1804     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1805     p = message;
1806     q = buf;
1807     left = count;
1808     newcount = 0;
1809     while (left) {
1810         if (q >= buflim) {
1811             if (appData.debugMode) {
1812                 fprintf(debugFP, ">ICS: ");
1813                 show_bytes(debugFP, buf, newcount);
1814                 fprintf(debugFP, "\n");
1815             }
1816             outcount = OutputToProcess(pr, buf, newcount, outError);
1817             if (outcount < newcount) return -1; /* to be sure */
1818             q = buf;
1819             newcount = 0;
1820         }
1821         if (*p == '\n') {
1822             *q++ = '\r';
1823             newcount++;
1824         } else if (((unsigned char) *p) == TN_IAC) {
1825             *q++ = (char) TN_IAC;
1826             newcount ++;
1827         }
1828         *q++ = *p++;
1829         newcount++;
1830         left--;
1831     }
1832     if (appData.debugMode) {
1833         fprintf(debugFP, ">ICS: ");
1834         show_bytes(debugFP, buf, newcount);
1835         fprintf(debugFP, "\n");
1836     }
1837     outcount = OutputToProcess(pr, buf, newcount, outError);
1838     if (outcount < newcount) return -1; /* to be sure */
1839     return count;
1840 }
1841
1842 void
1843 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1844 {
1845     int outError, outCount;
1846     static int gotEof = 0;
1847
1848     /* Pass data read from player on to ICS */
1849     if (count > 0) {
1850         gotEof = 0;
1851         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1852         if (outCount < count) {
1853             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1854         }
1855     } else if (count < 0) {
1856         RemoveInputSource(isr);
1857         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1858     } else if (gotEof++ > 0) {
1859         RemoveInputSource(isr);
1860         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1861     }
1862 }
1863
1864 void
1865 KeepAlive ()
1866 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1867     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1868     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1869     SendToICS("date\n");
1870     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1871 }
1872
1873 /* added routine for printf style output to ics */
1874 void
1875 ics_printf (char *format, ...)
1876 {
1877     char buffer[MSG_SIZ];
1878     va_list args;
1879
1880     va_start(args, format);
1881     vsnprintf(buffer, sizeof(buffer), format, args);
1882     buffer[sizeof(buffer)-1] = '\0';
1883     SendToICS(buffer);
1884     va_end(args);
1885 }
1886
1887 void
1888 SendToICS (char *s)
1889 {
1890     int count, outCount, outError;
1891
1892     if (icsPR == NoProc) return;
1893
1894     count = strlen(s);
1895     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1896     if (outCount < count) {
1897         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1898     }
1899 }
1900
1901 /* This is used for sending logon scripts to the ICS. Sending
1902    without a delay causes problems when using timestamp on ICC
1903    (at least on my machine). */
1904 void
1905 SendToICSDelayed (char *s, long msdelay)
1906 {
1907     int count, outCount, outError;
1908
1909     if (icsPR == NoProc) return;
1910
1911     count = strlen(s);
1912     if (appData.debugMode) {
1913         fprintf(debugFP, ">ICS: ");
1914         show_bytes(debugFP, s, count);
1915         fprintf(debugFP, "\n");
1916     }
1917     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1918                                       msdelay);
1919     if (outCount < count) {
1920         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1921     }
1922 }
1923
1924
1925 /* Remove all highlighting escape sequences in s
1926    Also deletes any suffix starting with '('
1927    */
1928 char *
1929 StripHighlightAndTitle (char *s)
1930 {
1931     static char retbuf[MSG_SIZ];
1932     char *p = retbuf;
1933
1934     while (*s != NULLCHAR) {
1935         while (*s == '\033') {
1936             while (*s != NULLCHAR && !isalpha(*s)) s++;
1937             if (*s != NULLCHAR) s++;
1938         }
1939         while (*s != NULLCHAR && *s != '\033') {
1940             if (*s == '(' || *s == '[') {
1941                 *p = NULLCHAR;
1942                 return retbuf;
1943             }
1944             *p++ = *s++;
1945         }
1946     }
1947     *p = NULLCHAR;
1948     return retbuf;
1949 }
1950
1951 /* Remove all highlighting escape sequences in s */
1952 char *
1953 StripHighlight (char *s)
1954 {
1955     static char retbuf[MSG_SIZ];
1956     char *p = retbuf;
1957
1958     while (*s != NULLCHAR) {
1959         while (*s == '\033') {
1960             while (*s != NULLCHAR && !isalpha(*s)) s++;
1961             if (*s != NULLCHAR) s++;
1962         }
1963         while (*s != NULLCHAR && *s != '\033') {
1964             *p++ = *s++;
1965         }
1966     }
1967     *p = NULLCHAR;
1968     return retbuf;
1969 }
1970
1971 char *variantNames[] = VARIANT_NAMES;
1972 char *
1973 VariantName (VariantClass v)
1974 {
1975     return variantNames[v];
1976 }
1977
1978
1979 /* Identify a variant from the strings the chess servers use or the
1980    PGN Variant tag names we use. */
1981 VariantClass
1982 StringToVariant (char *e)
1983 {
1984     char *p;
1985     int wnum = -1;
1986     VariantClass v = VariantNormal;
1987     int i, found = FALSE;
1988     char buf[MSG_SIZ];
1989     int len;
1990
1991     if (!e) return v;
1992
1993     /* [HGM] skip over optional board-size prefixes */
1994     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1995         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1996         while( *e++ != '_');
1997     }
1998
1999     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2000         v = VariantNormal;
2001         found = TRUE;
2002     } else
2003     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2004       if (StrCaseStr(e, variantNames[i])) {
2005         v = (VariantClass) i;
2006         found = TRUE;
2007         break;
2008       }
2009     }
2010
2011     if (!found) {
2012       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2013           || StrCaseStr(e, "wild/fr")
2014           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2015         v = VariantFischeRandom;
2016       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2017                  (i = 1, p = StrCaseStr(e, "w"))) {
2018         p += i;
2019         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2020         if (isdigit(*p)) {
2021           wnum = atoi(p);
2022         } else {
2023           wnum = -1;
2024         }
2025         switch (wnum) {
2026         case 0: /* FICS only, actually */
2027         case 1:
2028           /* Castling legal even if K starts on d-file */
2029           v = VariantWildCastle;
2030           break;
2031         case 2:
2032         case 3:
2033         case 4:
2034           /* Castling illegal even if K & R happen to start in
2035              normal positions. */
2036           v = VariantNoCastle;
2037           break;
2038         case 5:
2039         case 7:
2040         case 8:
2041         case 10:
2042         case 11:
2043         case 12:
2044         case 13:
2045         case 14:
2046         case 15:
2047         case 18:
2048         case 19:
2049           /* Castling legal iff K & R start in normal positions */
2050           v = VariantNormal;
2051           break;
2052         case 6:
2053         case 20:
2054         case 21:
2055           /* Special wilds for position setup; unclear what to do here */
2056           v = VariantLoadable;
2057           break;
2058         case 9:
2059           /* Bizarre ICC game */
2060           v = VariantTwoKings;
2061           break;
2062         case 16:
2063           v = VariantKriegspiel;
2064           break;
2065         case 17:
2066           v = VariantLosers;
2067           break;
2068         case 22:
2069           v = VariantFischeRandom;
2070           break;
2071         case 23:
2072           v = VariantCrazyhouse;
2073           break;
2074         case 24:
2075           v = VariantBughouse;
2076           break;
2077         case 25:
2078           v = Variant3Check;
2079           break;
2080         case 26:
2081           /* Not quite the same as FICS suicide! */
2082           v = VariantGiveaway;
2083           break;
2084         case 27:
2085           v = VariantAtomic;
2086           break;
2087         case 28:
2088           v = VariantShatranj;
2089           break;
2090
2091         /* Temporary names for future ICC types.  The name *will* change in
2092            the next xboard/WinBoard release after ICC defines it. */
2093         case 29:
2094           v = Variant29;
2095           break;
2096         case 30:
2097           v = Variant30;
2098           break;
2099         case 31:
2100           v = Variant31;
2101           break;
2102         case 32:
2103           v = Variant32;
2104           break;
2105         case 33:
2106           v = Variant33;
2107           break;
2108         case 34:
2109           v = Variant34;
2110           break;
2111         case 35:
2112           v = Variant35;
2113           break;
2114         case 36:
2115           v = Variant36;
2116           break;
2117         case 37:
2118           v = VariantShogi;
2119           break;
2120         case 38:
2121           v = VariantXiangqi;
2122           break;
2123         case 39:
2124           v = VariantCourier;
2125           break;
2126         case 40:
2127           v = VariantGothic;
2128           break;
2129         case 41:
2130           v = VariantCapablanca;
2131           break;
2132         case 42:
2133           v = VariantKnightmate;
2134           break;
2135         case 43:
2136           v = VariantFairy;
2137           break;
2138         case 44:
2139           v = VariantCylinder;
2140           break;
2141         case 45:
2142           v = VariantFalcon;
2143           break;
2144         case 46:
2145           v = VariantCapaRandom;
2146           break;
2147         case 47:
2148           v = VariantBerolina;
2149           break;
2150         case 48:
2151           v = VariantJanus;
2152           break;
2153         case 49:
2154           v = VariantSuper;
2155           break;
2156         case 50:
2157           v = VariantGreat;
2158           break;
2159         case -1:
2160           /* Found "wild" or "w" in the string but no number;
2161              must assume it's normal chess. */
2162           v = VariantNormal;
2163           break;
2164         default:
2165           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2166           if( (len >= MSG_SIZ) && appData.debugMode )
2167             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2168
2169           DisplayError(buf, 0);
2170           v = VariantUnknown;
2171           break;
2172         }
2173       }
2174     }
2175     if (appData.debugMode) {
2176       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2177               e, wnum, VariantName(v));
2178     }
2179     return v;
2180 }
2181
2182 static int leftover_start = 0, leftover_len = 0;
2183 char star_match[STAR_MATCH_N][MSG_SIZ];
2184
2185 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2186    advance *index beyond it, and set leftover_start to the new value of
2187    *index; else return FALSE.  If pattern contains the character '*', it
2188    matches any sequence of characters not containing '\r', '\n', or the
2189    character following the '*' (if any), and the matched sequence(s) are
2190    copied into star_match.
2191    */
2192 int
2193 looking_at ( char *buf, int *index, char *pattern)
2194 {
2195     char *bufp = &buf[*index], *patternp = pattern;
2196     int star_count = 0;
2197     char *matchp = star_match[0];
2198
2199     for (;;) {
2200         if (*patternp == NULLCHAR) {
2201             *index = leftover_start = bufp - buf;
2202             *matchp = NULLCHAR;
2203             return TRUE;
2204         }
2205         if (*bufp == NULLCHAR) return FALSE;
2206         if (*patternp == '*') {
2207             if (*bufp == *(patternp + 1)) {
2208                 *matchp = NULLCHAR;
2209                 matchp = star_match[++star_count];
2210                 patternp += 2;
2211                 bufp++;
2212                 continue;
2213             } else if (*bufp == '\n' || *bufp == '\r') {
2214                 patternp++;
2215                 if (*patternp == NULLCHAR)
2216                   continue;
2217                 else
2218                   return FALSE;
2219             } else {
2220                 *matchp++ = *bufp++;
2221                 continue;
2222             }
2223         }
2224         if (*patternp != *bufp) return FALSE;
2225         patternp++;
2226         bufp++;
2227     }
2228 }
2229
2230 void
2231 SendToPlayer (char *data, int length)
2232 {
2233     int error, outCount;
2234     outCount = OutputToProcess(NoProc, data, length, &error);
2235     if (outCount < length) {
2236         DisplayFatalError(_("Error writing to display"), error, 1);
2237     }
2238 }
2239
2240 void
2241 PackHolding (char packed[], char *holding)
2242 {
2243     char *p = holding;
2244     char *q = packed;
2245     int runlength = 0;
2246     int curr = 9999;
2247     do {
2248         if (*p == curr) {
2249             runlength++;
2250         } else {
2251             switch (runlength) {
2252               case 0:
2253                 break;
2254               case 1:
2255                 *q++ = curr;
2256                 break;
2257               case 2:
2258                 *q++ = curr;
2259                 *q++ = curr;
2260                 break;
2261               default:
2262                 sprintf(q, "%d", runlength);
2263                 while (*q) q++;
2264                 *q++ = curr;
2265                 break;
2266             }
2267             runlength = 1;
2268             curr = *p;
2269         }
2270     } while (*p++);
2271     *q = NULLCHAR;
2272 }
2273
2274 /* Telnet protocol requests from the front end */
2275 void
2276 TelnetRequest (unsigned char ddww, unsigned char option)
2277 {
2278     unsigned char msg[3];
2279     int outCount, outError;
2280
2281     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2282
2283     if (appData.debugMode) {
2284         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2285         switch (ddww) {
2286           case TN_DO:
2287             ddwwStr = "DO";
2288             break;
2289           case TN_DONT:
2290             ddwwStr = "DONT";
2291             break;
2292           case TN_WILL:
2293             ddwwStr = "WILL";
2294             break;
2295           case TN_WONT:
2296             ddwwStr = "WONT";
2297             break;
2298           default:
2299             ddwwStr = buf1;
2300             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2301             break;
2302         }
2303         switch (option) {
2304           case TN_ECHO:
2305             optionStr = "ECHO";
2306             break;
2307           default:
2308             optionStr = buf2;
2309             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2310             break;
2311         }
2312         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2313     }
2314     msg[0] = TN_IAC;
2315     msg[1] = ddww;
2316     msg[2] = option;
2317     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2318     if (outCount < 3) {
2319         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2320     }
2321 }
2322
2323 void
2324 DoEcho ()
2325 {
2326     if (!appData.icsActive) return;
2327     TelnetRequest(TN_DO, TN_ECHO);
2328 }
2329
2330 void
2331 DontEcho ()
2332 {
2333     if (!appData.icsActive) return;
2334     TelnetRequest(TN_DONT, TN_ECHO);
2335 }
2336
2337 void
2338 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2339 {
2340     /* put the holdings sent to us by the server on the board holdings area */
2341     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2342     char p;
2343     ChessSquare piece;
2344
2345     if(gameInfo.holdingsWidth < 2)  return;
2346     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2347         return; // prevent overwriting by pre-board holdings
2348
2349     if( (int)lowestPiece >= BlackPawn ) {
2350         holdingsColumn = 0;
2351         countsColumn = 1;
2352         holdingsStartRow = BOARD_HEIGHT-1;
2353         direction = -1;
2354     } else {
2355         holdingsColumn = BOARD_WIDTH-1;
2356         countsColumn = BOARD_WIDTH-2;
2357         holdingsStartRow = 0;
2358         direction = 1;
2359     }
2360
2361     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2362         board[i][holdingsColumn] = EmptySquare;
2363         board[i][countsColumn]   = (ChessSquare) 0;
2364     }
2365     while( (p=*holdings++) != NULLCHAR ) {
2366         piece = CharToPiece( ToUpper(p) );
2367         if(piece == EmptySquare) continue;
2368         /*j = (int) piece - (int) WhitePawn;*/
2369         j = PieceToNumber(piece);
2370         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2371         if(j < 0) continue;               /* should not happen */
2372         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2373         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2374         board[holdingsStartRow+j*direction][countsColumn]++;
2375     }
2376 }
2377
2378
2379 void
2380 VariantSwitch (Board board, VariantClass newVariant)
2381 {
2382    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2383    static Board oldBoard;
2384
2385    startedFromPositionFile = FALSE;
2386    if(gameInfo.variant == newVariant) return;
2387
2388    /* [HGM] This routine is called each time an assignment is made to
2389     * gameInfo.variant during a game, to make sure the board sizes
2390     * are set to match the new variant. If that means adding or deleting
2391     * holdings, we shift the playing board accordingly
2392     * This kludge is needed because in ICS observe mode, we get boards
2393     * of an ongoing game without knowing the variant, and learn about the
2394     * latter only later. This can be because of the move list we requested,
2395     * in which case the game history is refilled from the beginning anyway,
2396     * but also when receiving holdings of a crazyhouse game. In the latter
2397     * case we want to add those holdings to the already received position.
2398     */
2399
2400
2401    if (appData.debugMode) {
2402      fprintf(debugFP, "Switch board from %s to %s\n",
2403              VariantName(gameInfo.variant), VariantName(newVariant));
2404      setbuf(debugFP, NULL);
2405    }
2406    shuffleOpenings = 0;       /* [HGM] shuffle */
2407    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2408    switch(newVariant)
2409      {
2410      case VariantShogi:
2411        newWidth = 9;  newHeight = 9;
2412        gameInfo.holdingsSize = 7;
2413      case VariantBughouse:
2414      case VariantCrazyhouse:
2415        newHoldingsWidth = 2; break;
2416      case VariantGreat:
2417        newWidth = 10;
2418      case VariantSuper:
2419        newHoldingsWidth = 2;
2420        gameInfo.holdingsSize = 8;
2421        break;
2422      case VariantGothic:
2423      case VariantCapablanca:
2424      case VariantCapaRandom:
2425        newWidth = 10;
2426      default:
2427        newHoldingsWidth = gameInfo.holdingsSize = 0;
2428      };
2429
2430    if(newWidth  != gameInfo.boardWidth  ||
2431       newHeight != gameInfo.boardHeight ||
2432       newHoldingsWidth != gameInfo.holdingsWidth ) {
2433
2434      /* shift position to new playing area, if needed */
2435      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2436        for(i=0; i<BOARD_HEIGHT; i++)
2437          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2438            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2439              board[i][j];
2440        for(i=0; i<newHeight; i++) {
2441          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2442          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2443        }
2444      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2445        for(i=0; i<BOARD_HEIGHT; i++)
2446          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2447            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2448              board[i][j];
2449      }
2450      board[HOLDINGS_SET] = 0;
2451      gameInfo.boardWidth  = newWidth;
2452      gameInfo.boardHeight = newHeight;
2453      gameInfo.holdingsWidth = newHoldingsWidth;
2454      gameInfo.variant = newVariant;
2455      InitDrawingSizes(-2, 0);
2456    } else gameInfo.variant = newVariant;
2457    CopyBoard(oldBoard, board);   // remember correctly formatted board
2458      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2459    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2460 }
2461
2462 static int loggedOn = FALSE;
2463
2464 /*-- Game start info cache: --*/
2465 int gs_gamenum;
2466 char gs_kind[MSG_SIZ];
2467 static char player1Name[128] = "";
2468 static char player2Name[128] = "";
2469 static char cont_seq[] = "\n\\   ";
2470 static int player1Rating = -1;
2471 static int player2Rating = -1;
2472 /*----------------------------*/
2473
2474 ColorClass curColor = ColorNormal;
2475 int suppressKibitz = 0;
2476
2477 // [HGM] seekgraph
2478 Boolean soughtPending = FALSE;
2479 Boolean seekGraphUp;
2480 #define MAX_SEEK_ADS 200
2481 #define SQUARE 0x80
2482 char *seekAdList[MAX_SEEK_ADS];
2483 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2484 float tcList[MAX_SEEK_ADS];
2485 char colorList[MAX_SEEK_ADS];
2486 int nrOfSeekAds = 0;
2487 int minRating = 1010, maxRating = 2800;
2488 int hMargin = 10, vMargin = 20, h, w;
2489 extern int squareSize, lineGap;
2490
2491 void
2492 PlotSeekAd (int i)
2493 {
2494         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2495         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2496         if(r < minRating+100 && r >=0 ) r = minRating+100;
2497         if(r > maxRating) r = maxRating;
2498         if(tc < 1.f) tc = 1.f;
2499         if(tc > 95.f) tc = 95.f;
2500         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2501         y = ((double)r - minRating)/(maxRating - minRating)
2502             * (h-vMargin-squareSize/8-1) + vMargin;
2503         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2504         if(strstr(seekAdList[i], " u ")) color = 1;
2505         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2506            !strstr(seekAdList[i], "bullet") &&
2507            !strstr(seekAdList[i], "blitz") &&
2508            !strstr(seekAdList[i], "standard") ) color = 2;
2509         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2510         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2511 }
2512
2513 void
2514 PlotSingleSeekAd (int i)
2515 {
2516         DrawSeekOpen();
2517         PlotSeekAd(i);
2518         DrawSeekClose();
2519 }
2520
2521 void
2522 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2523 {
2524         char buf[MSG_SIZ], *ext = "";
2525         VariantClass v = StringToVariant(type);
2526         if(strstr(type, "wild")) {
2527             ext = type + 4; // append wild number
2528             if(v == VariantFischeRandom) type = "chess960"; else
2529             if(v == VariantLoadable) type = "setup"; else
2530             type = VariantName(v);
2531         }
2532         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2533         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2534             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2535             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2536             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2537             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2538             seekNrList[nrOfSeekAds] = nr;
2539             zList[nrOfSeekAds] = 0;
2540             seekAdList[nrOfSeekAds++] = StrSave(buf);
2541             if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2542         }
2543 }
2544
2545 void
2546 EraseSeekDot (int i)
2547 {
2548     int x = xList[i], y = yList[i], d=squareSize/4, k;
2549     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2550     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2551     // now replot every dot that overlapped
2552     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2553         int xx = xList[k], yy = yList[k];
2554         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2555             DrawSeekDot(xx, yy, colorList[k]);
2556     }
2557 }
2558
2559 void
2560 RemoveSeekAd (int nr)
2561 {
2562         int i;
2563         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2564             EraseSeekDot(i);
2565             if(seekAdList[i]) free(seekAdList[i]);
2566             seekAdList[i] = seekAdList[--nrOfSeekAds];
2567             seekNrList[i] = seekNrList[nrOfSeekAds];
2568             ratingList[i] = ratingList[nrOfSeekAds];
2569             colorList[i]  = colorList[nrOfSeekAds];
2570             tcList[i] = tcList[nrOfSeekAds];
2571             xList[i]  = xList[nrOfSeekAds];
2572             yList[i]  = yList[nrOfSeekAds];
2573             zList[i]  = zList[nrOfSeekAds];
2574             seekAdList[nrOfSeekAds] = NULL;
2575             break;
2576         }
2577 }
2578
2579 Boolean
2580 MatchSoughtLine (char *line)
2581 {
2582     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2583     int nr, base, inc, u=0; char dummy;
2584
2585     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2586        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2587        (u=1) &&
2588        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2589         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2590         // match: compact and save the line
2591         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2592         return TRUE;
2593     }
2594     return FALSE;
2595 }
2596
2597 int
2598 DrawSeekGraph ()
2599 {
2600     int i;
2601     if(!seekGraphUp) return FALSE;
2602     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2603     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2604
2605     DrawSeekOpen();
2606     DrawSeekBackground(0, 0, w, h);
2607     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2608     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2609     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2610         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2611         yy = h-1-yy;
2612         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2613         if(i%500 == 0) {
2614             char buf[MSG_SIZ];
2615             snprintf(buf, MSG_SIZ, "%d", i);
2616             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2617         }
2618     }
2619     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2620     for(i=1; i<100; i+=(i<10?1:5)) {
2621         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2622         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2623         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2624             char buf[MSG_SIZ];
2625             snprintf(buf, MSG_SIZ, "%d", i);
2626             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2627         }
2628     }
2629     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2630     DrawSeekClose();
2631     return TRUE;
2632 }
2633
2634 int
2635 SeekGraphClick (ClickType click, int x, int y, int moving)
2636 {
2637     static int lastDown = 0, displayed = 0, lastSecond;
2638     if(y < 0) return FALSE;
2639     if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2640         (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2641         if(!seekGraphUp) return FALSE;
2642         seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2643         DrawPosition(TRUE, NULL);
2644         return TRUE;
2645     }
2646     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2647         if(click == Release || moving) return FALSE;
2648         nrOfSeekAds = 0;
2649         soughtPending = TRUE;
2650         SendToICS(ics_prefix);
2651         SendToICS("sought\n"); // should this be "sought all"?
2652     } else { // issue challenge based on clicked ad
2653         int dist = 10000; int i, closest = 0, second = 0;
2654         for(i=0; i<nrOfSeekAds; i++) {
2655             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2656             if(d < dist) { dist = d; closest = i; }
2657             second += (d - zList[i] < 120); // count in-range ads
2658             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2659         }
2660         if(dist < 120) {
2661             char buf[MSG_SIZ];
2662             second = (second > 1);
2663             if(displayed != closest || second != lastSecond) {
2664                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2665                 lastSecond = second; displayed = closest;
2666             }
2667             if(click == Press) {
2668                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2669                 lastDown = closest;
2670                 return TRUE;
2671             } // on press 'hit', only show info
2672             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2673             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2674             SendToICS(ics_prefix);
2675             SendToICS(buf);
2676             return TRUE; // let incoming board of started game pop down the graph
2677         } else if(click == Release) { // release 'miss' is ignored
2678             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2679             if(moving == 2) { // right up-click
2680                 nrOfSeekAds = 0; // refresh graph
2681                 soughtPending = TRUE;
2682                 SendToICS(ics_prefix);
2683                 SendToICS("sought\n"); // should this be "sought all"?
2684             }
2685             return TRUE;
2686         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2687         // press miss or release hit 'pop down' seek graph
2688         seekGraphUp = FALSE;
2689         DrawPosition(TRUE, NULL);
2690     }
2691     return TRUE;
2692 }
2693
2694 void
2695 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2696 {
2697 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2698 #define STARTED_NONE 0
2699 #define STARTED_MOVES 1
2700 #define STARTED_BOARD 2
2701 #define STARTED_OBSERVE 3
2702 #define STARTED_HOLDINGS 4
2703 #define STARTED_CHATTER 5
2704 #define STARTED_COMMENT 6
2705 #define STARTED_MOVES_NOHIDE 7
2706
2707     static int started = STARTED_NONE;
2708     static char parse[20000];
2709     static int parse_pos = 0;
2710     static char buf[BUF_SIZE + 1];
2711     static int firstTime = TRUE, intfSet = FALSE;
2712     static ColorClass prevColor = ColorNormal;
2713     static int savingComment = FALSE;
2714     static int cmatch = 0; // continuation sequence match
2715     char *bp;
2716     char str[MSG_SIZ];
2717     int i, oldi;
2718     int buf_len;
2719     int next_out;
2720     int tkind;
2721     int backup;    /* [DM] For zippy color lines */
2722     char *p;
2723     char talker[MSG_SIZ]; // [HGM] chat
2724     int channel;
2725
2726     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2727
2728     if (appData.debugMode) {
2729       if (!error) {
2730         fprintf(debugFP, "<ICS: ");
2731         show_bytes(debugFP, data, count);
2732         fprintf(debugFP, "\n");
2733       }
2734     }
2735
2736     if (appData.debugMode) { int f = forwardMostMove;
2737         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2738                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2739                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2740     }
2741     if (count > 0) {
2742         /* If last read ended with a partial line that we couldn't parse,
2743            prepend it to the new read and try again. */
2744         if (leftover_len > 0) {
2745             for (i=0; i<leftover_len; i++)
2746               buf[i] = buf[leftover_start + i];
2747         }
2748
2749     /* copy new characters into the buffer */
2750     bp = buf + leftover_len;
2751     buf_len=leftover_len;
2752     for (i=0; i<count; i++)
2753     {
2754         // ignore these
2755         if (data[i] == '\r')
2756             continue;
2757
2758         // join lines split by ICS?
2759         if (!appData.noJoin)
2760         {
2761             /*
2762                 Joining just consists of finding matches against the
2763                 continuation sequence, and discarding that sequence
2764                 if found instead of copying it.  So, until a match
2765                 fails, there's nothing to do since it might be the
2766                 complete sequence, and thus, something we don't want
2767                 copied.
2768             */
2769             if (data[i] == cont_seq[cmatch])
2770             {
2771                 cmatch++;
2772                 if (cmatch == strlen(cont_seq))
2773                 {
2774                     cmatch = 0; // complete match.  just reset the counter
2775
2776                     /*
2777                         it's possible for the ICS to not include the space
2778                         at the end of the last word, making our [correct]
2779                         join operation fuse two separate words.  the server
2780                         does this when the space occurs at the width setting.
2781                     */
2782                     if (!buf_len || buf[buf_len-1] != ' ')
2783                     {
2784                         *bp++ = ' ';
2785                         buf_len++;
2786                     }
2787                 }
2788                 continue;
2789             }
2790             else if (cmatch)
2791             {
2792                 /*
2793                     match failed, so we have to copy what matched before
2794                     falling through and copying this character.  In reality,
2795                     this will only ever be just the newline character, but
2796                     it doesn't hurt to be precise.
2797                 */
2798                 strncpy(bp, cont_seq, cmatch);
2799                 bp += cmatch;
2800                 buf_len += cmatch;
2801                 cmatch = 0;
2802             }
2803         }
2804
2805         // copy this char
2806         *bp++ = data[i];
2807         buf_len++;
2808     }
2809
2810         buf[buf_len] = NULLCHAR;
2811 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2812         next_out = 0;
2813         leftover_start = 0;
2814
2815         i = 0;
2816         while (i < buf_len) {
2817             /* Deal with part of the TELNET option negotiation
2818                protocol.  We refuse to do anything beyond the
2819                defaults, except that we allow the WILL ECHO option,
2820                which ICS uses to turn off password echoing when we are
2821                directly connected to it.  We reject this option
2822                if localLineEditing mode is on (always on in xboard)
2823                and we are talking to port 23, which might be a real
2824                telnet server that will try to keep WILL ECHO on permanently.
2825              */
2826             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2827                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2828                 unsigned char option;
2829                 oldi = i;
2830                 switch ((unsigned char) buf[++i]) {
2831                   case TN_WILL:
2832                     if (appData.debugMode)
2833                       fprintf(debugFP, "\n<WILL ");
2834                     switch (option = (unsigned char) buf[++i]) {
2835                       case TN_ECHO:
2836                         if (appData.debugMode)
2837                           fprintf(debugFP, "ECHO ");
2838                         /* Reply only if this is a change, according
2839                            to the protocol rules. */
2840                         if (remoteEchoOption) break;
2841                         if (appData.localLineEditing &&
2842                             atoi(appData.icsPort) == TN_PORT) {
2843                             TelnetRequest(TN_DONT, TN_ECHO);
2844                         } else {
2845                             EchoOff();
2846                             TelnetRequest(TN_DO, TN_ECHO);
2847                             remoteEchoOption = TRUE;
2848                         }
2849                         break;
2850                       default:
2851                         if (appData.debugMode)
2852                           fprintf(debugFP, "%d ", option);
2853                         /* Whatever this is, we don't want it. */
2854                         TelnetRequest(TN_DONT, option);
2855                         break;
2856                     }
2857                     break;
2858                   case TN_WONT:
2859                     if (appData.debugMode)
2860                       fprintf(debugFP, "\n<WONT ");
2861                     switch (option = (unsigned char) buf[++i]) {
2862                       case TN_ECHO:
2863                         if (appData.debugMode)
2864                           fprintf(debugFP, "ECHO ");
2865                         /* Reply only if this is a change, according
2866                            to the protocol rules. */
2867                         if (!remoteEchoOption) break;
2868                         EchoOn();
2869                         TelnetRequest(TN_DONT, TN_ECHO);
2870                         remoteEchoOption = FALSE;
2871                         break;
2872                       default:
2873                         if (appData.debugMode)
2874                           fprintf(debugFP, "%d ", (unsigned char) option);
2875                         /* Whatever this is, it must already be turned
2876                            off, because we never agree to turn on
2877                            anything non-default, so according to the
2878                            protocol rules, we don't reply. */
2879                         break;
2880                     }
2881                     break;
2882                   case TN_DO:
2883                     if (appData.debugMode)
2884                       fprintf(debugFP, "\n<DO ");
2885                     switch (option = (unsigned char) buf[++i]) {
2886                       default:
2887                         /* Whatever this is, we refuse to do it. */
2888                         if (appData.debugMode)
2889                           fprintf(debugFP, "%d ", option);
2890                         TelnetRequest(TN_WONT, option);
2891                         break;
2892                     }
2893                     break;
2894                   case TN_DONT:
2895                     if (appData.debugMode)
2896                       fprintf(debugFP, "\n<DONT ");
2897                     switch (option = (unsigned char) buf[++i]) {
2898                       default:
2899                         if (appData.debugMode)
2900                           fprintf(debugFP, "%d ", option);
2901                         /* Whatever this is, we are already not doing
2902                            it, because we never agree to do anything
2903                            non-default, so according to the protocol
2904                            rules, we don't reply. */
2905                         break;
2906                     }
2907                     break;
2908                   case TN_IAC:
2909                     if (appData.debugMode)
2910                       fprintf(debugFP, "\n<IAC ");
2911                     /* Doubled IAC; pass it through */
2912                     i--;
2913                     break;
2914                   default:
2915                     if (appData.debugMode)
2916                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2917                     /* Drop all other telnet commands on the floor */
2918                     break;
2919                 }
2920                 if (oldi > next_out)
2921                   SendToPlayer(&buf[next_out], oldi - next_out);
2922                 if (++i > next_out)
2923                   next_out = i;
2924                 continue;
2925             }
2926
2927             /* OK, this at least will *usually* work */
2928             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2929                 loggedOn = TRUE;
2930             }
2931
2932             if (loggedOn && !intfSet) {
2933                 if (ics_type == ICS_ICC) {
2934                   snprintf(str, MSG_SIZ,
2935                           "/set-quietly interface %s\n/set-quietly style 12\n",
2936                           programVersion);
2937                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2938                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2939                 } else if (ics_type == ICS_CHESSNET) {
2940                   snprintf(str, MSG_SIZ, "/style 12\n");
2941                 } else {
2942                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2943                   strcat(str, programVersion);
2944                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2945                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2946                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2947 #ifdef WIN32
2948                   strcat(str, "$iset nohighlight 1\n");
2949 #endif
2950                   strcat(str, "$iset lock 1\n$style 12\n");
2951                 }
2952                 SendToICS(str);
2953                 NotifyFrontendLogin();
2954                 intfSet = TRUE;
2955             }
2956
2957             if (started == STARTED_COMMENT) {
2958                 /* Accumulate characters in comment */
2959                 parse[parse_pos++] = buf[i];
2960                 if (buf[i] == '\n') {
2961                     parse[parse_pos] = NULLCHAR;
2962                     if(chattingPartner>=0) {
2963                         char mess[MSG_SIZ];
2964                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2965                         OutputChatMessage(chattingPartner, mess);
2966                         chattingPartner = -1;
2967                         next_out = i+1; // [HGM] suppress printing in ICS window
2968                     } else
2969                     if(!suppressKibitz) // [HGM] kibitz
2970                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2971                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2972                         int nrDigit = 0, nrAlph = 0, j;
2973                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2974                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2975                         parse[parse_pos] = NULLCHAR;
2976                         // try to be smart: if it does not look like search info, it should go to
2977                         // ICS interaction window after all, not to engine-output window.
2978                         for(j=0; j<parse_pos; j++) { // count letters and digits
2979                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2980                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2981                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2982                         }
2983                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2984                             int depth=0; float score;
2985                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2986                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2987                                 pvInfoList[forwardMostMove-1].depth = depth;
2988                                 pvInfoList[forwardMostMove-1].score = 100*score;
2989                             }
2990                             OutputKibitz(suppressKibitz, parse);
2991                         } else {
2992                             char tmp[MSG_SIZ];
2993                             if(gameMode == IcsObserving) // restore original ICS messages
2994                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
2995                             else
2996                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2997                             SendToPlayer(tmp, strlen(tmp));
2998                         }
2999                         next_out = i+1; // [HGM] suppress printing in ICS window
3000                     }
3001                     started = STARTED_NONE;
3002                 } else {
3003                     /* Don't match patterns against characters in comment */
3004                     i++;
3005                     continue;
3006                 }
3007             }
3008             if (started == STARTED_CHATTER) {
3009                 if (buf[i] != '\n') {
3010                     /* Don't match patterns against characters in chatter */
3011                     i++;
3012                     continue;
3013                 }
3014                 started = STARTED_NONE;
3015                 if(suppressKibitz) next_out = i+1;
3016             }
3017
3018             /* Kludge to deal with rcmd protocol */
3019             if (firstTime && looking_at(buf, &i, "\001*")) {
3020                 DisplayFatalError(&buf[1], 0, 1);
3021                 continue;
3022             } else {
3023                 firstTime = FALSE;
3024             }
3025
3026             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3027                 ics_type = ICS_ICC;
3028                 ics_prefix = "/";
3029                 if (appData.debugMode)
3030                   fprintf(debugFP, "ics_type %d\n", ics_type);
3031                 continue;
3032             }
3033             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3034                 ics_type = ICS_FICS;
3035                 ics_prefix = "$";
3036                 if (appData.debugMode)
3037                   fprintf(debugFP, "ics_type %d\n", ics_type);
3038                 continue;
3039             }
3040             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3041                 ics_type = ICS_CHESSNET;
3042                 ics_prefix = "/";
3043                 if (appData.debugMode)
3044                   fprintf(debugFP, "ics_type %d\n", ics_type);
3045                 continue;
3046             }
3047
3048             if (!loggedOn &&
3049                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3050                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3051                  looking_at(buf, &i, "will be \"*\""))) {
3052               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3053               continue;
3054             }
3055
3056             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3057               char buf[MSG_SIZ];
3058               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3059               DisplayIcsInteractionTitle(buf);
3060               have_set_title = TRUE;
3061             }
3062
3063             /* skip finger notes */
3064             if (started == STARTED_NONE &&
3065                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3066                  (buf[i] == '1' && buf[i+1] == '0')) &&
3067                 buf[i+2] == ':' && buf[i+3] == ' ') {
3068               started = STARTED_CHATTER;
3069               i += 3;
3070               continue;
3071             }
3072
3073             oldi = i;
3074             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3075             if(appData.seekGraph) {
3076                 if(soughtPending && MatchSoughtLine(buf+i)) {
3077                     i = strstr(buf+i, "rated") - buf;
3078                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3079                     next_out = leftover_start = i;
3080                     started = STARTED_CHATTER;
3081                     suppressKibitz = TRUE;
3082                     continue;
3083                 }
3084                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3085                         && looking_at(buf, &i, "* ads displayed")) {
3086                     soughtPending = FALSE;
3087                     seekGraphUp = TRUE;
3088                     DrawSeekGraph();
3089                     continue;
3090                 }
3091                 if(appData.autoRefresh) {
3092                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3093                         int s = (ics_type == ICS_ICC); // ICC format differs
3094                         if(seekGraphUp)
3095                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3096                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3097                         looking_at(buf, &i, "*% "); // eat prompt
3098                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3099                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3100                         next_out = i; // suppress
3101                         continue;
3102                     }
3103                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3104                         char *p = star_match[0];
3105                         while(*p) {
3106                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3107                             while(*p && *p++ != ' '); // next
3108                         }
3109                         looking_at(buf, &i, "*% "); // eat prompt
3110                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3111                         next_out = i;
3112                         continue;
3113                     }
3114                 }
3115             }
3116
3117             /* skip formula vars */
3118             if (started == STARTED_NONE &&
3119                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3120               started = STARTED_CHATTER;
3121               i += 3;
3122               continue;
3123             }
3124
3125             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3126             if (appData.autoKibitz && started == STARTED_NONE &&
3127                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3128                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3129                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3130                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3131                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3132                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3133                         suppressKibitz = TRUE;
3134                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3135                         next_out = i;
3136                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3137                                 && (gameMode == IcsPlayingWhite)) ||
3138                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3139                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3140                             started = STARTED_CHATTER; // own kibitz we simply discard
3141                         else {
3142                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3143                             parse_pos = 0; parse[0] = NULLCHAR;
3144                             savingComment = TRUE;
3145                             suppressKibitz = gameMode != IcsObserving ? 2 :
3146                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3147                         }
3148                         continue;
3149                 } else
3150                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3151                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3152                          && atoi(star_match[0])) {
3153                     // suppress the acknowledgements of our own autoKibitz
3154                     char *p;
3155                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3156                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3157                     SendToPlayer(star_match[0], strlen(star_match[0]));
3158                     if(looking_at(buf, &i, "*% ")) // eat prompt
3159                         suppressKibitz = FALSE;
3160                     next_out = i;
3161                     continue;
3162                 }
3163             } // [HGM] kibitz: end of patch
3164
3165             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3166
3167             // [HGM] chat: intercept tells by users for which we have an open chat window
3168             channel = -1;
3169             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3170                                            looking_at(buf, &i, "* whispers:") ||
3171                                            looking_at(buf, &i, "* kibitzes:") ||
3172                                            looking_at(buf, &i, "* shouts:") ||
3173                                            looking_at(buf, &i, "* c-shouts:") ||
3174                                            looking_at(buf, &i, "--> * ") ||
3175                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3176                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3177                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3178                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3179                 int p;
3180                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3181                 chattingPartner = -1;
3182
3183                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3184                 for(p=0; p<MAX_CHAT; p++) {
3185                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3186                     talker[0] = '['; strcat(talker, "] ");
3187                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3188                     chattingPartner = p; break;
3189                     }
3190                 } else
3191                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3192                 for(p=0; p<MAX_CHAT; p++) {
3193                     if(!strcmp("kibitzes", chatPartner[p])) {
3194                         talker[0] = '['; strcat(talker, "] ");
3195                         chattingPartner = p; break;
3196                     }
3197                 } else
3198                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3199                 for(p=0; p<MAX_CHAT; p++) {
3200                     if(!strcmp("whispers", chatPartner[p])) {
3201                         talker[0] = '['; strcat(talker, "] ");
3202                         chattingPartner = p; break;
3203                     }
3204                 } else
3205                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3206                   if(buf[i-8] == '-' && buf[i-3] == 't')
3207                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3208                     if(!strcmp("c-shouts", chatPartner[p])) {
3209                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3210                         chattingPartner = p; break;
3211                     }
3212                   }
3213                   if(chattingPartner < 0)
3214                   for(p=0; p<MAX_CHAT; p++) {
3215                     if(!strcmp("shouts", chatPartner[p])) {
3216                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3217                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3218                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3219                         chattingPartner = p; break;
3220                     }
3221                   }
3222                 }
3223                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3224                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3225                     talker[0] = 0; Colorize(ColorTell, FALSE);
3226                     chattingPartner = p; break;
3227                 }
3228                 if(chattingPartner<0) i = oldi; else {
3229                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3230                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3231                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3232                     started = STARTED_COMMENT;
3233                     parse_pos = 0; parse[0] = NULLCHAR;
3234                     savingComment = 3 + chattingPartner; // counts as TRUE
3235                     suppressKibitz = TRUE;
3236                     continue;
3237                 }
3238             } // [HGM] chat: end of patch
3239
3240           backup = i;
3241             if (appData.zippyTalk || appData.zippyPlay) {
3242                 /* [DM] Backup address for color zippy lines */
3243 #if ZIPPY
3244                if (loggedOn == TRUE)
3245                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3246                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3247 #endif
3248             } // [DM] 'else { ' deleted
3249                 if (
3250                     /* Regular tells and says */
3251                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3252                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3253                     looking_at(buf, &i, "* says: ") ||
3254                     /* Don't color "message" or "messages" output */
3255                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3256                     looking_at(buf, &i, "*. * at *:*: ") ||
3257                     looking_at(buf, &i, "--* (*:*): ") ||
3258                     /* Message notifications (same color as tells) */
3259                     looking_at(buf, &i, "* has left a message ") ||
3260                     looking_at(buf, &i, "* just sent you a message:\n") ||
3261                     /* Whispers and kibitzes */
3262                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3263                     looking_at(buf, &i, "* kibitzes: ") ||
3264                     /* Channel tells */
3265                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3266
3267                   if (tkind == 1 && strchr(star_match[0], ':')) {
3268                       /* Avoid "tells you:" spoofs in channels */
3269                      tkind = 3;
3270                   }
3271                   if (star_match[0][0] == NULLCHAR ||
3272                       strchr(star_match[0], ' ') ||
3273                       (tkind == 3 && strchr(star_match[1], ' '))) {
3274                     /* Reject bogus matches */
3275                     i = oldi;
3276                   } else {
3277                     if (appData.colorize) {
3278                       if (oldi > next_out) {
3279                         SendToPlayer(&buf[next_out], oldi - next_out);
3280                         next_out = oldi;
3281                       }
3282                       switch (tkind) {
3283                       case 1:
3284                         Colorize(ColorTell, FALSE);
3285                         curColor = ColorTell;
3286                         break;
3287                       case 2:
3288                         Colorize(ColorKibitz, FALSE);
3289                         curColor = ColorKibitz;
3290                         break;
3291                       case 3:
3292                         p = strrchr(star_match[1], '(');
3293                         if (p == NULL) {
3294                           p = star_match[1];
3295                         } else {
3296                           p++;
3297                         }
3298                         if (atoi(p) == 1) {
3299                           Colorize(ColorChannel1, FALSE);
3300                           curColor = ColorChannel1;
3301                         } else {
3302                           Colorize(ColorChannel, FALSE);
3303                           curColor = ColorChannel;
3304                         }
3305                         break;
3306                       case 5:
3307                         curColor = ColorNormal;
3308                         break;
3309                       }
3310                     }
3311                     if (started == STARTED_NONE && appData.autoComment &&
3312                         (gameMode == IcsObserving ||
3313                          gameMode == IcsPlayingWhite ||
3314                          gameMode == IcsPlayingBlack)) {
3315                       parse_pos = i - oldi;
3316                       memcpy(parse, &buf[oldi], parse_pos);
3317                       parse[parse_pos] = NULLCHAR;
3318                       started = STARTED_COMMENT;
3319                       savingComment = TRUE;
3320                     } else {
3321                       started = STARTED_CHATTER;
3322                       savingComment = FALSE;
3323                     }
3324                     loggedOn = TRUE;
3325                     continue;
3326                   }
3327                 }
3328
3329                 if (looking_at(buf, &i, "* s-shouts: ") ||
3330                     looking_at(buf, &i, "* c-shouts: ")) {
3331                     if (appData.colorize) {
3332                         if (oldi > next_out) {
3333                             SendToPlayer(&buf[next_out], oldi - next_out);
3334                             next_out = oldi;
3335                         }
3336                         Colorize(ColorSShout, FALSE);
3337                         curColor = ColorSShout;
3338                     }
3339                     loggedOn = TRUE;
3340                     started = STARTED_CHATTER;
3341                     continue;
3342                 }
3343
3344                 if (looking_at(buf, &i, "--->")) {
3345                     loggedOn = TRUE;
3346                     continue;
3347                 }
3348
3349                 if (looking_at(buf, &i, "* shouts: ") ||
3350                     looking_at(buf, &i, "--> ")) {
3351                     if (appData.colorize) {
3352                         if (oldi > next_out) {
3353                             SendToPlayer(&buf[next_out], oldi - next_out);
3354                             next_out = oldi;
3355                         }
3356                         Colorize(ColorShout, FALSE);
3357                         curColor = ColorShout;
3358                     }
3359                     loggedOn = TRUE;
3360                     started = STARTED_CHATTER;
3361                     continue;
3362                 }
3363
3364                 if (looking_at( buf, &i, "Challenge:")) {
3365                     if (appData.colorize) {
3366                         if (oldi > next_out) {
3367                             SendToPlayer(&buf[next_out], oldi - next_out);
3368                             next_out = oldi;
3369                         }
3370                         Colorize(ColorChallenge, FALSE);
3371                         curColor = ColorChallenge;
3372                     }
3373                     loggedOn = TRUE;
3374                     continue;
3375                 }
3376
3377                 if (looking_at(buf, &i, "* offers you") ||
3378                     looking_at(buf, &i, "* offers to be") ||
3379                     looking_at(buf, &i, "* would like to") ||
3380                     looking_at(buf, &i, "* requests to") ||
3381                     looking_at(buf, &i, "Your opponent offers") ||
3382                     looking_at(buf, &i, "Your opponent requests")) {
3383
3384                     if (appData.colorize) {
3385                         if (oldi > next_out) {
3386                             SendToPlayer(&buf[next_out], oldi - next_out);
3387                             next_out = oldi;
3388                         }
3389                         Colorize(ColorRequest, FALSE);
3390                         curColor = ColorRequest;
3391                     }
3392                     continue;
3393                 }
3394
3395                 if (looking_at(buf, &i, "* (*) seeking")) {
3396                     if (appData.colorize) {
3397                         if (oldi > next_out) {
3398                             SendToPlayer(&buf[next_out], oldi - next_out);
3399                             next_out = oldi;
3400                         }
3401                         Colorize(ColorSeek, FALSE);
3402                         curColor = ColorSeek;
3403                     }
3404                     continue;
3405             }
3406
3407           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3408
3409             if (looking_at(buf, &i, "\\   ")) {
3410                 if (prevColor != ColorNormal) {
3411                     if (oldi > next_out) {
3412                         SendToPlayer(&buf[next_out], oldi - next_out);
3413                         next_out = oldi;
3414                     }
3415                     Colorize(prevColor, TRUE);
3416                     curColor = prevColor;
3417                 }
3418                 if (savingComment) {
3419                     parse_pos = i - oldi;
3420                     memcpy(parse, &buf[oldi], parse_pos);
3421                     parse[parse_pos] = NULLCHAR;
3422                     started = STARTED_COMMENT;
3423                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3424                         chattingPartner = savingComment - 3; // kludge to remember the box
3425                 } else {
3426                     started = STARTED_CHATTER;
3427                 }
3428                 continue;
3429             }
3430
3431             if (looking_at(buf, &i, "Black Strength :") ||
3432                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3433                 looking_at(buf, &i, "<10>") ||
3434                 looking_at(buf, &i, "#@#")) {
3435                 /* Wrong board style */
3436                 loggedOn = TRUE;
3437                 SendToICS(ics_prefix);
3438                 SendToICS("set style 12\n");
3439                 SendToICS(ics_prefix);
3440                 SendToICS("refresh\n");
3441                 continue;
3442             }
3443
3444             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3445                 ICSInitScript();
3446                 have_sent_ICS_logon = 1;
3447                 continue;
3448             }
3449
3450             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3451                 (looking_at(buf, &i, "\n<12> ") ||
3452                  looking_at(buf, &i, "<12> "))) {
3453                 loggedOn = TRUE;
3454                 if (oldi > next_out) {
3455                     SendToPlayer(&buf[next_out], oldi - next_out);
3456                 }
3457                 next_out = i;
3458                 started = STARTED_BOARD;
3459                 parse_pos = 0;
3460                 continue;
3461             }
3462
3463             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3464                 looking_at(buf, &i, "<b1> ")) {
3465                 if (oldi > next_out) {
3466                     SendToPlayer(&buf[next_out], oldi - next_out);
3467                 }
3468                 next_out = i;
3469                 started = STARTED_HOLDINGS;
3470                 parse_pos = 0;
3471                 continue;
3472             }
3473
3474             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3475                 loggedOn = TRUE;
3476                 /* Header for a move list -- first line */
3477
3478                 switch (ics_getting_history) {
3479                   case H_FALSE:
3480                     switch (gameMode) {
3481                       case IcsIdle:
3482                       case BeginningOfGame:
3483                         /* User typed "moves" or "oldmoves" while we
3484                            were idle.  Pretend we asked for these
3485                            moves and soak them up so user can step
3486                            through them and/or save them.
3487                            */
3488                         Reset(FALSE, TRUE);
3489                         gameMode = IcsObserving;
3490                         ModeHighlight();
3491                         ics_gamenum = -1;
3492                         ics_getting_history = H_GOT_UNREQ_HEADER;
3493                         break;
3494                       case EditGame: /*?*/
3495                       case EditPosition: /*?*/
3496                         /* Should above feature work in these modes too? */
3497                         /* For now it doesn't */
3498                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3499                         break;
3500                       default:
3501                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3502                         break;
3503                     }
3504                     break;
3505                   case H_REQUESTED:
3506                     /* Is this the right one? */
3507                     if (gameInfo.white && gameInfo.black &&
3508                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3509                         strcmp(gameInfo.black, star_match[2]) == 0) {
3510                         /* All is well */
3511                         ics_getting_history = H_GOT_REQ_HEADER;
3512                     }
3513                     break;
3514                   case H_GOT_REQ_HEADER:
3515                   case H_GOT_UNREQ_HEADER:
3516                   case H_GOT_UNWANTED_HEADER:
3517                   case H_GETTING_MOVES:
3518                     /* Should not happen */
3519                     DisplayError(_("Error gathering move list: two headers"), 0);
3520                     ics_getting_history = H_FALSE;
3521                     break;
3522                 }
3523
3524                 /* Save player ratings into gameInfo if needed */
3525                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3526                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3527                     (gameInfo.whiteRating == -1 ||
3528                      gameInfo.blackRating == -1)) {
3529
3530                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3531                     gameInfo.blackRating = string_to_rating(star_match[3]);
3532                     if (appData.debugMode)
3533                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3534                               gameInfo.whiteRating, gameInfo.blackRating);
3535                 }
3536                 continue;
3537             }
3538
3539             if (looking_at(buf, &i,
3540               "* * match, initial time: * minute*, increment: * second")) {
3541                 /* Header for a move list -- second line */
3542                 /* Initial board will follow if this is a wild game */
3543                 if (gameInfo.event != NULL) free(gameInfo.event);
3544                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3545                 gameInfo.event = StrSave(str);
3546                 /* [HGM] we switched variant. Translate boards if needed. */
3547                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3548                 continue;
3549             }
3550
3551             if (looking_at(buf, &i, "Move  ")) {
3552                 /* Beginning of a move list */
3553                 switch (ics_getting_history) {
3554                   case H_FALSE:
3555                     /* Normally should not happen */
3556                     /* Maybe user hit reset while we were parsing */
3557                     break;
3558                   case H_REQUESTED:
3559                     /* Happens if we are ignoring a move list that is not
3560                      * the one we just requested.  Common if the user
3561                      * tries to observe two games without turning off
3562                      * getMoveList */
3563                     break;
3564                   case H_GETTING_MOVES:
3565                     /* Should not happen */
3566                     DisplayError(_("Error gathering move list: nested"), 0);
3567                     ics_getting_history = H_FALSE;
3568                     break;
3569                   case H_GOT_REQ_HEADER:
3570                     ics_getting_history = H_GETTING_MOVES;
3571                     started = STARTED_MOVES;
3572                     parse_pos = 0;
3573                     if (oldi > next_out) {
3574                         SendToPlayer(&buf[next_out], oldi - next_out);
3575                     }
3576                     break;
3577                   case H_GOT_UNREQ_HEADER:
3578                     ics_getting_history = H_GETTING_MOVES;
3579                     started = STARTED_MOVES_NOHIDE;
3580                     parse_pos = 0;
3581                     break;
3582                   case H_GOT_UNWANTED_HEADER:
3583                     ics_getting_history = H_FALSE;
3584                     break;
3585                 }
3586                 continue;
3587             }
3588
3589             if (looking_at(buf, &i, "% ") ||
3590                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3591                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3592                 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3593                     soughtPending = FALSE;
3594                     seekGraphUp = TRUE;
3595                     DrawSeekGraph();
3596                 }
3597                 if(suppressKibitz) next_out = i;
3598                 savingComment = FALSE;
3599                 suppressKibitz = 0;
3600                 switch (started) {
3601                   case STARTED_MOVES:
3602                   case STARTED_MOVES_NOHIDE:
3603                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3604                     parse[parse_pos + i - oldi] = NULLCHAR;
3605                     ParseGameHistory(parse);
3606 #if ZIPPY
3607                     if (appData.zippyPlay && first.initDone) {
3608                         FeedMovesToProgram(&first, forwardMostMove);
3609                         if (gameMode == IcsPlayingWhite) {
3610                             if (WhiteOnMove(forwardMostMove)) {
3611                                 if (first.sendTime) {
3612                                   if (first.useColors) {
3613                                     SendToProgram("black\n", &first);
3614                                   }
3615                                   SendTimeRemaining(&first, TRUE);
3616                                 }
3617                                 if (first.useColors) {
3618                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3619                                 }
3620                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3621                                 first.maybeThinking = TRUE;
3622                             } else {
3623                                 if (first.usePlayother) {
3624                                   if (first.sendTime) {
3625                                     SendTimeRemaining(&first, TRUE);
3626                                   }
3627                                   SendToProgram("playother\n", &first);
3628                                   firstMove = FALSE;
3629                                 } else {
3630                                   firstMove = TRUE;
3631                                 }
3632                             }
3633                         } else if (gameMode == IcsPlayingBlack) {
3634                             if (!WhiteOnMove(forwardMostMove)) {
3635                                 if (first.sendTime) {
3636                                   if (first.useColors) {
3637                                     SendToProgram("white\n", &first);
3638                                   }
3639                                   SendTimeRemaining(&first, FALSE);
3640                                 }
3641                                 if (first.useColors) {
3642                                   SendToProgram("black\n", &first);
3643                                 }
3644                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3645                                 first.maybeThinking = TRUE;
3646                             } else {
3647                                 if (first.usePlayother) {
3648                                   if (first.sendTime) {
3649                                     SendTimeRemaining(&first, FALSE);
3650                                   }
3651                                   SendToProgram("playother\n", &first);
3652                                   firstMove = FALSE;
3653                                 } else {
3654                                   firstMove = TRUE;
3655                                 }
3656                             }
3657                         }
3658                     }
3659 #endif
3660                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3661                         /* Moves came from oldmoves or moves command
3662                            while we weren't doing anything else.
3663                            */
3664                         currentMove = forwardMostMove;
3665                         ClearHighlights();/*!!could figure this out*/
3666                         flipView = appData.flipView;
3667                         DrawPosition(TRUE, boards[currentMove]);
3668                         DisplayBothClocks();
3669                         snprintf(str, MSG_SIZ, "%s %s %s",
3670                                 gameInfo.white, _("vs."),  gameInfo.black);
3671                         DisplayTitle(str);
3672                         gameMode = IcsIdle;
3673                     } else {
3674                         /* Moves were history of an active game */
3675                         if (gameInfo.resultDetails != NULL) {
3676                             free(gameInfo.resultDetails);
3677                             gameInfo.resultDetails = NULL;
3678                         }
3679                     }
3680                     HistorySet(parseList, backwardMostMove,
3681                                forwardMostMove, currentMove-1);
3682                     DisplayMove(currentMove - 1);
3683                     if (started == STARTED_MOVES) next_out = i;
3684                     started = STARTED_NONE;
3685                     ics_getting_history = H_FALSE;
3686                     break;
3687
3688                   case STARTED_OBSERVE:
3689                     started = STARTED_NONE;
3690                     SendToICS(ics_prefix);
3691                     SendToICS("refresh\n");
3692                     break;
3693
3694                   default:
3695                     break;
3696                 }
3697                 if(bookHit) { // [HGM] book: simulate book reply
3698                     static char bookMove[MSG_SIZ]; // a bit generous?
3699
3700                     programStats.nodes = programStats.depth = programStats.time =
3701                     programStats.score = programStats.got_only_move = 0;
3702                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3703
3704                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3705                     strcat(bookMove, bookHit);
3706                     HandleMachineMove(bookMove, &first);
3707                 }
3708                 continue;
3709             }
3710
3711             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3712                  started == STARTED_HOLDINGS ||
3713                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3714                 /* Accumulate characters in move list or board */
3715                 parse[parse_pos++] = buf[i];
3716             }
3717
3718             /* Start of game messages.  Mostly we detect start of game
3719                when the first board image arrives.  On some versions
3720                of the ICS, though, we need to do a "refresh" after starting
3721                to observe in order to get the current board right away. */
3722             if (looking_at(buf, &i, "Adding game * to observation list")) {
3723                 started = STARTED_OBSERVE;
3724                 continue;
3725             }
3726
3727             /* Handle auto-observe */
3728             if (appData.autoObserve &&
3729                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3730                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3731                 char *player;
3732                 /* Choose the player that was highlighted, if any. */
3733                 if (star_match[0][0] == '\033' ||
3734                     star_match[1][0] != '\033') {
3735                     player = star_match[0];
3736                 } else {
3737                     player = star_match[2];
3738                 }
3739                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3740                         ics_prefix, StripHighlightAndTitle(player));
3741                 SendToICS(str);
3742
3743                 /* Save ratings from notify string */
3744                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3745                 player1Rating = string_to_rating(star_match[1]);
3746                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3747                 player2Rating = string_to_rating(star_match[3]);
3748
3749                 if (appData.debugMode)
3750                   fprintf(debugFP,
3751                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3752                           player1Name, player1Rating,
3753                           player2Name, player2Rating);
3754
3755                 continue;
3756             }
3757
3758             /* Deal with automatic examine mode after a game,
3759                and with IcsObserving -> IcsExamining transition */
3760             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3761                 looking_at(buf, &i, "has made you an examiner of game *")) {
3762
3763                 int gamenum = atoi(star_match[0]);
3764                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3765                     gamenum == ics_gamenum) {
3766                     /* We were already playing or observing this game;
3767                        no need to refetch history */
3768                     gameMode = IcsExamining;
3769                     if (pausing) {
3770                         pauseExamForwardMostMove = forwardMostMove;
3771                     } else if (currentMove < forwardMostMove) {
3772                         ForwardInner(forwardMostMove);
3773                     }
3774                 } else {
3775                     /* I don't think this case really can happen */
3776                     SendToICS(ics_prefix);
3777                     SendToICS("refresh\n");
3778                 }
3779                 continue;
3780             }
3781
3782             /* Error messages */
3783 //          if (ics_user_moved) {
3784             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3785                 if (looking_at(buf, &i, "Illegal move") ||
3786                     looking_at(buf, &i, "Not a legal move") ||
3787                     looking_at(buf, &i, "Your king is in check") ||
3788                     looking_at(buf, &i, "It isn't your turn") ||
3789                     looking_at(buf, &i, "It is not your move")) {
3790                     /* Illegal move */
3791                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3792                         currentMove = forwardMostMove-1;
3793                         DisplayMove(currentMove - 1); /* before DMError */
3794                         DrawPosition(FALSE, boards[currentMove]);
3795                         SwitchClocks(forwardMostMove-1); // [HGM] race
3796                         DisplayBothClocks();
3797                     }
3798                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3799                     ics_user_moved = 0;
3800                     continue;
3801                 }
3802             }
3803
3804             if (looking_at(buf, &i, "still have time") ||
3805                 looking_at(buf, &i, "not out of time") ||
3806                 looking_at(buf, &i, "either player is out of time") ||
3807                 looking_at(buf, &i, "has timeseal; checking")) {
3808                 /* We must have called his flag a little too soon */
3809                 whiteFlag = blackFlag = FALSE;
3810                 continue;
3811             }
3812
3813             if (looking_at(buf, &i, "added * seconds to") ||
3814                 looking_at(buf, &i, "seconds were added to")) {
3815                 /* Update the clocks */
3816                 SendToICS(ics_prefix);
3817                 SendToICS("refresh\n");
3818                 continue;
3819             }
3820
3821             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3822                 ics_clock_paused = TRUE;
3823                 StopClocks();
3824                 continue;
3825             }
3826
3827             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3828                 ics_clock_paused = FALSE;
3829                 StartClocks();
3830                 continue;
3831             }
3832
3833             /* Grab player ratings from the Creating: message.
3834                Note we have to check for the special case when
3835                the ICS inserts things like [white] or [black]. */
3836             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3837                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3838                 /* star_matches:
3839                    0    player 1 name (not necessarily white)
3840                    1    player 1 rating
3841                    2    empty, white, or black (IGNORED)
3842                    3    player 2 name (not necessarily black)
3843                    4    player 2 rating
3844
3845                    The names/ratings are sorted out when the game
3846                    actually starts (below).
3847                 */
3848                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3849                 player1Rating = string_to_rating(star_match[1]);
3850                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3851                 player2Rating = string_to_rating(star_match[4]);
3852
3853                 if (appData.debugMode)
3854                   fprintf(debugFP,
3855                           "Ratings from 'Creating:' %s %d, %s %d\n",
3856                           player1Name, player1Rating,
3857                           player2Name, player2Rating);
3858
3859                 continue;
3860             }
3861
3862             /* Improved generic start/end-of-game messages */
3863             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3864                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3865                 /* If tkind == 0: */
3866                 /* star_match[0] is the game number */
3867                 /*           [1] is the white player's name */
3868                 /*           [2] is the black player's name */
3869                 /* For end-of-game: */
3870                 /*           [3] is the reason for the game end */
3871                 /*           [4] is a PGN end game-token, preceded by " " */
3872                 /* For start-of-game: */
3873                 /*           [3] begins with "Creating" or "Continuing" */
3874                 /*           [4] is " *" or empty (don't care). */
3875                 int gamenum = atoi(star_match[0]);
3876                 char *whitename, *blackname, *why, *endtoken;
3877                 ChessMove endtype = EndOfFile;
3878
3879                 if (tkind == 0) {
3880                   whitename = star_match[1];
3881                   blackname = star_match[2];
3882                   why = star_match[3];
3883                   endtoken = star_match[4];
3884                 } else {
3885                   whitename = star_match[1];
3886                   blackname = star_match[3];
3887                   why = star_match[5];
3888                   endtoken = star_match[6];
3889                 }
3890
3891                 /* Game start messages */
3892                 if (strncmp(why, "Creating ", 9) == 0 ||
3893                     strncmp(why, "Continuing ", 11) == 0) {
3894                     gs_gamenum = gamenum;
3895                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3896                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
3897                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3898 #if ZIPPY
3899                     if (appData.zippyPlay) {
3900                         ZippyGameStart(whitename, blackname);
3901                     }
3902 #endif /*ZIPPY*/
3903                     partnerBoardValid = FALSE; // [HGM] bughouse
3904                     continue;
3905                 }
3906
3907                 /* Game end messages */
3908                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3909                     ics_gamenum != gamenum) {
3910                     continue;
3911                 }
3912                 while (endtoken[0] == ' ') endtoken++;
3913                 switch (endtoken[0]) {
3914                   case '*':
3915                   default:
3916                     endtype = GameUnfinished;
3917                     break;
3918                   case '0':
3919                     endtype = BlackWins;
3920                     break;
3921                   case '1':
3922                     if (endtoken[1] == '/')
3923                       endtype = GameIsDrawn;
3924                     else
3925                       endtype = WhiteWins;
3926                     break;
3927                 }
3928                 GameEnds(endtype, why, GE_ICS);
3929 #if ZIPPY
3930                 if (appData.zippyPlay && first.initDone) {
3931                     ZippyGameEnd(endtype, why);
3932                     if (first.pr == NoProc) {
3933                       /* Start the next process early so that we'll
3934                          be ready for the next challenge */
3935                       StartChessProgram(&first);
3936                     }
3937                     /* Send "new" early, in case this command takes
3938                        a long time to finish, so that we'll be ready
3939                        for the next challenge. */
3940                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3941                     Reset(TRUE, TRUE);
3942                 }
3943 #endif /*ZIPPY*/
3944                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3945                 continue;
3946             }
3947
3948             if (looking_at(buf, &i, "Removing game * from observation") ||
3949                 looking_at(buf, &i, "no longer observing game *") ||
3950                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3951                 if (gameMode == IcsObserving &&
3952                     atoi(star_match[0]) == ics_gamenum)
3953                   {
3954                       /* icsEngineAnalyze */
3955                       if (appData.icsEngineAnalyze) {
3956                             ExitAnalyzeMode();
3957                             ModeHighlight();
3958                       }
3959                       StopClocks();
3960                       gameMode = IcsIdle;
3961                       ics_gamenum = -1;
3962                       ics_user_moved = FALSE;
3963                   }
3964                 continue;
3965             }
3966
3967             if (looking_at(buf, &i, "no longer examining game *")) {
3968                 if (gameMode == IcsExamining &&
3969                     atoi(star_match[0]) == ics_gamenum)
3970                   {
3971                       gameMode = IcsIdle;
3972                       ics_gamenum = -1;
3973                       ics_user_moved = FALSE;
3974                   }
3975                 continue;
3976             }
3977
3978             /* Advance leftover_start past any newlines we find,
3979                so only partial lines can get reparsed */
3980             if (looking_at(buf, &i, "\n")) {
3981                 prevColor = curColor;
3982                 if (curColor != ColorNormal) {
3983                     if (oldi > next_out) {
3984                         SendToPlayer(&buf[next_out], oldi - next_out);
3985                         next_out = oldi;
3986                     }
3987                     Colorize(ColorNormal, FALSE);
3988                     curColor = ColorNormal;
3989                 }
3990                 if (started == STARTED_BOARD) {
3991                     started = STARTED_NONE;
3992                     parse[parse_pos] = NULLCHAR;
3993                     ParseBoard12(parse);
3994                     ics_user_moved = 0;
3995
3996                     /* Send premove here */
3997                     if (appData.premove) {
3998                       char str[MSG_SIZ];
3999                       if (currentMove == 0 &&
4000                           gameMode == IcsPlayingWhite &&
4001                           appData.premoveWhite) {
4002                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
4003                         if (appData.debugMode)
4004                           fprintf(debugFP, "Sending premove:\n");
4005                         SendToICS(str);
4006                       } else if (currentMove == 1 &&
4007                                  gameMode == IcsPlayingBlack &&
4008                                  appData.premoveBlack) {
4009                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
4010                         if (appData.debugMode)
4011                           fprintf(debugFP, "Sending premove:\n");
4012                         SendToICS(str);
4013                       } else if (gotPremove) {
4014                         gotPremove = 0;
4015                         ClearPremoveHighlights();
4016                         if (appData.debugMode)
4017                           fprintf(debugFP, "Sending premove:\n");
4018                           UserMoveEvent(premoveFromX, premoveFromY,
4019                                         premoveToX, premoveToY,
4020                                         premovePromoChar);
4021                       }
4022                     }
4023
4024                     /* Usually suppress following prompt */
4025                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4026                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4027                         if (looking_at(buf, &i, "*% ")) {
4028                             savingComment = FALSE;
4029                             suppressKibitz = 0;
4030                         }
4031                     }
4032                     next_out = i;
4033                 } else if (started == STARTED_HOLDINGS) {
4034                     int gamenum;
4035                     char new_piece[MSG_SIZ];
4036                     started = STARTED_NONE;
4037                     parse[parse_pos] = NULLCHAR;
4038                     if (appData.debugMode)
4039                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4040                                                         parse, currentMove);
4041                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4042                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4043                         if (gameInfo.variant == VariantNormal) {
4044                           /* [HGM] We seem to switch variant during a game!
4045                            * Presumably no holdings were displayed, so we have
4046                            * to move the position two files to the right to
4047                            * create room for them!
4048                            */
4049                           VariantClass newVariant;
4050                           switch(gameInfo.boardWidth) { // base guess on board width
4051                                 case 9:  newVariant = VariantShogi; break;
4052                                 case 10: newVariant = VariantGreat; break;
4053                                 default: newVariant = VariantCrazyhouse; break;
4054                           }
4055                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4056                           /* Get a move list just to see the header, which
4057                              will tell us whether this is really bug or zh */
4058                           if (ics_getting_history == H_FALSE) {
4059                             ics_getting_history = H_REQUESTED;
4060                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4061                             SendToICS(str);
4062                           }
4063                         }
4064                         new_piece[0] = NULLCHAR;
4065                         sscanf(parse, "game %d white [%s black [%s <- %s",
4066                                &gamenum, white_holding, black_holding,
4067                                new_piece);
4068                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4069                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4070                         /* [HGM] copy holdings to board holdings area */
4071                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4072                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4073                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4074 #if ZIPPY
4075                         if (appData.zippyPlay && first.initDone) {
4076                             ZippyHoldings(white_holding, black_holding,
4077                                           new_piece);
4078                         }
4079 #endif /*ZIPPY*/
4080                         if (tinyLayout || smallLayout) {
4081                             char wh[16], bh[16];
4082                             PackHolding(wh, white_holding);
4083                             PackHolding(bh, black_holding);
4084                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4085                                     gameInfo.white, gameInfo.black);
4086                         } else {
4087                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4088                                     gameInfo.white, white_holding, _("vs."),
4089                                     gameInfo.black, black_holding);
4090                         }
4091                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4092                         DrawPosition(FALSE, boards[currentMove]);
4093                         DisplayTitle(str);
4094                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4095                         sscanf(parse, "game %d white [%s black [%s <- %s",
4096                                &gamenum, white_holding, black_holding,
4097                                new_piece);
4098                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4099                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4100                         /* [HGM] copy holdings to partner-board holdings area */
4101                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4102                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4103                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4104                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4105                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4106                       }
4107                     }
4108                     /* Suppress following prompt */
4109                     if (looking_at(buf, &i, "*% ")) {
4110                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4111                         savingComment = FALSE;
4112                         suppressKibitz = 0;
4113                     }
4114                     next_out = i;
4115                 }
4116                 continue;
4117             }
4118
4119             i++;                /* skip unparsed character and loop back */
4120         }
4121
4122         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4123 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4124 //          SendToPlayer(&buf[next_out], i - next_out);
4125             started != STARTED_HOLDINGS && leftover_start > next_out) {
4126             SendToPlayer(&buf[next_out], leftover_start - next_out);
4127             next_out = i;
4128         }
4129
4130         leftover_len = buf_len - leftover_start;
4131         /* if buffer ends with something we couldn't parse,
4132            reparse it after appending the next read */
4133
4134     } else if (count == 0) {
4135         RemoveInputSource(isr);
4136         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4137     } else {
4138         DisplayFatalError(_("Error reading from ICS"), error, 1);
4139     }
4140 }
4141
4142
4143 /* Board style 12 looks like this:
4144
4145    <12> r-b---k- pp----pp ---bP--- ---p---- q------- ------P- P--Q--BP -----R-K W -1 0 0 0 0 0 0 paf MaxII 0 2 12 21 25 234 174 24 Q/d7-a4 (0:06) Qxa4 0 0
4146
4147  * The "<12> " is stripped before it gets to this routine.  The two
4148  * trailing 0's (flip state and clock ticking) are later addition, and
4149  * some chess servers may not have them, or may have only the first.
4150  * Additional trailing fields may be added in the future.
4151  */
4152
4153 #define PATTERN "%c%d%d%d%d%d%d%d%s%s%d%d%d%d%d%d%d%d%s%s%s%d%d"
4154
4155 #define RELATION_OBSERVING_PLAYED    0
4156 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4157 #define RELATION_PLAYING_MYMOVE      1
4158 #define RELATION_PLAYING_NOTMYMOVE  -1
4159 #define RELATION_EXAMINING           2
4160 #define RELATION_ISOLATED_BOARD     -3
4161 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4162
4163 void
4164 ParseBoard12 (char *string)
4165 {
4166     GameMode newGameMode;
4167     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
4168     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
4169     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4170     char to_play, board_chars[200];
4171     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4172     char black[32], white[32];
4173     Board board;
4174     int prevMove = currentMove;
4175     int ticking = 2;
4176     ChessMove moveType;
4177     int fromX, fromY, toX, toY;
4178     char promoChar;
4179     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4180     char *bookHit = NULL; // [HGM] book
4181     Boolean weird = FALSE, reqFlag = FALSE;
4182
4183     fromX = fromY = toX = toY = -1;
4184
4185     newGame = FALSE;
4186
4187     if (appData.debugMode)
4188       fprintf(debugFP, _("Parsing board: %s\n"), string);
4189
4190     move_str[0] = NULLCHAR;
4191     elapsed_time[0] = NULLCHAR;
4192     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4193         int  i = 0, j;
4194         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4195             if(string[i] == ' ') { ranks++; files = 0; }
4196             else files++;
4197             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4198             i++;
4199         }
4200         for(j = 0; j <i; j++) board_chars[j] = string[j];
4201         board_chars[i] = '\0';
4202         string += i + 1;
4203     }
4204     n = sscanf(string, PATTERN, &to_play, &double_push,
4205                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4206                &gamenum, white, black, &relation, &basetime, &increment,
4207                &white_stren, &black_stren, &white_time, &black_time,
4208                &moveNum, str, elapsed_time, move_str, &ics_flip,
4209                &ticking);
4210
4211     if (n < 21) {
4212         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4213         DisplayError(str, 0);
4214         return;
4215     }
4216
4217     /* Convert the move number to internal form */
4218     moveNum = (moveNum - 1) * 2;
4219     if (to_play == 'B') moveNum++;
4220     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4221       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4222                         0, 1);
4223       return;
4224     }
4225
4226     switch (relation) {
4227       case RELATION_OBSERVING_PLAYED:
4228       case RELATION_OBSERVING_STATIC:
4229         if (gamenum == -1) {
4230             /* Old ICC buglet */
4231             relation = RELATION_OBSERVING_STATIC;
4232         }
4233         newGameMode = IcsObserving;
4234         break;
4235       case RELATION_PLAYING_MYMOVE:
4236       case RELATION_PLAYING_NOTMYMOVE:
4237         newGameMode =
4238           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4239             IcsPlayingWhite : IcsPlayingBlack;
4240         soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4241         break;
4242       case RELATION_EXAMINING:
4243         newGameMode = IcsExamining;
4244         break;
4245       case RELATION_ISOLATED_BOARD:
4246       default:
4247         /* Just display this board.  If user was doing something else,
4248            we will forget about it until the next board comes. */
4249         newGameMode = IcsIdle;
4250         break;
4251       case RELATION_STARTING_POSITION:
4252         newGameMode = gameMode;
4253         break;
4254     }
4255
4256     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4257         gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4258          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4259       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4260       int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4261       static int lastBgGame = -1;
4262       char *toSqr;
4263       for (k = 0; k < ranks; k++) {
4264         for (j = 0; j < files; j++)
4265           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4266         if(gameInfo.holdingsWidth > 1) {
4267              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4268              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4269         }
4270       }
4271       CopyBoard(partnerBoard, board);
4272       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4273         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4274         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4275       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4276       if(toSqr = strchr(str, '-')) {
4277         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4278         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4279       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4280       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4281       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4282       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4283       if(twoBoards) {
4284           DisplayWhiteClock(white_time*fac, to_play == 'W');
4285           DisplayBlackClock(black_time*fac, to_play != 'W');
4286           activePartner = to_play;
4287           if(gamenum != lastBgGame) {
4288               char buf[MSG_SIZ];
4289               snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4290               DisplayTitle(buf);
4291           }
4292           lastBgGame = gamenum;
4293           activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4294                       partnerUp = 0; flipView = !flipView; } // [HGM] dual
4295       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4296                  (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4297       DisplayMessage(partnerStatus, "");
4298         partnerBoardValid = TRUE;
4299       return;
4300     }
4301
4302     if(appData.dualBoard && appData.bgObserve) {
4303         if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4304             SendToICS(ics_prefix), SendToICS("pobserve\n");
4305         else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4306             char buf[MSG_SIZ];
4307             snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4308             SendToICS(buf);
4309         }
4310     }
4311
4312     /* Modify behavior for initial board display on move listing
4313        of wild games.
4314        */
4315     switch (ics_getting_history) {
4316       case H_FALSE:
4317       case H_REQUESTED:
4318         break;
4319       case H_GOT_REQ_HEADER:
4320       case H_GOT_UNREQ_HEADER:
4321         /* This is the initial position of the current game */
4322         gamenum = ics_gamenum;
4323         moveNum = 0;            /* old ICS bug workaround */
4324         if (to_play == 'B') {
4325           startedFromSetupPosition = TRUE;
4326           blackPlaysFirst = TRUE;
4327           moveNum = 1;
4328           if (forwardMostMove == 0) forwardMostMove = 1;
4329           if (backwardMostMove == 0) backwardMostMove = 1;
4330           if (currentMove == 0) currentMove = 1;
4331         }
4332         newGameMode = gameMode;
4333         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4334         break;
4335       case H_GOT_UNWANTED_HEADER:
4336         /* This is an initial board that we don't want */
4337         return;
4338       case H_GETTING_MOVES:
4339         /* Should not happen */
4340         DisplayError(_("Error gathering move list: extra board"), 0);
4341         ics_getting_history = H_FALSE;
4342         return;
4343     }
4344
4345    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4346                                         move_str[1] == '@' && !gameInfo.holdingsWidth ||
4347                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4348      /* [HGM] We seem to have switched variant unexpectedly
4349       * Try to guess new variant from board size
4350       */
4351           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4352           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4353           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4354           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4355           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4356           if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4357           if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4358           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4359           /* Get a move list just to see the header, which
4360              will tell us whether this is really bug or zh */
4361           if (ics_getting_history == H_FALSE) {
4362             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4363             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4364             SendToICS(str);
4365           }
4366     }
4367
4368     /* Take action if this is the first board of a new game, or of a
4369        different game than is currently being displayed.  */
4370     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4371         relation == RELATION_ISOLATED_BOARD) {
4372
4373         /* Forget the old game and get the history (if any) of the new one */
4374         if (gameMode != BeginningOfGame) {
4375           Reset(TRUE, TRUE);
4376         }
4377         newGame = TRUE;
4378         if (appData.autoRaiseBoard) BoardToTop();
4379         prevMove = -3;
4380         if (gamenum == -1) {
4381             newGameMode = IcsIdle;
4382         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4383                    appData.getMoveList && !reqFlag) {
4384             /* Need to get game history */
4385             ics_getting_history = H_REQUESTED;
4386             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4387             SendToICS(str);
4388         }
4389
4390         /* Initially flip the board to have black on the bottom if playing
4391            black or if the ICS flip flag is set, but let the user change
4392            it with the Flip View button. */
4393         flipView = appData.autoFlipView ?
4394           (newGameMode == IcsPlayingBlack) || ics_flip :
4395           appData.flipView;
4396
4397         /* Done with values from previous mode; copy in new ones */
4398         gameMode = newGameMode;
4399         ModeHighlight();
4400         ics_gamenum = gamenum;
4401         if (gamenum == gs_gamenum) {
4402             int klen = strlen(gs_kind);
4403             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4404             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4405             gameInfo.event = StrSave(str);
4406         } else {
4407             gameInfo.event = StrSave("ICS game");
4408         }
4409         gameInfo.site = StrSave(appData.icsHost);
4410         gameInfo.date = PGNDate();
4411         gameInfo.round = StrSave("-");
4412         gameInfo.white = StrSave(white);
4413         gameInfo.black = StrSave(black);
4414         timeControl = basetime * 60 * 1000;
4415         timeControl_2 = 0;
4416         timeIncrement = increment * 1000;
4417         movesPerSession = 0;
4418         gameInfo.timeControl = TimeControlTagValue();
4419         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4420   if (appData.debugMode) {
4421     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4422     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4423     setbuf(debugFP, NULL);
4424   }
4425
4426         gameInfo.outOfBook = NULL;
4427
4428         /* Do we have the ratings? */
4429         if (strcmp(player1Name, white) == 0 &&
4430             strcmp(player2Name, black) == 0) {
4431             if (appData.debugMode)
4432               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4433                       player1Rating, player2Rating);
4434             gameInfo.whiteRating = player1Rating;
4435             gameInfo.blackRating = player2Rating;
4436         } else if (strcmp(player2Name, white) == 0 &&
4437                    strcmp(player1Name, black) == 0) {
4438             if (appData.debugMode)
4439               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4440                       player2Rating, player1Rating);
4441             gameInfo.whiteRating = player2Rating;
4442             gameInfo.blackRating = player1Rating;
4443         }
4444         player1Name[0] = player2Name[0] = NULLCHAR;
4445
4446         /* Silence shouts if requested */
4447         if (appData.quietPlay &&
4448             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4449             SendToICS(ics_prefix);
4450             SendToICS("set shout 0\n");
4451         }
4452     }
4453
4454     /* Deal with midgame name changes */
4455     if (!newGame) {
4456         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4457             if (gameInfo.white) free(gameInfo.white);
4458             gameInfo.white = StrSave(white);
4459         }
4460         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4461             if (gameInfo.black) free(gameInfo.black);
4462             gameInfo.black = StrSave(black);
4463         }
4464     }
4465
4466     /* Throw away game result if anything actually changes in examine mode */
4467     if (gameMode == IcsExamining && !newGame) {
4468         gameInfo.result = GameUnfinished;
4469         if (gameInfo.resultDetails != NULL) {
4470             free(gameInfo.resultDetails);
4471             gameInfo.resultDetails = NULL;
4472         }
4473     }
4474
4475     /* In pausing && IcsExamining mode, we ignore boards coming
4476        in if they are in a different variation than we are. */
4477     if (pauseExamInvalid) return;
4478     if (pausing && gameMode == IcsExamining) {
4479         if (moveNum <= pauseExamForwardMostMove) {
4480             pauseExamInvalid = TRUE;
4481             forwardMostMove = pauseExamForwardMostMove;
4482             return;
4483         }
4484     }
4485
4486   if (appData.debugMode) {
4487     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4488   }
4489     /* Parse the board */
4490     for (k = 0; k < ranks; k++) {
4491       for (j = 0; j < files; j++)
4492         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4493       if(gameInfo.holdingsWidth > 1) {
4494            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4495            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4496       }
4497     }
4498     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4499       board[5][BOARD_RGHT+1] = WhiteAngel;
4500       board[6][BOARD_RGHT+1] = WhiteMarshall;
4501       board[1][0] = BlackMarshall;
4502       board[2][0] = BlackAngel;
4503       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4504     }
4505     CopyBoard(boards[moveNum], board);
4506     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4507     if (moveNum == 0) {
4508         startedFromSetupPosition =
4509           !CompareBoards(board, initialPosition);
4510         if(startedFromSetupPosition)
4511             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4512     }
4513
4514     /* [HGM] Set castling rights. Take the outermost Rooks,
4515        to make it also work for FRC opening positions. Note that board12
4516        is really defective for later FRC positions, as it has no way to
4517        indicate which Rook can castle if they are on the same side of King.
4518        For the initial position we grant rights to the outermost Rooks,
4519        and remember thos rights, and we then copy them on positions
4520        later in an FRC game. This means WB might not recognize castlings with
4521        Rooks that have moved back to their original position as illegal,
4522        but in ICS mode that is not its job anyway.
4523     */
4524     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4525     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4526
4527         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4528             if(board[0][i] == WhiteRook) j = i;
4529         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4530         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4531             if(board[0][i] == WhiteRook) j = i;
4532         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4533         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4534             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4535         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4536         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4537             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4538         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4539
4540         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4541         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4542         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4543             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4544         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4545             if(board[BOARD_HEIGHT-1][k] == bKing)
4546                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4547         if(gameInfo.variant == VariantTwoKings) {
4548             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4549             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4550             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4551         }
4552     } else { int r;
4553         r = boards[moveNum][CASTLING][0] = initialRights[0];
4554         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4555         r = boards[moveNum][CASTLING][1] = initialRights[1];
4556         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4557         r = boards[moveNum][CASTLING][3] = initialRights[3];
4558         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4559         r = boards[moveNum][CASTLING][4] = initialRights[4];
4560         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4561         /* wildcastle kludge: always assume King has rights */
4562         r = boards[moveNum][CASTLING][2] = initialRights[2];
4563         r = boards[moveNum][CASTLING][5] = initialRights[5];
4564     }
4565     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4566     boards[moveNum][EP_STATUS] = EP_NONE;
4567     if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4568     if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4569     if(double_push !=  -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4570
4571
4572     if (ics_getting_history == H_GOT_REQ_HEADER ||
4573         ics_getting_history == H_GOT_UNREQ_HEADER) {
4574         /* This was an initial position from a move list, not
4575            the current position */
4576         return;
4577     }
4578
4579     /* Update currentMove and known move number limits */
4580     newMove = newGame || moveNum > forwardMostMove;
4581
4582     if (newGame) {
4583         forwardMostMove = backwardMostMove = currentMove = moveNum;
4584         if (gameMode == IcsExamining && moveNum == 0) {
4585           /* Workaround for ICS limitation: we are not told the wild
4586              type when starting to examine a game.  But if we ask for
4587              the move list, the move list header will tell us */
4588             ics_getting_history = H_REQUESTED;
4589             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4590             SendToICS(str);
4591         }
4592     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4593                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4594 #if ZIPPY
4595         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4596         /* [HGM] applied this also to an engine that is silently watching        */
4597         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4598             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4599             gameInfo.variant == currentlyInitializedVariant) {
4600           takeback = forwardMostMove - moveNum;
4601           for (i = 0; i < takeback; i++) {
4602             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4603             SendToProgram("undo\n", &first);
4604           }
4605         }
4606 #endif
4607
4608         forwardMostMove = moveNum;
4609         if (!pausing || currentMove > forwardMostMove)
4610           currentMove = forwardMostMove;
4611     } else {
4612         /* New part of history that is not contiguous with old part */
4613         if (pausing && gameMode == IcsExamining) {
4614             pauseExamInvalid = TRUE;
4615             forwardMostMove = pauseExamForwardMostMove;
4616             return;
4617         }
4618         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4619 #if ZIPPY
4620             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4621                 // [HGM] when we will receive the move list we now request, it will be
4622                 // fed to the engine from the first move on. So if the engine is not
4623                 // in the initial position now, bring it there.
4624                 InitChessProgram(&first, 0);
4625             }
4626 #endif
4627             ics_getting_history = H_REQUESTED;
4628             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4629             SendToICS(str);
4630         }
4631         forwardMostMove = backwardMostMove = currentMove = moveNum;
4632     }
4633
4634     /* Update the clocks */
4635     if (strchr(elapsed_time, '.')) {
4636       /* Time is in ms */
4637       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4638       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4639     } else {
4640       /* Time is in seconds */
4641       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4642       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4643     }
4644
4645
4646 #if ZIPPY
4647     if (appData.zippyPlay && newGame &&
4648         gameMode != IcsObserving && gameMode != IcsIdle &&
4649         gameMode != IcsExamining)
4650       ZippyFirstBoard(moveNum, basetime, increment);
4651 #endif
4652
4653     /* Put the move on the move list, first converting
4654        to canonical algebraic form. */
4655     if (moveNum > 0) {
4656   if (appData.debugMode) {
4657     if (appData.debugMode) { int f = forwardMostMove;
4658         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4659                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4660                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4661     }
4662     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4663     fprintf(debugFP, "moveNum = %d\n", moveNum);
4664     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4665     setbuf(debugFP, NULL);
4666   }
4667         if (moveNum <= backwardMostMove) {
4668             /* We don't know what the board looked like before
4669                this move.  Punt. */
4670           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4671             strcat(parseList[moveNum - 1], " ");
4672             strcat(parseList[moveNum - 1], elapsed_time);
4673             moveList[moveNum - 1][0] = NULLCHAR;
4674         } else if (strcmp(move_str, "none") == 0) {
4675             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4676             /* Again, we don't know what the board looked like;
4677                this is really the start of the game. */
4678             parseList[moveNum - 1][0] = NULLCHAR;
4679             moveList[moveNum - 1][0] = NULLCHAR;
4680             backwardMostMove = moveNum;
4681             startedFromSetupPosition = TRUE;
4682             fromX = fromY = toX = toY = -1;
4683         } else {
4684           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4685           //                 So we parse the long-algebraic move string in stead of the SAN move
4686           int valid; char buf[MSG_SIZ], *prom;
4687
4688           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4689                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4690           // str looks something like "Q/a1-a2"; kill the slash
4691           if(str[1] == '/')
4692             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4693           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4694           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4695                 strcat(buf, prom); // long move lacks promo specification!
4696           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4697                 if(appData.debugMode)
4698                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4699                 safeStrCpy(move_str, buf, MSG_SIZ);
4700           }
4701           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4702                                 &fromX, &fromY, &toX, &toY, &promoChar)
4703                || ParseOneMove(buf, moveNum - 1, &moveType,
4704                                 &fromX, &fromY, &toX, &toY, &promoChar);
4705           // end of long SAN patch
4706           if (valid) {
4707             (void) CoordsToAlgebraic(boards[moveNum - 1],
4708                                      PosFlags(moveNum - 1),
4709                                      fromY, fromX, toY, toX, promoChar,
4710                                      parseList[moveNum-1]);
4711             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4712               case MT_NONE:
4713               case MT_STALEMATE:
4714               default:
4715                 break;
4716               case MT_CHECK:
4717                 if(gameInfo.variant != VariantShogi)
4718                     strcat(parseList[moveNum - 1], "+");
4719                 break;
4720               case MT_CHECKMATE:
4721               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4722                 strcat(parseList[moveNum - 1], "#");
4723                 break;
4724             }
4725             strcat(parseList[moveNum - 1], " ");
4726             strcat(parseList[moveNum - 1], elapsed_time);
4727             /* currentMoveString is set as a side-effect of ParseOneMove */
4728             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4729             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4730             strcat(moveList[moveNum - 1], "\n");
4731
4732             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4733                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4734               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4735                 ChessSquare old, new = boards[moveNum][k][j];
4736                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4737                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4738                   if(old == new) continue;
4739                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4740                   else if(new == WhiteWazir || new == BlackWazir) {
4741                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4742                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4743                       else boards[moveNum][k][j] = old; // preserve type of Gold
4744                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4745                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4746               }
4747           } else {
4748             /* Move from ICS was illegal!?  Punt. */
4749             if (appData.debugMode) {
4750               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4751               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4752             }
4753             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4754             strcat(parseList[moveNum - 1], " ");
4755             strcat(parseList[moveNum - 1], elapsed_time);
4756             moveList[moveNum - 1][0] = NULLCHAR;
4757             fromX = fromY = toX = toY = -1;
4758           }
4759         }
4760   if (appData.debugMode) {
4761     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4762     setbuf(debugFP, NULL);
4763   }
4764
4765 #if ZIPPY
4766         /* Send move to chess program (BEFORE animating it). */
4767         if (appData.zippyPlay && !newGame && newMove &&
4768            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4769
4770             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4771                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4772                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4773                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4774                             move_str);
4775                     DisplayError(str, 0);
4776                 } else {
4777                     if (first.sendTime) {
4778                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4779                     }
4780                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4781                     if (firstMove && !bookHit) {
4782                         firstMove = FALSE;
4783                         if (first.useColors) {
4784                           SendToProgram(gameMode == IcsPlayingWhite ?
4785                                         "white\ngo\n" :
4786                                         "black\ngo\n", &first);
4787                         } else {
4788                           SendToProgram("go\n", &first);
4789                         }
4790                         first.maybeThinking = TRUE;
4791                     }
4792                 }
4793             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4794               if (moveList[moveNum - 1][0] == NULLCHAR) {
4795                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4796                 DisplayError(str, 0);
4797               } else {
4798                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4799                 SendMoveToProgram(moveNum - 1, &first);
4800               }
4801             }
4802         }
4803 #endif
4804     }
4805
4806     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4807         /* If move comes from a remote source, animate it.  If it
4808            isn't remote, it will have already been animated. */
4809         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4810             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4811         }
4812         if (!pausing && appData.highlightLastMove) {
4813             SetHighlights(fromX, fromY, toX, toY);
4814         }
4815     }
4816
4817     /* Start the clocks */
4818     whiteFlag = blackFlag = FALSE;
4819     appData.clockMode = !(basetime == 0 && increment == 0);
4820     if (ticking == 0) {
4821       ics_clock_paused = TRUE;
4822       StopClocks();
4823     } else if (ticking == 1) {
4824       ics_clock_paused = FALSE;
4825     }
4826     if (gameMode == IcsIdle ||
4827         relation == RELATION_OBSERVING_STATIC ||
4828         relation == RELATION_EXAMINING ||
4829         ics_clock_paused)
4830       DisplayBothClocks();
4831     else
4832       StartClocks();
4833
4834     /* Display opponents and material strengths */
4835     if (gameInfo.variant != VariantBughouse &&
4836         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4837         if (tinyLayout || smallLayout) {
4838             if(gameInfo.variant == VariantNormal)
4839               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4840                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4841                     basetime, increment);
4842             else
4843               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4844                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4845                     basetime, increment, (int) gameInfo.variant);
4846         } else {
4847             if(gameInfo.variant == VariantNormal)
4848               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4849                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4850                     basetime, increment);
4851             else
4852               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
4853                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4854                     basetime, increment, VariantName(gameInfo.variant));
4855         }
4856         DisplayTitle(str);
4857   if (appData.debugMode) {
4858     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4859   }
4860     }
4861
4862
4863     /* Display the board */
4864     if (!pausing && !appData.noGUI) {
4865
4866       if (appData.premove)
4867           if (!gotPremove ||
4868              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4869              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4870               ClearPremoveHighlights();
4871
4872       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4873         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4874       DrawPosition(j, boards[currentMove]);
4875
4876       DisplayMove(moveNum - 1);
4877       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4878             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4879               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4880         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4881       }
4882     }
4883
4884     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4885 #if ZIPPY
4886     if(bookHit) { // [HGM] book: simulate book reply
4887         static char bookMove[MSG_SIZ]; // a bit generous?
4888
4889         programStats.nodes = programStats.depth = programStats.time =
4890         programStats.score = programStats.got_only_move = 0;
4891         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4892
4893         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4894         strcat(bookMove, bookHit);
4895         HandleMachineMove(bookMove, &first);
4896     }
4897 #endif
4898 }
4899
4900 void
4901 GetMoveListEvent ()
4902 {
4903     char buf[MSG_SIZ];
4904     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4905         ics_getting_history = H_REQUESTED;
4906         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4907         SendToICS(buf);
4908     }
4909 }
4910
4911 void
4912 SendToBoth (char *msg)
4913 {   // to make it easy to keep two engines in step in dual analysis
4914     SendToProgram(msg, &first);
4915     if(second.analyzing) SendToProgram(msg, &second);
4916 }
4917
4918 void
4919 AnalysisPeriodicEvent (int force)
4920 {
4921     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4922          && !force) || !appData.periodicUpdates)
4923       return;
4924
4925     /* Send . command to Crafty to collect stats */
4926     SendToBoth(".\n");
4927
4928     /* Don't send another until we get a response (this makes
4929        us stop sending to old Crafty's which don't understand
4930        the "." command (sending illegal cmds resets node count & time,
4931        which looks bad)) */
4932     programStats.ok_to_send = 0;
4933 }
4934
4935 void
4936 ics_update_width (int new_width)
4937 {
4938         ics_printf("set width %d\n", new_width);
4939 }
4940
4941 void
4942 SendMoveToProgram (int moveNum, ChessProgramState *cps)
4943 {
4944     char buf[MSG_SIZ];
4945
4946     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
4947         // null move in variant where engine does not understand it (for analysis purposes)
4948         SendBoard(cps, moveNum + 1); // send position after move in stead.
4949         return;
4950     }
4951     if (cps->useUsermove) {
4952       SendToProgram("usermove ", cps);
4953     }
4954     if (cps->useSAN) {
4955       char *space;
4956       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4957         int len = space - parseList[moveNum];
4958         memcpy(buf, parseList[moveNum], len);
4959         buf[len++] = '\n';
4960         buf[len] = NULLCHAR;
4961       } else {
4962         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4963       }
4964       SendToProgram(buf, cps);
4965     } else {
4966       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4967         AlphaRank(moveList[moveNum], 4);
4968         SendToProgram(moveList[moveNum], cps);
4969         AlphaRank(moveList[moveNum], 4); // and back
4970       } else
4971       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4972        * the engine. It would be nice to have a better way to identify castle
4973        * moves here. */
4974       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4975                                                                          && cps->useOOCastle) {
4976         int fromX = moveList[moveNum][0] - AAA;
4977         int fromY = moveList[moveNum][1] - ONE;
4978         int toX = moveList[moveNum][2] - AAA;
4979         int toY = moveList[moveNum][3] - ONE;
4980         if((boards[moveNum][fromY][fromX] == WhiteKing
4981             && boards[moveNum][toY][toX] == WhiteRook)
4982            || (boards[moveNum][fromY][fromX] == BlackKing
4983                && boards[moveNum][toY][toX] == BlackRook)) {
4984           if(toX > fromX) SendToProgram("O-O\n", cps);
4985           else SendToProgram("O-O-O\n", cps);
4986         }
4987         else SendToProgram(moveList[moveNum], cps);
4988       } else
4989       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
4990         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
4991           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
4992           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
4993                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4994         } else
4995           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
4996                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4997         SendToProgram(buf, cps);
4998       }
4999       else SendToProgram(moveList[moveNum], cps);
5000       /* End of additions by Tord */
5001     }
5002
5003     /* [HGM] setting up the opening has brought engine in force mode! */
5004     /*       Send 'go' if we are in a mode where machine should play. */
5005     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5006         (gameMode == TwoMachinesPlay   ||
5007 #if ZIPPY
5008          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
5009 #endif
5010          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5011         SendToProgram("go\n", cps);
5012   if (appData.debugMode) {
5013     fprintf(debugFP, "(extra)\n");
5014   }
5015     }
5016     setboardSpoiledMachineBlack = 0;
5017 }
5018
5019 void
5020 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5021 {
5022     char user_move[MSG_SIZ];
5023     char suffix[4];
5024
5025     if(gameInfo.variant == VariantSChess && promoChar) {
5026         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5027         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5028     } else suffix[0] = NULLCHAR;
5029
5030     switch (moveType) {
5031       default:
5032         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5033                 (int)moveType, fromX, fromY, toX, toY);
5034         DisplayError(user_move + strlen("say "), 0);
5035         break;
5036       case WhiteKingSideCastle:
5037       case BlackKingSideCastle:
5038       case WhiteQueenSideCastleWild:
5039       case BlackQueenSideCastleWild:
5040       /* PUSH Fabien */
5041       case WhiteHSideCastleFR:
5042       case BlackHSideCastleFR:
5043       /* POP Fabien */
5044         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5045         break;
5046       case WhiteQueenSideCastle:
5047       case BlackQueenSideCastle:
5048       case WhiteKingSideCastleWild:
5049       case BlackKingSideCastleWild:
5050       /* PUSH Fabien */
5051       case WhiteASideCastleFR:
5052       case BlackASideCastleFR:
5053       /* POP Fabien */
5054         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5055         break;
5056       case WhiteNonPromotion:
5057       case BlackNonPromotion:
5058         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5059         break;
5060       case WhitePromotion:
5061       case BlackPromotion:
5062         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
5063           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5064                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5065                 PieceToChar(WhiteFerz));
5066         else if(gameInfo.variant == VariantGreat)
5067           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5068                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5069                 PieceToChar(WhiteMan));
5070         else
5071           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5072                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5073                 promoChar);
5074         break;
5075       case WhiteDrop:
5076       case BlackDrop:
5077       drop:
5078         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5079                  ToUpper(PieceToChar((ChessSquare) fromX)),
5080                  AAA + toX, ONE + toY);
5081         break;
5082       case IllegalMove:  /* could be a variant we don't quite understand */
5083         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5084       case NormalMove:
5085       case WhiteCapturesEnPassant:
5086       case BlackCapturesEnPassant:
5087         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5088                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5089         break;
5090     }
5091     SendToICS(user_move);
5092     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5093         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5094 }
5095
5096 void
5097 UploadGameEvent ()
5098 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5099     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5100     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5101     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5102       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5103       return;
5104     }
5105     if(gameMode != IcsExamining) { // is this ever not the case?
5106         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5107
5108         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5109           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5110         } else { // on FICS we must first go to general examine mode
5111           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5112         }
5113         if(gameInfo.variant != VariantNormal) {
5114             // try figure out wild number, as xboard names are not always valid on ICS
5115             for(i=1; i<=36; i++) {
5116               snprintf(buf, MSG_SIZ, "wild/%d", i);
5117                 if(StringToVariant(buf) == gameInfo.variant) break;
5118             }
5119             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5120             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5121             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5122         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5123         SendToICS(ics_prefix);
5124         SendToICS(buf);
5125         if(startedFromSetupPosition || backwardMostMove != 0) {
5126           fen = PositionToFEN(backwardMostMove, NULL);
5127           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5128             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5129             SendToICS(buf);
5130           } else { // FICS: everything has to set by separate bsetup commands
5131             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5132             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5133             SendToICS(buf);
5134             if(!WhiteOnMove(backwardMostMove)) {
5135                 SendToICS("bsetup tomove black\n");
5136             }
5137             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5138             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5139             SendToICS(buf);
5140             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5141             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5142             SendToICS(buf);
5143             i = boards[backwardMostMove][EP_STATUS];
5144             if(i >= 0) { // set e.p.
5145               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5146                 SendToICS(buf);
5147             }
5148             bsetup++;
5149           }
5150         }
5151       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5152             SendToICS("bsetup done\n"); // switch to normal examining.
5153     }
5154     for(i = backwardMostMove; i<last; i++) {
5155         char buf[20];
5156         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5157         if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5158             int len = strlen(moveList[i]);
5159             snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5160             if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5161         }
5162         SendToICS(buf);
5163     }
5164     SendToICS(ics_prefix);
5165     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5166 }
5167
5168 void
5169 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5170 {
5171     if (rf == DROP_RANK) {
5172       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5173       sprintf(move, "%c@%c%c\n",
5174                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5175     } else {
5176         if (promoChar == 'x' || promoChar == NULLCHAR) {
5177           sprintf(move, "%c%c%c%c\n",
5178                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5179         } else {
5180             sprintf(move, "%c%c%c%c%c\n",
5181                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5182         }
5183     }
5184 }
5185
5186 void
5187 ProcessICSInitScript (FILE *f)
5188 {
5189     char buf[MSG_SIZ];
5190
5191     while (fgets(buf, MSG_SIZ, f)) {
5192         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5193     }
5194
5195     fclose(f);
5196 }
5197
5198
5199 static int lastX, lastY, selectFlag, dragging;
5200
5201 void
5202 Sweep (int step)
5203 {
5204     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5205     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5206     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5207     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5208     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5209     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5210     do {
5211         promoSweep -= step;
5212         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5213         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5214         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5215         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5216         if(!step) step = -1;
5217     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5218             appData.testLegality && (promoSweep == king ||
5219             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5220     if(toX >= 0) {
5221         int victim = boards[currentMove][toY][toX];
5222         boards[currentMove][toY][toX] = promoSweep;
5223         DrawPosition(FALSE, boards[currentMove]);
5224         boards[currentMove][toY][toX] = victim;
5225     } else
5226     ChangeDragPiece(promoSweep);
5227 }
5228
5229 int
5230 PromoScroll (int x, int y)
5231 {
5232   int step = 0;
5233
5234   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5235   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5236   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5237   if(!step) return FALSE;
5238   lastX = x; lastY = y;
5239   if((promoSweep < BlackPawn) == flipView) step = -step;
5240   if(step > 0) selectFlag = 1;
5241   if(!selectFlag) Sweep(step);
5242   return FALSE;
5243 }
5244
5245 void
5246 NextPiece (int step)
5247 {
5248     ChessSquare piece = boards[currentMove][toY][toX];
5249     do {
5250         pieceSweep -= step;
5251         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5252         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5253         if(!step) step = -1;
5254     } while(PieceToChar(pieceSweep) == '.');
5255     boards[currentMove][toY][toX] = pieceSweep;
5256     DrawPosition(FALSE, boards[currentMove]);
5257     boards[currentMove][toY][toX] = piece;
5258 }
5259 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5260 void
5261 AlphaRank (char *move, int n)
5262 {
5263 //    char *p = move, c; int x, y;
5264
5265     if (appData.debugMode) {
5266         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5267     }
5268
5269     if(move[1]=='*' &&
5270        move[2]>='0' && move[2]<='9' &&
5271        move[3]>='a' && move[3]<='x'    ) {
5272         move[1] = '@';
5273         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5274         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5275     } else
5276     if(move[0]>='0' && move[0]<='9' &&
5277        move[1]>='a' && move[1]<='x' &&
5278        move[2]>='0' && move[2]<='9' &&
5279        move[3]>='a' && move[3]<='x'    ) {
5280         /* input move, Shogi -> normal */
5281         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5282         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5283         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5284         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5285     } else
5286     if(move[1]=='@' &&
5287        move[3]>='0' && move[3]<='9' &&
5288        move[2]>='a' && move[2]<='x'    ) {
5289         move[1] = '*';
5290         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5291         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5292     } else
5293     if(
5294        move[0]>='a' && move[0]<='x' &&
5295        move[3]>='0' && move[3]<='9' &&
5296        move[2]>='a' && move[2]<='x'    ) {
5297          /* output move, normal -> Shogi */
5298         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5299         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5300         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5301         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5302         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5303     }
5304     if (appData.debugMode) {
5305         fprintf(debugFP, "   out = '%s'\n", move);
5306     }
5307 }
5308
5309 char yy_textstr[8000];
5310
5311 /* Parser for moves from gnuchess, ICS, or user typein box */
5312 Boolean
5313 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5314 {
5315     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5316
5317     switch (*moveType) {
5318       case WhitePromotion:
5319       case BlackPromotion:
5320       case WhiteNonPromotion:
5321       case BlackNonPromotion:
5322       case NormalMove:
5323       case WhiteCapturesEnPassant:
5324       case BlackCapturesEnPassant:
5325       case WhiteKingSideCastle:
5326       case WhiteQueenSideCastle:
5327       case BlackKingSideCastle:
5328       case BlackQueenSideCastle:
5329       case WhiteKingSideCastleWild:
5330       case WhiteQueenSideCastleWild:
5331       case BlackKingSideCastleWild:
5332       case BlackQueenSideCastleWild:
5333       /* Code added by Tord: */
5334       case WhiteHSideCastleFR:
5335       case WhiteASideCastleFR:
5336       case BlackHSideCastleFR:
5337       case BlackASideCastleFR:
5338       /* End of code added by Tord */
5339       case IllegalMove:         /* bug or odd chess variant */
5340         *fromX = currentMoveString[0] - AAA;
5341         *fromY = currentMoveString[1] - ONE;
5342         *toX = currentMoveString[2] - AAA;
5343         *toY = currentMoveString[3] - ONE;
5344         *promoChar = currentMoveString[4];
5345         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5346             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5347     if (appData.debugMode) {
5348         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5349     }
5350             *fromX = *fromY = *toX = *toY = 0;
5351             return FALSE;
5352         }
5353         if (appData.testLegality) {
5354           return (*moveType != IllegalMove);
5355         } else {
5356           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5357                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5358         }
5359
5360       case WhiteDrop:
5361       case BlackDrop:
5362         *fromX = *moveType == WhiteDrop ?
5363           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5364           (int) CharToPiece(ToLower(currentMoveString[0]));
5365         *fromY = DROP_RANK;
5366         *toX = currentMoveString[2] - AAA;
5367         *toY = currentMoveString[3] - ONE;
5368         *promoChar = NULLCHAR;
5369         return TRUE;
5370
5371       case AmbiguousMove:
5372       case ImpossibleMove:
5373       case EndOfFile:
5374       case ElapsedTime:
5375       case Comment:
5376       case PGNTag:
5377       case NAG:
5378       case WhiteWins:
5379       case BlackWins:
5380       case GameIsDrawn:
5381       default:
5382     if (appData.debugMode) {
5383         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5384     }
5385         /* bug? */
5386         *fromX = *fromY = *toX = *toY = 0;
5387         *promoChar = NULLCHAR;
5388         return FALSE;
5389     }
5390 }
5391
5392 Boolean pushed = FALSE;
5393 char *lastParseAttempt;
5394
5395 void
5396 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5397 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5398   int fromX, fromY, toX, toY; char promoChar;
5399   ChessMove moveType;
5400   Boolean valid;
5401   int nr = 0;
5402
5403   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5404     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5405     pushed = TRUE;
5406   }
5407   endPV = forwardMostMove;
5408   do {
5409     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5410     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5411     lastParseAttempt = pv;
5412     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5413     if(!valid && nr == 0 &&
5414        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5415         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5416         // Hande case where played move is different from leading PV move
5417         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5418         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5419         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5420         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5421           endPV += 2; // if position different, keep this
5422           moveList[endPV-1][0] = fromX + AAA;
5423           moveList[endPV-1][1] = fromY + ONE;
5424           moveList[endPV-1][2] = toX + AAA;
5425           moveList[endPV-1][3] = toY + ONE;
5426           parseList[endPV-1][0] = NULLCHAR;
5427           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5428         }
5429       }
5430     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5431     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5432     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5433     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5434         valid++; // allow comments in PV
5435         continue;
5436     }
5437     nr++;
5438     if(endPV+1 > framePtr) break; // no space, truncate
5439     if(!valid) break;
5440     endPV++;
5441     CopyBoard(boards[endPV], boards[endPV-1]);
5442     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5443     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5444     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5445     CoordsToAlgebraic(boards[endPV - 1],
5446                              PosFlags(endPV - 1),
5447                              fromY, fromX, toY, toX, promoChar,
5448                              parseList[endPV - 1]);
5449   } while(valid);
5450   if(atEnd == 2) return; // used hidden, for PV conversion
5451   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5452   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5453   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5454                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5455   DrawPosition(TRUE, boards[currentMove]);
5456 }
5457
5458 int
5459 MultiPV (ChessProgramState *cps)
5460 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5461         int i;
5462         for(i=0; i<cps->nrOptions; i++)
5463             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5464                 return i;
5465         return -1;
5466 }
5467
5468 Boolean
5469 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5470 {
5471         int startPV, multi, lineStart, origIndex = index;
5472         char *p, buf2[MSG_SIZ];
5473         ChessProgramState *cps = (pane ? &second : &first);
5474
5475         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5476         lastX = x; lastY = y;
5477         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5478         lineStart = startPV = index;
5479         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5480         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5481         index = startPV;
5482         do{ while(buf[index] && buf[index] != '\n') index++;
5483         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5484         buf[index] = 0;
5485         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(cps)) >= 0) {
5486                 int n = cps->option[multi].value;
5487                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5488                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5489                 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5490                 cps->option[multi].value = n;
5491                 *start = *end = 0;
5492                 return FALSE;
5493         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5494                 ExcludeClick(origIndex - lineStart);
5495                 return FALSE;
5496         }
5497         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5498         *start = startPV; *end = index-1;
5499         return TRUE;
5500 }
5501
5502 char *
5503 PvToSAN (char *pv)
5504 {
5505         static char buf[10*MSG_SIZ];
5506         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5507         *buf = NULLCHAR;
5508         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV);
5509         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5510         for(i = forwardMostMove; i<endPV; i++){
5511             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5512             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5513             k += strlen(buf+k);
5514         }
5515         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5516         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5517         endPV = savedEnd;
5518         return buf;
5519 }
5520
5521 Boolean
5522 LoadPV (int x, int y)
5523 { // called on right mouse click to load PV
5524   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5525   lastX = x; lastY = y;
5526   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5527   return TRUE;
5528 }
5529
5530 void
5531 UnLoadPV ()
5532 {
5533   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5534   if(endPV < 0) return;
5535   if(appData.autoCopyPV) CopyFENToClipboard();
5536   endPV = -1;
5537   if(gameMode == AnalyzeMode && currentMove > forwardMostMove) {
5538         Boolean saveAnimate = appData.animate;
5539         if(pushed) {
5540             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5541                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5542             } else storedGames--; // abandon shelved tail of original game
5543         }
5544         pushed = FALSE;
5545         forwardMostMove = currentMove;
5546         currentMove = oldFMM;
5547         appData.animate = FALSE;
5548         ToNrEvent(forwardMostMove);
5549         appData.animate = saveAnimate;
5550   }
5551   currentMove = forwardMostMove;
5552   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5553   ClearPremoveHighlights();
5554   DrawPosition(TRUE, boards[currentMove]);
5555 }
5556
5557 void
5558 MovePV (int x, int y, int h)
5559 { // step through PV based on mouse coordinates (called on mouse move)
5560   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5561
5562   // we must somehow check if right button is still down (might be released off board!)
5563   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5564   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5565   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5566   if(!step) return;
5567   lastX = x; lastY = y;
5568
5569   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5570   if(endPV < 0) return;
5571   if(y < margin) step = 1; else
5572   if(y > h - margin) step = -1;
5573   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5574   currentMove += step;
5575   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5576   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5577                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5578   DrawPosition(FALSE, boards[currentMove]);
5579 }
5580
5581
5582 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5583 // All positions will have equal probability, but the current method will not provide a unique
5584 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5585 #define DARK 1
5586 #define LITE 2
5587 #define ANY 3
5588
5589 int squaresLeft[4];
5590 int piecesLeft[(int)BlackPawn];
5591 int seed, nrOfShuffles;
5592
5593 void
5594 GetPositionNumber ()
5595 {       // sets global variable seed
5596         int i;
5597
5598         seed = appData.defaultFrcPosition;
5599         if(seed < 0) { // randomize based on time for negative FRC position numbers
5600                 for(i=0; i<50; i++) seed += random();
5601                 seed = random() ^ random() >> 8 ^ random() << 8;
5602                 if(seed<0) seed = -seed;
5603         }
5604 }
5605
5606 int
5607 put (Board board, int pieceType, int rank, int n, int shade)
5608 // put the piece on the (n-1)-th empty squares of the given shade
5609 {
5610         int i;
5611
5612         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5613                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5614                         board[rank][i] = (ChessSquare) pieceType;
5615                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5616                         squaresLeft[ANY]--;
5617                         piecesLeft[pieceType]--;
5618                         return i;
5619                 }
5620         }
5621         return -1;
5622 }
5623
5624
5625 void
5626 AddOnePiece (Board board, int pieceType, int rank, int shade)
5627 // calculate where the next piece goes, (any empty square), and put it there
5628 {
5629         int i;
5630
5631         i = seed % squaresLeft[shade];
5632         nrOfShuffles *= squaresLeft[shade];
5633         seed /= squaresLeft[shade];
5634         put(board, pieceType, rank, i, shade);
5635 }
5636
5637 void
5638 AddTwoPieces (Board board, int pieceType, int rank)
5639 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5640 {
5641         int i, n=squaresLeft[ANY], j=n-1, k;
5642
5643         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5644         i = seed % k;  // pick one
5645         nrOfShuffles *= k;
5646         seed /= k;
5647         while(i >= j) i -= j--;
5648         j = n - 1 - j; i += j;
5649         put(board, pieceType, rank, j, ANY);
5650         put(board, pieceType, rank, i, ANY);
5651 }
5652
5653 void
5654 SetUpShuffle (Board board, int number)
5655 {
5656         int i, p, first=1;
5657
5658         GetPositionNumber(); nrOfShuffles = 1;
5659
5660         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5661         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5662         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5663
5664         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5665
5666         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5667             p = (int) board[0][i];
5668             if(p < (int) BlackPawn) piecesLeft[p] ++;
5669             board[0][i] = EmptySquare;
5670         }
5671
5672         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5673             // shuffles restricted to allow normal castling put KRR first
5674             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5675                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5676             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5677                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5678             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5679                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5680             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5681                 put(board, WhiteRook, 0, 0, ANY);
5682             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5683         }
5684
5685         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5686             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5687             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5688                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5689                 while(piecesLeft[p] >= 2) {
5690                     AddOnePiece(board, p, 0, LITE);
5691                     AddOnePiece(board, p, 0, DARK);
5692                 }
5693                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5694             }
5695
5696         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5697             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5698             // but we leave King and Rooks for last, to possibly obey FRC restriction
5699             if(p == (int)WhiteRook) continue;
5700             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5701             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5702         }
5703
5704         // now everything is placed, except perhaps King (Unicorn) and Rooks
5705
5706         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5707             // Last King gets castling rights
5708             while(piecesLeft[(int)WhiteUnicorn]) {
5709                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5710                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5711             }
5712
5713             while(piecesLeft[(int)WhiteKing]) {
5714                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5715                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5716             }
5717
5718
5719         } else {
5720             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5721             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5722         }
5723
5724         // Only Rooks can be left; simply place them all
5725         while(piecesLeft[(int)WhiteRook]) {
5726                 i = put(board, WhiteRook, 0, 0, ANY);
5727                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5728                         if(first) {
5729                                 first=0;
5730                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5731                         }
5732                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5733                 }
5734         }
5735         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5736             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5737         }
5738
5739         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5740 }
5741
5742 int
5743 SetCharTable (char *table, const char * map)
5744 /* [HGM] moved here from winboard.c because of its general usefulness */
5745 /*       Basically a safe strcpy that uses the last character as King */
5746 {
5747     int result = FALSE; int NrPieces;
5748
5749     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5750                     && NrPieces >= 12 && !(NrPieces&1)) {
5751         int i; /* [HGM] Accept even length from 12 to 34 */
5752
5753         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5754         for( i=0; i<NrPieces/2-1; i++ ) {
5755             table[i] = map[i];
5756             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5757         }
5758         table[(int) WhiteKing]  = map[NrPieces/2-1];
5759         table[(int) BlackKing]  = map[NrPieces-1];
5760
5761         result = TRUE;
5762     }
5763
5764     return result;
5765 }
5766
5767 void
5768 Prelude (Board board)
5769 {       // [HGM] superchess: random selection of exo-pieces
5770         int i, j, k; ChessSquare p;
5771         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5772
5773         GetPositionNumber(); // use FRC position number
5774
5775         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5776             SetCharTable(pieceToChar, appData.pieceToCharTable);
5777             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5778                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5779         }
5780
5781         j = seed%4;                 seed /= 4;
5782         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5783         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5784         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5785         j = seed%3 + (seed%3 >= j); seed /= 3;
5786         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5787         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5788         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5789         j = seed%3;                 seed /= 3;
5790         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5791         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5792         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5793         j = seed%2 + (seed%2 >= j); seed /= 2;
5794         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5795         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5796         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5797         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5798         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5799         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5800         put(board, exoPieces[0],    0, 0, ANY);
5801         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5802 }
5803
5804 void
5805 InitPosition (int redraw)
5806 {
5807     ChessSquare (* pieces)[BOARD_FILES];
5808     int i, j, pawnRow, overrule,
5809     oldx = gameInfo.boardWidth,
5810     oldy = gameInfo.boardHeight,
5811     oldh = gameInfo.holdingsWidth;
5812     static int oldv;
5813
5814     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5815
5816     /* [AS] Initialize pv info list [HGM] and game status */
5817     {
5818         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5819             pvInfoList[i].depth = 0;
5820             boards[i][EP_STATUS] = EP_NONE;
5821             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5822         }
5823
5824         initialRulePlies = 0; /* 50-move counter start */
5825
5826         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5827         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5828     }
5829
5830
5831     /* [HGM] logic here is completely changed. In stead of full positions */
5832     /* the initialized data only consist of the two backranks. The switch */
5833     /* selects which one we will use, which is than copied to the Board   */
5834     /* initialPosition, which for the rest is initialized by Pawns and    */
5835     /* empty squares. This initial position is then copied to boards[0],  */
5836     /* possibly after shuffling, so that it remains available.            */
5837
5838     gameInfo.holdingsWidth = 0; /* default board sizes */
5839     gameInfo.boardWidth    = 8;
5840     gameInfo.boardHeight   = 8;
5841     gameInfo.holdingsSize  = 0;
5842     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5843     for(i=0; i<BOARD_FILES-2; i++)
5844       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5845     initialPosition[EP_STATUS] = EP_NONE;
5846     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5847     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5848          SetCharTable(pieceNickName, appData.pieceNickNames);
5849     else SetCharTable(pieceNickName, "............");
5850     pieces = FIDEArray;
5851
5852     switch (gameInfo.variant) {
5853     case VariantFischeRandom:
5854       shuffleOpenings = TRUE;
5855     default:
5856       break;
5857     case VariantShatranj:
5858       pieces = ShatranjArray;
5859       nrCastlingRights = 0;
5860       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5861       break;
5862     case VariantMakruk:
5863       pieces = makrukArray;
5864       nrCastlingRights = 0;
5865       startedFromSetupPosition = TRUE;
5866       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5867       break;
5868     case VariantTwoKings:
5869       pieces = twoKingsArray;
5870       break;
5871     case VariantGrand:
5872       pieces = GrandArray;
5873       nrCastlingRights = 0;
5874       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5875       gameInfo.boardWidth = 10;
5876       gameInfo.boardHeight = 10;
5877       gameInfo.holdingsSize = 7;
5878       break;
5879     case VariantCapaRandom:
5880       shuffleOpenings = TRUE;
5881     case VariantCapablanca:
5882       pieces = CapablancaArray;
5883       gameInfo.boardWidth = 10;
5884       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5885       break;
5886     case VariantGothic:
5887       pieces = GothicArray;
5888       gameInfo.boardWidth = 10;
5889       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5890       break;
5891     case VariantSChess:
5892       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5893       gameInfo.holdingsSize = 7;
5894       for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
5895       break;
5896     case VariantJanus:
5897       pieces = JanusArray;
5898       gameInfo.boardWidth = 10;
5899       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5900       nrCastlingRights = 6;
5901         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5902         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5903         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5904         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5905         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5906         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5907       break;
5908     case VariantFalcon:
5909       pieces = FalconArray;
5910       gameInfo.boardWidth = 10;
5911       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5912       break;
5913     case VariantXiangqi:
5914       pieces = XiangqiArray;
5915       gameInfo.boardWidth  = 9;
5916       gameInfo.boardHeight = 10;
5917       nrCastlingRights = 0;
5918       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5919       break;
5920     case VariantShogi:
5921       pieces = ShogiArray;
5922       gameInfo.boardWidth  = 9;
5923       gameInfo.boardHeight = 9;
5924       gameInfo.holdingsSize = 7;
5925       nrCastlingRights = 0;
5926       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5927       break;
5928     case VariantCourier:
5929       pieces = CourierArray;
5930       gameInfo.boardWidth  = 12;
5931       nrCastlingRights = 0;
5932       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5933       break;
5934     case VariantKnightmate:
5935       pieces = KnightmateArray;
5936       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5937       break;
5938     case VariantSpartan:
5939       pieces = SpartanArray;
5940       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5941       break;
5942     case VariantFairy:
5943       pieces = fairyArray;
5944       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5945       break;
5946     case VariantGreat:
5947       pieces = GreatArray;
5948       gameInfo.boardWidth = 10;
5949       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5950       gameInfo.holdingsSize = 8;
5951       break;
5952     case VariantSuper:
5953       pieces = FIDEArray;
5954       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5955       gameInfo.holdingsSize = 8;
5956       startedFromSetupPosition = TRUE;
5957       break;
5958     case VariantCrazyhouse:
5959     case VariantBughouse:
5960       pieces = FIDEArray;
5961       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5962       gameInfo.holdingsSize = 5;
5963       break;
5964     case VariantWildCastle:
5965       pieces = FIDEArray;
5966       /* !!?shuffle with kings guaranteed to be on d or e file */
5967       shuffleOpenings = 1;
5968       break;
5969     case VariantNoCastle:
5970       pieces = FIDEArray;
5971       nrCastlingRights = 0;
5972       /* !!?unconstrained back-rank shuffle */
5973       shuffleOpenings = 1;
5974       break;
5975     }
5976
5977     overrule = 0;
5978     if(appData.NrFiles >= 0) {
5979         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5980         gameInfo.boardWidth = appData.NrFiles;
5981     }
5982     if(appData.NrRanks >= 0) {
5983         gameInfo.boardHeight = appData.NrRanks;
5984     }
5985     if(appData.holdingsSize >= 0) {
5986         i = appData.holdingsSize;
5987         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5988         gameInfo.holdingsSize = i;
5989     }
5990     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5991     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5992         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5993
5994     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5995     if(pawnRow < 1) pawnRow = 1;
5996     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) pawnRow = 2;
5997
5998     /* User pieceToChar list overrules defaults */
5999     if(appData.pieceToCharTable != NULL)
6000         SetCharTable(pieceToChar, appData.pieceToCharTable);
6001
6002     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6003
6004         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6005             s = (ChessSquare) 0; /* account holding counts in guard band */
6006         for( i=0; i<BOARD_HEIGHT; i++ )
6007             initialPosition[i][j] = s;
6008
6009         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6010         initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
6011         initialPosition[pawnRow][j] = WhitePawn;
6012         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6013         if(gameInfo.variant == VariantXiangqi) {
6014             if(j&1) {
6015                 initialPosition[pawnRow][j] =
6016                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6017                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6018                    initialPosition[2][j] = WhiteCannon;
6019                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6020                 }
6021             }
6022         }
6023         if(gameInfo.variant == VariantGrand) {
6024             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6025                initialPosition[0][j] = WhiteRook;
6026                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6027             }
6028         }
6029         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] =  pieces[1][j-gameInfo.holdingsWidth];
6030     }
6031     if( (gameInfo.variant == VariantShogi) && !overrule ) {
6032
6033             j=BOARD_LEFT+1;
6034             initialPosition[1][j] = WhiteBishop;
6035             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6036             j=BOARD_RGHT-2;
6037             initialPosition[1][j] = WhiteRook;
6038             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6039     }
6040
6041     if( nrCastlingRights == -1) {
6042         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6043         /*       This sets default castling rights from none to normal corners   */
6044         /* Variants with other castling rights must set them themselves above    */
6045         nrCastlingRights = 6;
6046
6047         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6048         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6049         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6050         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6051         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6052         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6053      }
6054
6055      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6056      if(gameInfo.variant == VariantGreat) { // promotion commoners
6057         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6058         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6059         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6060         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6061      }
6062      if( gameInfo.variant == VariantSChess ) {
6063       initialPosition[1][0] = BlackMarshall;
6064       initialPosition[2][0] = BlackAngel;
6065       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6066       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6067       initialPosition[1][1] = initialPosition[2][1] = 
6068       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6069      }
6070   if (appData.debugMode) {
6071     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6072   }
6073     if(shuffleOpenings) {
6074         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6075         startedFromSetupPosition = TRUE;
6076     }
6077     if(startedFromPositionFile) {
6078       /* [HGM] loadPos: use PositionFile for every new game */
6079       CopyBoard(initialPosition, filePosition);
6080       for(i=0; i<nrCastlingRights; i++)
6081           initialRights[i] = filePosition[CASTLING][i];
6082       startedFromSetupPosition = TRUE;
6083     }
6084
6085     CopyBoard(boards[0], initialPosition);
6086
6087     if(oldx != gameInfo.boardWidth ||
6088        oldy != gameInfo.boardHeight ||
6089        oldv != gameInfo.variant ||
6090        oldh != gameInfo.holdingsWidth
6091                                          )
6092             InitDrawingSizes(-2 ,0);
6093
6094     oldv = gameInfo.variant;
6095     if (redraw)
6096       DrawPosition(TRUE, boards[currentMove]);
6097 }
6098
6099 void
6100 SendBoard (ChessProgramState *cps, int moveNum)
6101 {
6102     char message[MSG_SIZ];
6103
6104     if (cps->useSetboard) {
6105       char* fen = PositionToFEN(moveNum, cps->fenOverride);
6106       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6107       SendToProgram(message, cps);
6108       free(fen);
6109
6110     } else {
6111       ChessSquare *bp;
6112       int i, j, left=0, right=BOARD_WIDTH;
6113       /* Kludge to set black to move, avoiding the troublesome and now
6114        * deprecated "black" command.
6115        */
6116       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6117         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6118
6119       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6120
6121       SendToProgram("edit\n", cps);
6122       SendToProgram("#\n", cps);
6123       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6124         bp = &boards[moveNum][i][left];
6125         for (j = left; j < right; j++, bp++) {
6126           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6127           if ((int) *bp < (int) BlackPawn) {
6128             if(j == BOARD_RGHT+1)
6129                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6130             else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6131             if(message[0] == '+' || message[0] == '~') {
6132               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6133                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6134                         AAA + j, ONE + i);
6135             }
6136             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6137                 message[1] = BOARD_RGHT   - 1 - j + '1';
6138                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6139             }
6140             SendToProgram(message, cps);
6141           }
6142         }
6143       }
6144
6145       SendToProgram("c\n", cps);
6146       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6147         bp = &boards[moveNum][i][left];
6148         for (j = left; j < right; j++, bp++) {
6149           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6150           if (((int) *bp != (int) EmptySquare)
6151               && ((int) *bp >= (int) BlackPawn)) {
6152             if(j == BOARD_LEFT-2)
6153                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6154             else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6155                     AAA + j, ONE + i);
6156             if(message[0] == '+' || message[0] == '~') {
6157               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6158                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6159                         AAA + j, ONE + i);
6160             }
6161             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6162                 message[1] = BOARD_RGHT   - 1 - j + '1';
6163                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6164             }
6165             SendToProgram(message, cps);
6166           }
6167         }
6168       }
6169
6170       SendToProgram(".\n", cps);
6171     }
6172     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6173 }
6174
6175 char exclusionHeader[MSG_SIZ];
6176 int exCnt, excludePtr;
6177 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6178 static Exclusion excluTab[200];
6179 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6180
6181 static void
6182 WriteMap (int s)
6183 {
6184     int j;
6185     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6186     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6187 }
6188
6189 static void
6190 ClearMap ()
6191 {
6192     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6193     excludePtr = 24; exCnt = 0;
6194     WriteMap(0);
6195 }
6196
6197 static void
6198 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6199 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6200     char buf[2*MOVE_LEN], *p;
6201     Exclusion *e = excluTab;
6202     int i;
6203     for(i=0; i<exCnt; i++)
6204         if(e[i].ff == fromX && e[i].fr == fromY &&
6205            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6206     if(i == exCnt) { // was not in exclude list; add it
6207         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6208         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6209             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6210             return; // abort
6211         }
6212         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6213         excludePtr++; e[i].mark = excludePtr++;
6214         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6215         exCnt++;
6216     }
6217     exclusionHeader[e[i].mark] = state;
6218 }
6219
6220 static int
6221 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6222 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6223     char buf[MSG_SIZ];
6224     int j, k;
6225     ChessMove moveType;
6226     if((signed char)promoChar == -1) { // kludge to indicate best move
6227         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6228             return 1; // if unparsable, abort
6229     }
6230     // update exclusion map (resolving toggle by consulting existing state)
6231     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6232     j = k%8; k >>= 3;
6233     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6234     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6235          excludeMap[k] |=   1<<j;
6236     else excludeMap[k] &= ~(1<<j);
6237     // update header
6238     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6239     // inform engine
6240     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6241     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6242     SendToBoth(buf);
6243     return (state == '+');
6244 }
6245
6246 static void
6247 ExcludeClick (int index)
6248 {
6249     int i, j;
6250     Exclusion *e = excluTab;
6251     if(index < 25) { // none, best or tail clicked
6252         if(index < 13) { // none: include all
6253             WriteMap(0); // clear map
6254             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6255             SendToBoth("include all\n"); // and inform engine
6256         } else if(index > 18) { // tail
6257             if(exclusionHeader[19] == '-') { // tail was excluded
6258                 SendToBoth("include all\n");
6259                 WriteMap(0); // clear map completely
6260                 // now re-exclude selected moves
6261                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6262                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6263             } else { // tail was included or in mixed state
6264                 SendToBoth("exclude all\n");
6265                 WriteMap(0xFF); // fill map completely
6266                 // now re-include selected moves
6267                 j = 0; // count them
6268                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6269                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6270                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6271             }
6272         } else { // best
6273             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6274         }
6275     } else {
6276         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6277             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6278             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6279             break;
6280         }
6281     }
6282 }
6283
6284 ChessSquare
6285 DefaultPromoChoice (int white)
6286 {
6287     ChessSquare result;
6288     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
6289         result = WhiteFerz; // no choice
6290     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6291         result= WhiteKing; // in Suicide Q is the last thing we want
6292     else if(gameInfo.variant == VariantSpartan)
6293         result = white ? WhiteQueen : WhiteAngel;
6294     else result = WhiteQueen;
6295     if(!white) result = WHITE_TO_BLACK result;
6296     return result;
6297 }
6298
6299 static int autoQueen; // [HGM] oneclick
6300
6301 int
6302 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6303 {
6304     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6305     /* [HGM] add Shogi promotions */
6306     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6307     ChessSquare piece;
6308     ChessMove moveType;
6309     Boolean premove;
6310
6311     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6312     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6313
6314     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6315       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6316         return FALSE;
6317
6318     piece = boards[currentMove][fromY][fromX];
6319     if(gameInfo.variant == VariantShogi) {
6320         promotionZoneSize = BOARD_HEIGHT/3;
6321         highestPromotingPiece = (int)WhiteFerz;
6322     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6323         promotionZoneSize = 3;
6324     }
6325
6326     // Treat Lance as Pawn when it is not representing Amazon
6327     if(gameInfo.variant != VariantSuper) {
6328         if(piece == WhiteLance) piece = WhitePawn; else
6329         if(piece == BlackLance) piece = BlackPawn;
6330     }
6331
6332     // next weed out all moves that do not touch the promotion zone at all
6333     if((int)piece >= BlackPawn) {
6334         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6335              return FALSE;
6336         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6337     } else {
6338         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6339            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6340     }
6341
6342     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6343
6344     // weed out mandatory Shogi promotions
6345     if(gameInfo.variant == VariantShogi) {
6346         if(piece >= BlackPawn) {
6347             if(toY == 0 && piece == BlackPawn ||
6348                toY == 0 && piece == BlackQueen ||
6349                toY <= 1 && piece == BlackKnight) {
6350                 *promoChoice = '+';
6351                 return FALSE;
6352             }
6353         } else {
6354             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6355                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6356                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6357                 *promoChoice = '+';
6358                 return FALSE;
6359             }
6360         }
6361     }
6362
6363     // weed out obviously illegal Pawn moves
6364     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6365         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6366         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6367         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6368         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6369         // note we are not allowed to test for valid (non-)capture, due to premove
6370     }
6371
6372     // we either have a choice what to promote to, or (in Shogi) whether to promote
6373     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6374         *promoChoice = PieceToChar(BlackFerz);  // no choice
6375         return FALSE;
6376     }
6377     // no sense asking what we must promote to if it is going to explode...
6378     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6379         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6380         return FALSE;
6381     }
6382     // give caller the default choice even if we will not make it
6383     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6384     if(gameInfo.variant == VariantShogi) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6385     if(        sweepSelect && gameInfo.variant != VariantGreat
6386                            && gameInfo.variant != VariantGrand
6387                            && gameInfo.variant != VariantSuper) return FALSE;
6388     if(autoQueen) return FALSE; // predetermined
6389
6390     // suppress promotion popup on illegal moves that are not premoves
6391     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6392               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6393     if(appData.testLegality && !premove) {
6394         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6395                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6396         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6397             return FALSE;
6398     }
6399
6400     return TRUE;
6401 }
6402
6403 int
6404 InPalace (int row, int column)
6405 {   /* [HGM] for Xiangqi */
6406     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6407          column < (BOARD_WIDTH + 4)/2 &&
6408          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6409     return FALSE;
6410 }
6411
6412 int
6413 PieceForSquare (int x, int y)
6414 {
6415   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6416      return -1;
6417   else
6418      return boards[currentMove][y][x];
6419 }
6420
6421 int
6422 OKToStartUserMove (int x, int y)
6423 {
6424     ChessSquare from_piece;
6425     int white_piece;
6426
6427     if (matchMode) return FALSE;
6428     if (gameMode == EditPosition) return TRUE;
6429
6430     if (x >= 0 && y >= 0)
6431       from_piece = boards[currentMove][y][x];
6432     else
6433       from_piece = EmptySquare;
6434
6435     if (from_piece == EmptySquare) return FALSE;
6436
6437     white_piece = (int)from_piece >= (int)WhitePawn &&
6438       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6439
6440     switch (gameMode) {
6441       case AnalyzeFile:
6442       case TwoMachinesPlay:
6443       case EndOfGame:
6444         return FALSE;
6445
6446       case IcsObserving:
6447       case IcsIdle:
6448         return FALSE;
6449
6450       case MachinePlaysWhite:
6451       case IcsPlayingBlack:
6452         if (appData.zippyPlay) return FALSE;
6453         if (white_piece) {
6454             DisplayMoveError(_("You are playing Black"));
6455             return FALSE;
6456         }
6457         break;
6458
6459       case MachinePlaysBlack:
6460       case IcsPlayingWhite:
6461         if (appData.zippyPlay) return FALSE;
6462         if (!white_piece) {
6463             DisplayMoveError(_("You are playing White"));
6464             return FALSE;
6465         }
6466         break;
6467
6468       case PlayFromGameFile:
6469             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6470       case EditGame:
6471         if (!white_piece && WhiteOnMove(currentMove)) {
6472             DisplayMoveError(_("It is White's turn"));
6473             return FALSE;
6474         }
6475         if (white_piece && !WhiteOnMove(currentMove)) {
6476             DisplayMoveError(_("It is Black's turn"));
6477             return FALSE;
6478         }
6479         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6480             /* Editing correspondence game history */
6481             /* Could disallow this or prompt for confirmation */
6482             cmailOldMove = -1;
6483         }
6484         break;
6485
6486       case BeginningOfGame:
6487         if (appData.icsActive) return FALSE;
6488         if (!appData.noChessProgram) {
6489             if (!white_piece) {
6490                 DisplayMoveError(_("You are playing White"));
6491                 return FALSE;
6492             }
6493         }
6494         break;
6495
6496       case Training:
6497         if (!white_piece && WhiteOnMove(currentMove)) {
6498             DisplayMoveError(_("It is White's turn"));
6499             return FALSE;
6500         }
6501         if (white_piece && !WhiteOnMove(currentMove)) {
6502             DisplayMoveError(_("It is Black's turn"));
6503             return FALSE;
6504         }
6505         break;
6506
6507       default:
6508       case IcsExamining:
6509         break;
6510     }
6511     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6512         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6513         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6514         && gameMode != AnalyzeFile && gameMode != Training) {
6515         DisplayMoveError(_("Displayed position is not current"));
6516         return FALSE;
6517     }
6518     return TRUE;
6519 }
6520
6521 Boolean
6522 OnlyMove (int *x, int *y, Boolean captures) 
6523 {
6524     DisambiguateClosure cl;
6525     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6526     switch(gameMode) {
6527       case MachinePlaysBlack:
6528       case IcsPlayingWhite:
6529       case BeginningOfGame:
6530         if(!WhiteOnMove(currentMove)) return FALSE;
6531         break;
6532       case MachinePlaysWhite:
6533       case IcsPlayingBlack:
6534         if(WhiteOnMove(currentMove)) return FALSE;
6535         break;
6536       case EditGame:
6537         break;
6538       default:
6539         return FALSE;
6540     }
6541     cl.pieceIn = EmptySquare;
6542     cl.rfIn = *y;
6543     cl.ffIn = *x;
6544     cl.rtIn = -1;
6545     cl.ftIn = -1;
6546     cl.promoCharIn = NULLCHAR;
6547     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6548     if( cl.kind == NormalMove ||
6549         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6550         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6551         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6552       fromX = cl.ff;
6553       fromY = cl.rf;
6554       *x = cl.ft;
6555       *y = cl.rt;
6556       return TRUE;
6557     }
6558     if(cl.kind != ImpossibleMove) return FALSE;
6559     cl.pieceIn = EmptySquare;
6560     cl.rfIn = -1;
6561     cl.ffIn = -1;
6562     cl.rtIn = *y;
6563     cl.ftIn = *x;
6564     cl.promoCharIn = NULLCHAR;
6565     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6566     if( cl.kind == NormalMove ||
6567         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6568         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6569         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6570       fromX = cl.ff;
6571       fromY = cl.rf;
6572       *x = cl.ft;
6573       *y = cl.rt;
6574       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6575       return TRUE;
6576     }
6577     return FALSE;
6578 }
6579
6580 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6581 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6582 int lastLoadGameUseList = FALSE;
6583 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6584 ChessMove lastLoadGameStart = EndOfFile;
6585 int doubleClick;
6586
6587 void
6588 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6589 {
6590     ChessMove moveType;
6591     ChessSquare pup;
6592     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6593
6594     /* Check if the user is playing in turn.  This is complicated because we
6595        let the user "pick up" a piece before it is his turn.  So the piece he
6596        tried to pick up may have been captured by the time he puts it down!
6597        Therefore we use the color the user is supposed to be playing in this
6598        test, not the color of the piece that is currently on the starting
6599        square---except in EditGame mode, where the user is playing both
6600        sides; fortunately there the capture race can't happen.  (It can
6601        now happen in IcsExamining mode, but that's just too bad.  The user
6602        will get a somewhat confusing message in that case.)
6603        */
6604
6605     switch (gameMode) {
6606       case AnalyzeFile:
6607       case TwoMachinesPlay:
6608       case EndOfGame:
6609       case IcsObserving:
6610       case IcsIdle:
6611         /* We switched into a game mode where moves are not accepted,
6612            perhaps while the mouse button was down. */
6613         return;
6614
6615       case MachinePlaysWhite:
6616         /* User is moving for Black */
6617         if (WhiteOnMove(currentMove)) {
6618             DisplayMoveError(_("It is White's turn"));
6619             return;
6620         }
6621         break;
6622
6623       case MachinePlaysBlack:
6624         /* User is moving for White */
6625         if (!WhiteOnMove(currentMove)) {
6626             DisplayMoveError(_("It is Black's turn"));
6627             return;
6628         }
6629         break;
6630
6631       case PlayFromGameFile:
6632             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6633       case EditGame:
6634       case IcsExamining:
6635       case BeginningOfGame:
6636       case AnalyzeMode:
6637       case Training:
6638         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6639         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6640             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6641             /* User is moving for Black */
6642             if (WhiteOnMove(currentMove)) {
6643                 DisplayMoveError(_("It is White's turn"));
6644                 return;
6645             }
6646         } else {
6647             /* User is moving for White */
6648             if (!WhiteOnMove(currentMove)) {
6649                 DisplayMoveError(_("It is Black's turn"));
6650                 return;
6651             }
6652         }
6653         break;
6654
6655       case IcsPlayingBlack:
6656         /* User is moving for Black */
6657         if (WhiteOnMove(currentMove)) {
6658             if (!appData.premove) {
6659                 DisplayMoveError(_("It is White's turn"));
6660             } else if (toX >= 0 && toY >= 0) {
6661                 premoveToX = toX;
6662                 premoveToY = toY;
6663                 premoveFromX = fromX;
6664                 premoveFromY = fromY;
6665                 premovePromoChar = promoChar;
6666                 gotPremove = 1;
6667                 if (appData.debugMode)
6668                     fprintf(debugFP, "Got premove: fromX %d,"
6669                             "fromY %d, toX %d, toY %d\n",
6670                             fromX, fromY, toX, toY);
6671             }
6672             return;
6673         }
6674         break;
6675
6676       case IcsPlayingWhite:
6677         /* User is moving for White */
6678         if (!WhiteOnMove(currentMove)) {
6679             if (!appData.premove) {
6680                 DisplayMoveError(_("It is Black's turn"));
6681             } else if (toX >= 0 && toY >= 0) {
6682                 premoveToX = toX;
6683                 premoveToY = toY;
6684                 premoveFromX = fromX;
6685                 premoveFromY = fromY;
6686                 premovePromoChar = promoChar;
6687                 gotPremove = 1;
6688                 if (appData.debugMode)
6689                     fprintf(debugFP, "Got premove: fromX %d,"
6690                             "fromY %d, toX %d, toY %d\n",
6691                             fromX, fromY, toX, toY);
6692             }
6693             return;
6694         }
6695         break;
6696
6697       default:
6698         break;
6699
6700       case EditPosition:
6701         /* EditPosition, empty square, or different color piece;
6702            click-click move is possible */
6703         if (toX == -2 || toY == -2) {
6704             boards[0][fromY][fromX] = EmptySquare;
6705             DrawPosition(FALSE, boards[currentMove]);
6706             return;
6707         } else if (toX >= 0 && toY >= 0) {
6708             boards[0][toY][toX] = boards[0][fromY][fromX];
6709             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6710                 if(boards[0][fromY][0] != EmptySquare) {
6711                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6712                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6713                 }
6714             } else
6715             if(fromX == BOARD_RGHT+1) {
6716                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6717                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6718                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6719                 }
6720             } else
6721             boards[0][fromY][fromX] = gatingPiece;
6722             DrawPosition(FALSE, boards[currentMove]);
6723             return;
6724         }
6725         return;
6726     }
6727
6728     if(toX < 0 || toY < 0) return;
6729     pup = boards[currentMove][toY][toX];
6730
6731     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6732     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6733          if( pup != EmptySquare ) return;
6734          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6735            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6736                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6737            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6738            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6739            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6740            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6741          fromY = DROP_RANK;
6742     }
6743
6744     /* [HGM] always test for legality, to get promotion info */
6745     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6746                                          fromY, fromX, toY, toX, promoChar);
6747
6748     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6749
6750     /* [HGM] but possibly ignore an IllegalMove result */
6751     if (appData.testLegality) {
6752         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6753             DisplayMoveError(_("Illegal move"));
6754             return;
6755         }
6756     }
6757
6758     if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
6759         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
6760              ClearPremoveHighlights(); // was included
6761         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
6762         return;
6763     }
6764
6765     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6766 }
6767
6768 /* Common tail of UserMoveEvent and DropMenuEvent */
6769 int
6770 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
6771 {
6772     char *bookHit = 0;
6773
6774     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6775         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6776         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6777         if(WhiteOnMove(currentMove)) {
6778             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6779         } else {
6780             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6781         }
6782     }
6783
6784     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6785        move type in caller when we know the move is a legal promotion */
6786     if(moveType == NormalMove && promoChar)
6787         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6788
6789     /* [HGM] <popupFix> The following if has been moved here from
6790        UserMoveEvent(). Because it seemed to belong here (why not allow
6791        piece drops in training games?), and because it can only be
6792        performed after it is known to what we promote. */
6793     if (gameMode == Training) {
6794       /* compare the move played on the board to the next move in the
6795        * game. If they match, display the move and the opponent's response.
6796        * If they don't match, display an error message.
6797        */
6798       int saveAnimate;
6799       Board testBoard;
6800       CopyBoard(testBoard, boards[currentMove]);
6801       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6802
6803       if (CompareBoards(testBoard, boards[currentMove+1])) {
6804         ForwardInner(currentMove+1);
6805
6806         /* Autoplay the opponent's response.
6807          * if appData.animate was TRUE when Training mode was entered,
6808          * the response will be animated.
6809          */
6810         saveAnimate = appData.animate;
6811         appData.animate = animateTraining;
6812         ForwardInner(currentMove+1);
6813         appData.animate = saveAnimate;
6814
6815         /* check for the end of the game */
6816         if (currentMove >= forwardMostMove) {
6817           gameMode = PlayFromGameFile;
6818           ModeHighlight();
6819           SetTrainingModeOff();
6820           DisplayInformation(_("End of game"));
6821         }
6822       } else {
6823         DisplayError(_("Incorrect move"), 0);
6824       }
6825       return 1;
6826     }
6827
6828   /* Ok, now we know that the move is good, so we can kill
6829      the previous line in Analysis Mode */
6830   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6831                                 && currentMove < forwardMostMove) {
6832     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6833     else forwardMostMove = currentMove;
6834   }
6835
6836   ClearMap();
6837
6838   /* If we need the chess program but it's dead, restart it */
6839   ResurrectChessProgram();
6840
6841   /* A user move restarts a paused game*/
6842   if (pausing)
6843     PauseEvent();
6844
6845   thinkOutput[0] = NULLCHAR;
6846
6847   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6848
6849   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6850     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6851     return 1;
6852   }
6853
6854   if (gameMode == BeginningOfGame) {
6855     if (appData.noChessProgram) {
6856       gameMode = EditGame;
6857       SetGameInfo();
6858     } else {
6859       char buf[MSG_SIZ];
6860       gameMode = MachinePlaysBlack;
6861       StartClocks();
6862       SetGameInfo();
6863       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
6864       DisplayTitle(buf);
6865       if (first.sendName) {
6866         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6867         SendToProgram(buf, &first);
6868       }
6869       StartClocks();
6870     }
6871     ModeHighlight();
6872   }
6873
6874   /* Relay move to ICS or chess engine */
6875   if (appData.icsActive) {
6876     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6877         gameMode == IcsExamining) {
6878       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6879         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6880         SendToICS("draw ");
6881         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6882       }
6883       // also send plain move, in case ICS does not understand atomic claims
6884       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6885       ics_user_moved = 1;
6886     }
6887   } else {
6888     if (first.sendTime && (gameMode == BeginningOfGame ||
6889                            gameMode == MachinePlaysWhite ||
6890                            gameMode == MachinePlaysBlack)) {
6891       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6892     }
6893     if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
6894          // [HGM] book: if program might be playing, let it use book
6895         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6896         first.maybeThinking = TRUE;
6897     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
6898         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
6899         SendBoard(&first, currentMove+1);
6900         if(second.analyzing) {
6901             if(!second.useSetboard) SendToProgram("undo\n", &second);
6902             SendBoard(&second, currentMove+1);
6903         }
6904     } else {
6905         SendMoveToProgram(forwardMostMove-1, &first);
6906         if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
6907     }
6908     if (currentMove == cmailOldMove + 1) {
6909       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6910     }
6911   }
6912
6913   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6914
6915   switch (gameMode) {
6916   case EditGame:
6917     if(appData.testLegality)
6918     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6919     case MT_NONE:
6920     case MT_CHECK:
6921       break;
6922     case MT_CHECKMATE:
6923     case MT_STAINMATE:
6924       if (WhiteOnMove(currentMove)) {
6925         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6926       } else {
6927         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6928       }
6929       break;
6930     case MT_STALEMATE:
6931       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6932       break;
6933     }
6934     break;
6935
6936   case MachinePlaysBlack:
6937   case MachinePlaysWhite:
6938     /* disable certain menu options while machine is thinking */
6939     SetMachineThinkingEnables();
6940     break;
6941
6942   default:
6943     break;
6944   }
6945
6946   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6947   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6948
6949   if(bookHit) { // [HGM] book: simulate book reply
6950         static char bookMove[MSG_SIZ]; // a bit generous?
6951
6952         programStats.nodes = programStats.depth = programStats.time =
6953         programStats.score = programStats.got_only_move = 0;
6954         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6955
6956         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6957         strcat(bookMove, bookHit);
6958         HandleMachineMove(bookMove, &first);
6959   }
6960   return 1;
6961 }
6962
6963 void
6964 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
6965 {
6966     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6967     Markers *m = (Markers *) closure;
6968     if(rf == fromY && ff == fromX)
6969         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6970                          || kind == WhiteCapturesEnPassant
6971                          || kind == BlackCapturesEnPassant);
6972     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6973 }
6974
6975 void
6976 MarkTargetSquares (int clear)
6977 {
6978   int x, y;
6979   if(clear) // no reason to ever suppress clearing
6980     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6981   if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
6982      !appData.testLegality || gameMode == EditPosition) return;
6983   if(!clear) {
6984     int capt = 0;
6985     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
6986     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6987       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6988       if(capt)
6989       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6990     }
6991   }
6992   DrawPosition(FALSE, NULL);
6993 }
6994
6995 int
6996 Explode (Board board, int fromX, int fromY, int toX, int toY)
6997 {
6998     if(gameInfo.variant == VariantAtomic &&
6999        (board[toY][toX] != EmptySquare ||                     // capture?
7000         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
7001                          board[fromY][fromX] == BlackPawn   )
7002       )) {
7003         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7004         return TRUE;
7005     }
7006     return FALSE;
7007 }
7008
7009 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7010
7011 int
7012 CanPromote (ChessSquare piece, int y)
7013 {
7014         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7015         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7016         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
7017            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7018            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7019                                                   gameInfo.variant == VariantMakruk) return FALSE;
7020         return (piece == BlackPawn && y == 1 ||
7021                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
7022                 piece == BlackLance && y == 1 ||
7023                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
7024 }
7025
7026 void
7027 LeftClick (ClickType clickType, int xPix, int yPix)
7028 {
7029     int x, y;
7030     Boolean saveAnimate;
7031     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7032     char promoChoice = NULLCHAR;
7033     ChessSquare piece;
7034     static TimeMark lastClickTime, prevClickTime;
7035
7036     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7037
7038     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7039
7040     if (clickType == Press) ErrorPopDown();
7041
7042     x = EventToSquare(xPix, BOARD_WIDTH);
7043     y = EventToSquare(yPix, BOARD_HEIGHT);
7044     if (!flipView && y >= 0) {
7045         y = BOARD_HEIGHT - 1 - y;
7046     }
7047     if (flipView && x >= 0) {
7048         x = BOARD_WIDTH - 1 - x;
7049     }
7050
7051     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7052         defaultPromoChoice = promoSweep;
7053         promoSweep = EmptySquare;   // terminate sweep
7054         promoDefaultAltered = TRUE;
7055         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7056     }
7057
7058     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7059         if(clickType == Release) return; // ignore upclick of click-click destination
7060         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7061         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7062         if(gameInfo.holdingsWidth &&
7063                 (WhiteOnMove(currentMove)
7064                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7065                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7066             // click in right holdings, for determining promotion piece
7067             ChessSquare p = boards[currentMove][y][x];
7068             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7069             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7070             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7071                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7072                 fromX = fromY = -1;
7073                 return;
7074             }
7075         }
7076         DrawPosition(FALSE, boards[currentMove]);
7077         return;
7078     }
7079
7080     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7081     if(clickType == Press
7082             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7083               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7084               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7085         return;
7086
7087     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7088         // could be static click on premove from-square: abort premove
7089         gotPremove = 0;
7090         ClearPremoveHighlights();
7091     }
7092
7093     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7094         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7095
7096     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7097         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7098                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7099         defaultPromoChoice = DefaultPromoChoice(side);
7100     }
7101
7102     autoQueen = appData.alwaysPromoteToQueen;
7103
7104     if (fromX == -1) {
7105       int originalY = y;
7106       gatingPiece = EmptySquare;
7107       if (clickType != Press) {
7108         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7109             DragPieceEnd(xPix, yPix); dragging = 0;
7110             DrawPosition(FALSE, NULL);
7111         }
7112         return;
7113       }
7114       doubleClick = FALSE;
7115       if(gameMode == AnalyzeMode && pausing && first.excludeMoves) { // use pause state to exclude moves
7116         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7117       }
7118       fromX = x; fromY = y; toX = toY = -1;
7119       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7120          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7121          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7122             /* First square */
7123             if (OKToStartUserMove(fromX, fromY)) {
7124                 second = 0;
7125                 MarkTargetSquares(0);
7126                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7127                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7128                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7129                     promoSweep = defaultPromoChoice;
7130                     selectFlag = 0; lastX = xPix; lastY = yPix;
7131                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7132                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7133                 }
7134                 if (appData.highlightDragging) {
7135                     SetHighlights(fromX, fromY, -1, -1);
7136                 } else {
7137                     ClearHighlights();
7138                 }
7139             } else fromX = fromY = -1;
7140             return;
7141         }
7142     }
7143
7144     /* fromX != -1 */
7145     if (clickType == Press && gameMode != EditPosition) {
7146         ChessSquare fromP;
7147         ChessSquare toP;
7148         int frc;
7149
7150         // ignore off-board to clicks
7151         if(y < 0 || x < 0) return;
7152
7153         /* Check if clicking again on the same color piece */
7154         fromP = boards[currentMove][fromY][fromX];
7155         toP = boards[currentMove][y][x];
7156         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
7157         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
7158              WhitePawn <= toP && toP <= WhiteKing &&
7159              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7160              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7161             (BlackPawn <= fromP && fromP <= BlackKing &&
7162              BlackPawn <= toP && toP <= BlackKing &&
7163              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7164              !(fromP == BlackKing && toP == BlackRook && frc))) {
7165             /* Clicked again on same color piece -- changed his mind */
7166             second = (x == fromX && y == fromY);
7167             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7168                 second = FALSE; // first double-click rather than scond click
7169                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7170             }
7171             promoDefaultAltered = FALSE;
7172             MarkTargetSquares(1);
7173            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
7174             if (appData.highlightDragging) {
7175                 SetHighlights(x, y, -1, -1);
7176             } else {
7177                 ClearHighlights();
7178             }
7179             if (OKToStartUserMove(x, y)) {
7180                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7181                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7182                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7183                  gatingPiece = boards[currentMove][fromY][fromX];
7184                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7185                 fromX = x;
7186                 fromY = y; dragging = 1;
7187                 MarkTargetSquares(0);
7188                 DragPieceBegin(xPix, yPix, FALSE);
7189                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7190                     promoSweep = defaultPromoChoice;
7191                     selectFlag = 0; lastX = xPix; lastY = yPix;
7192                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7193                 }
7194             }
7195            }
7196            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7197            second = FALSE; 
7198         }
7199         // ignore clicks on holdings
7200         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7201     }
7202
7203     if (clickType == Release && x == fromX && y == fromY) {
7204         DragPieceEnd(xPix, yPix); dragging = 0;
7205         if(clearFlag) {
7206             // a deferred attempt to click-click move an empty square on top of a piece
7207             boards[currentMove][y][x] = EmptySquare;
7208             ClearHighlights();
7209             DrawPosition(FALSE, boards[currentMove]);
7210             fromX = fromY = -1; clearFlag = 0;
7211             return;
7212         }
7213         if (appData.animateDragging) {
7214             /* Undo animation damage if any */
7215             DrawPosition(FALSE, NULL);
7216         }
7217         if (second || sweepSelecting) {
7218             /* Second up/down in same square; just abort move */
7219             if(sweepSelecting) DrawPosition(FALSE, boards[currentMove]);
7220             second = sweepSelecting = 0;
7221             fromX = fromY = -1;
7222             gatingPiece = EmptySquare;
7223             ClearHighlights();
7224             gotPremove = 0;
7225             ClearPremoveHighlights();
7226         } else {
7227             /* First upclick in same square; start click-click mode */
7228             SetHighlights(x, y, -1, -1);
7229         }
7230         return;
7231     }
7232
7233     clearFlag = 0;
7234
7235     /* we now have a different from- and (possibly off-board) to-square */
7236     /* Completed move */
7237     if(!sweepSelecting) {
7238         toX = x;
7239         toY = y;
7240     } else sweepSelecting = 0; // this must be the up-click corresponding to the down-click that started the sweep
7241
7242     saveAnimate = appData.animate;
7243     if (clickType == Press) {
7244         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7245             // must be Edit Position mode with empty-square selected
7246             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7247             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7248             return;
7249         }
7250         if(HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7251           if(appData.sweepSelect) {
7252             ChessSquare piece = boards[currentMove][fromY][fromX];
7253             promoSweep = defaultPromoChoice;
7254             if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
7255             selectFlag = 0; lastX = xPix; lastY = yPix;
7256             Sweep(0); // Pawn that is going to promote: preview promotion piece
7257             sweepSelecting = 1;
7258             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7259             MarkTargetSquares(1);
7260           }
7261           return; // promo popup appears on up-click
7262         }
7263         /* Finish clickclick move */
7264         if (appData.animate || appData.highlightLastMove) {
7265             SetHighlights(fromX, fromY, toX, toY);
7266         } else {
7267             ClearHighlights();
7268         }
7269     } else {
7270 #if 0
7271 // [HGM] this must be done after the move is made, as with arrow it could lead to a board redraw with piece still on from square
7272         /* Finish drag move */
7273         if (appData.highlightLastMove) {
7274             SetHighlights(fromX, fromY, toX, toY);
7275         } else {
7276             ClearHighlights();
7277         }
7278 #endif
7279         DragPieceEnd(xPix, yPix); dragging = 0;
7280         /* Don't animate move and drag both */
7281         appData.animate = FALSE;
7282     }
7283
7284     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7285     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7286         ChessSquare piece = boards[currentMove][fromY][fromX];
7287         if(gameMode == EditPosition && piece != EmptySquare &&
7288            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7289             int n;
7290
7291             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7292                 n = PieceToNumber(piece - (int)BlackPawn);
7293                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7294                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7295                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7296             } else
7297             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7298                 n = PieceToNumber(piece);
7299                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7300                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7301                 boards[currentMove][n][BOARD_WIDTH-2]++;
7302             }
7303             boards[currentMove][fromY][fromX] = EmptySquare;
7304         }
7305         ClearHighlights();
7306         fromX = fromY = -1;
7307         MarkTargetSquares(1);
7308         DrawPosition(TRUE, boards[currentMove]);
7309         return;
7310     }
7311
7312     // off-board moves should not be highlighted
7313     if(x < 0 || y < 0) ClearHighlights();
7314
7315     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7316
7317     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7318         SetHighlights(fromX, fromY, toX, toY);
7319         MarkTargetSquares(1);
7320         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7321             // [HGM] super: promotion to captured piece selected from holdings
7322             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7323             promotionChoice = TRUE;
7324             // kludge follows to temporarily execute move on display, without promoting yet
7325             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7326             boards[currentMove][toY][toX] = p;
7327             DrawPosition(FALSE, boards[currentMove]);
7328             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7329             boards[currentMove][toY][toX] = q;
7330             DisplayMessage("Click in holdings to choose piece", "");
7331             return;
7332         }
7333         PromotionPopUp();
7334     } else {
7335         int oldMove = currentMove;
7336         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7337         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7338         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7339         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7340            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7341             DrawPosition(TRUE, boards[currentMove]);
7342         MarkTargetSquares(1);
7343         fromX = fromY = -1;
7344     }
7345     appData.animate = saveAnimate;
7346     if (appData.animate || appData.animateDragging) {
7347         /* Undo animation damage if needed */
7348         DrawPosition(FALSE, NULL);
7349     }
7350 }
7351
7352 int
7353 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7354 {   // front-end-free part taken out of PieceMenuPopup
7355     int whichMenu; int xSqr, ySqr;
7356
7357     if(seekGraphUp) { // [HGM] seekgraph
7358         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7359         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7360         return -2;
7361     }
7362
7363     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7364          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7365         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7366         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7367         if(action == Press)   {
7368             originalFlip = flipView;
7369             flipView = !flipView; // temporarily flip board to see game from partners perspective
7370             DrawPosition(TRUE, partnerBoard);
7371             DisplayMessage(partnerStatus, "");
7372             partnerUp = TRUE;
7373         } else if(action == Release) {
7374             flipView = originalFlip;
7375             DrawPosition(TRUE, boards[currentMove]);
7376             partnerUp = FALSE;
7377         }
7378         return -2;
7379     }
7380
7381     xSqr = EventToSquare(x, BOARD_WIDTH);
7382     ySqr = EventToSquare(y, BOARD_HEIGHT);
7383     if (action == Release) {
7384         if(pieceSweep != EmptySquare) {
7385             EditPositionMenuEvent(pieceSweep, toX, toY);
7386             pieceSweep = EmptySquare;
7387         } else UnLoadPV(); // [HGM] pv
7388     }
7389     if (action != Press) return -2; // return code to be ignored
7390     switch (gameMode) {
7391       case IcsExamining:
7392         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7393       case EditPosition:
7394         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7395         if (xSqr < 0 || ySqr < 0) return -1;
7396         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7397         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7398         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7399         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7400         NextPiece(0);
7401         return 2; // grab
7402       case IcsObserving:
7403         if(!appData.icsEngineAnalyze) return -1;
7404       case IcsPlayingWhite:
7405       case IcsPlayingBlack:
7406         if(!appData.zippyPlay) goto noZip;
7407       case AnalyzeMode:
7408       case AnalyzeFile:
7409       case MachinePlaysWhite:
7410       case MachinePlaysBlack:
7411       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7412         if (!appData.dropMenu) {
7413           LoadPV(x, y);
7414           return 2; // flag front-end to grab mouse events
7415         }
7416         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7417            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7418       case EditGame:
7419       noZip:
7420         if (xSqr < 0 || ySqr < 0) return -1;
7421         if (!appData.dropMenu || appData.testLegality &&
7422             gameInfo.variant != VariantBughouse &&
7423             gameInfo.variant != VariantCrazyhouse) return -1;
7424         whichMenu = 1; // drop menu
7425         break;
7426       default:
7427         return -1;
7428     }
7429
7430     if (((*fromX = xSqr) < 0) ||
7431         ((*fromY = ySqr) < 0)) {
7432         *fromX = *fromY = -1;
7433         return -1;
7434     }
7435     if (flipView)
7436       *fromX = BOARD_WIDTH - 1 - *fromX;
7437     else
7438       *fromY = BOARD_HEIGHT - 1 - *fromY;
7439
7440     return whichMenu;
7441 }
7442
7443 void
7444 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7445 {
7446 //    char * hint = lastHint;
7447     FrontEndProgramStats stats;
7448
7449     stats.which = cps == &first ? 0 : 1;
7450     stats.depth = cpstats->depth;
7451     stats.nodes = cpstats->nodes;
7452     stats.score = cpstats->score;
7453     stats.time = cpstats->time;
7454     stats.pv = cpstats->movelist;
7455     stats.hint = lastHint;
7456     stats.an_move_index = 0;
7457     stats.an_move_count = 0;
7458
7459     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7460         stats.hint = cpstats->move_name;
7461         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7462         stats.an_move_count = cpstats->nr_moves;
7463     }
7464
7465     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
7466
7467     SetProgramStats( &stats );
7468 }
7469
7470 void
7471 ClearEngineOutputPane (int which)
7472 {
7473     static FrontEndProgramStats dummyStats;
7474     dummyStats.which = which;
7475     dummyStats.pv = "#";
7476     SetProgramStats( &dummyStats );
7477 }
7478
7479 #define MAXPLAYERS 500
7480
7481 char *
7482 TourneyStandings (int display)
7483 {
7484     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7485     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7486     char result, *p, *names[MAXPLAYERS];
7487
7488     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7489         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7490     names[0] = p = strdup(appData.participants);
7491     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7492
7493     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7494
7495     while(result = appData.results[nr]) {
7496         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7497         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7498         wScore = bScore = 0;
7499         switch(result) {
7500           case '+': wScore = 2; break;
7501           case '-': bScore = 2; break;
7502           case '=': wScore = bScore = 1; break;
7503           case ' ':
7504           case '*': return strdup("busy"); // tourney not finished
7505         }
7506         score[w] += wScore;
7507         score[b] += bScore;
7508         games[w]++;
7509         games[b]++;
7510         nr++;
7511     }
7512     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7513     for(w=0; w<nPlayers; w++) {
7514         bScore = -1;
7515         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7516         ranking[w] = b; points[w] = bScore; score[b] = -2;
7517     }
7518     p = malloc(nPlayers*34+1);
7519     for(w=0; w<nPlayers && w<display; w++)
7520         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7521     free(names[0]);
7522     return p;
7523 }
7524
7525 void
7526 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7527 {       // count all piece types
7528         int p, f, r;
7529         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7530         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7531         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7532                 p = board[r][f];
7533                 pCnt[p]++;
7534                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7535                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7536                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7537                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7538                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7539                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7540         }
7541 }
7542
7543 int
7544 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7545 {
7546         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7547         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7548
7549         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7550         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7551         if(myPawns == 2 && nMine == 3) // KPP
7552             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7553         if(myPawns == 1 && nMine == 2) // KP
7554             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7555         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7556             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7557         if(myPawns) return FALSE;
7558         if(pCnt[WhiteRook+side])
7559             return pCnt[BlackRook-side] ||
7560                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7561                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7562                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7563         if(pCnt[WhiteCannon+side]) {
7564             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7565             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7566         }
7567         if(pCnt[WhiteKnight+side])
7568             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7569         return FALSE;
7570 }
7571
7572 int
7573 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7574 {
7575         VariantClass v = gameInfo.variant;
7576
7577         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7578         if(v == VariantShatranj) return TRUE; // always winnable through baring
7579         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7580         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7581
7582         if(v == VariantXiangqi) {
7583                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7584
7585                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7586                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7587                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7588                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7589                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7590                 if(stale) // we have at least one last-rank P plus perhaps C
7591                     return majors // KPKX
7592                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7593                 else // KCA*E*
7594                     return pCnt[WhiteFerz+side] // KCAK
7595                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7596                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7597                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7598
7599         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7600                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7601
7602                 if(nMine == 1) return FALSE; // bare King
7603                 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
7604                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7605                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7606                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7607                 if(pCnt[WhiteKnight+side])
7608                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7609                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7610                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7611                 if(nBishops)
7612                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7613                 if(pCnt[WhiteAlfil+side])
7614                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7615                 if(pCnt[WhiteWazir+side])
7616                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7617         }
7618
7619         return TRUE;
7620 }
7621
7622 int
7623 CompareWithRights (Board b1, Board b2)
7624 {
7625     int rights = 0;
7626     if(!CompareBoards(b1, b2)) return FALSE;
7627     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7628     /* compare castling rights */
7629     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7630            rights++; /* King lost rights, while rook still had them */
7631     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7632         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7633            rights++; /* but at least one rook lost them */
7634     }
7635     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7636            rights++;
7637     if( b1[CASTLING][5] != NoRights ) {
7638         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7639            rights++;
7640     }
7641     return rights == 0;
7642 }
7643
7644 int
7645 Adjudicate (ChessProgramState *cps)
7646 {       // [HGM] some adjudications useful with buggy engines
7647         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7648         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7649         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7650         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7651         int k, count = 0; static int bare = 1;
7652         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7653         Boolean canAdjudicate = !appData.icsActive;
7654
7655         // most tests only when we understand the game, i.e. legality-checking on
7656             if( appData.testLegality )
7657             {   /* [HGM] Some more adjudications for obstinate engines */
7658                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7659                 static int moveCount = 6;
7660                 ChessMove result;
7661                 char *reason = NULL;
7662
7663                 /* Count what is on board. */
7664                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7665
7666                 /* Some material-based adjudications that have to be made before stalemate test */
7667                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7668                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7669                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7670                      if(canAdjudicate && appData.checkMates) {
7671                          if(engineOpponent)
7672                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7673                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7674                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7675                          return 1;
7676                      }
7677                 }
7678
7679                 /* Bare King in Shatranj (loses) or Losers (wins) */
7680                 if( nrW == 1 || nrB == 1) {
7681                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7682                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7683                      if(canAdjudicate && appData.checkMates) {
7684                          if(engineOpponent)
7685                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7686                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7687                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7688                          return 1;
7689                      }
7690                   } else
7691                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7692                   {    /* bare King */
7693                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7694                         if(canAdjudicate && appData.checkMates) {
7695                             /* but only adjudicate if adjudication enabled */
7696                             if(engineOpponent)
7697                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7698                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7699                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7700                             return 1;
7701                         }
7702                   }
7703                 } else bare = 1;
7704
7705
7706             // don't wait for engine to announce game end if we can judge ourselves
7707             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7708               case MT_CHECK:
7709                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7710                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7711                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7712                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7713                             checkCnt++;
7714                         if(checkCnt >= 2) {
7715                             reason = "Xboard adjudication: 3rd check";
7716                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7717                             break;
7718                         }
7719                     }
7720                 }
7721               case MT_NONE:
7722               default:
7723                 break;
7724               case MT_STALEMATE:
7725               case MT_STAINMATE:
7726                 reason = "Xboard adjudication: Stalemate";
7727                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7728                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7729                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7730                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7731                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7732                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7733                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7734                                                                         EP_CHECKMATE : EP_WINS);
7735                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7736                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7737                 }
7738                 break;
7739               case MT_CHECKMATE:
7740                 reason = "Xboard adjudication: Checkmate";
7741                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7742                 break;
7743             }
7744
7745                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7746                     case EP_STALEMATE:
7747                         result = GameIsDrawn; break;
7748                     case EP_CHECKMATE:
7749                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7750                     case EP_WINS:
7751                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7752                     default:
7753                         result = EndOfFile;
7754                 }
7755                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7756                     if(engineOpponent)
7757                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7758                     GameEnds( result, reason, GE_XBOARD );
7759                     return 1;
7760                 }
7761
7762                 /* Next absolutely insufficient mating material. */
7763                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7764                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7765                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7766
7767                      /* always flag draws, for judging claims */
7768                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7769
7770                      if(canAdjudicate && appData.materialDraws) {
7771                          /* but only adjudicate them if adjudication enabled */
7772                          if(engineOpponent) {
7773                            SendToProgram("force\n", engineOpponent); // suppress reply
7774                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7775                          }
7776                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7777                          return 1;
7778                      }
7779                 }
7780
7781                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7782                 if(gameInfo.variant == VariantXiangqi ?
7783                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7784                  : nrW + nrB == 4 &&
7785                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7786                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7787                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7788                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7789                    ) ) {
7790                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7791                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7792                           if(engineOpponent) {
7793                             SendToProgram("force\n", engineOpponent); // suppress reply
7794                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7795                           }
7796                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7797                           return 1;
7798                      }
7799                 } else moveCount = 6;
7800             }
7801
7802         // Repetition draws and 50-move rule can be applied independently of legality testing
7803
7804                 /* Check for rep-draws */
7805                 count = 0;
7806                 for(k = forwardMostMove-2;
7807                     k>=backwardMostMove && k>=forwardMostMove-100 &&
7808                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7809                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7810                     k-=2)
7811                 {   int rights=0;
7812                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7813                         /* compare castling rights */
7814                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7815                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7816                                 rights++; /* King lost rights, while rook still had them */
7817                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7818                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7819                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7820                                    rights++; /* but at least one rook lost them */
7821                         }
7822                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7823                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7824                                 rights++;
7825                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7826                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7827                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7828                                    rights++;
7829                         }
7830                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7831                             && appData.drawRepeats > 1) {
7832                              /* adjudicate after user-specified nr of repeats */
7833                              int result = GameIsDrawn;
7834                              char *details = "XBoard adjudication: repetition draw";
7835                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7836                                 // [HGM] xiangqi: check for forbidden perpetuals
7837                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7838                                 for(m=forwardMostMove; m>k; m-=2) {
7839                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7840                                         ourPerpetual = 0; // the current mover did not always check
7841                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7842                                         hisPerpetual = 0; // the opponent did not always check
7843                                 }
7844                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7845                                                                         ourPerpetual, hisPerpetual);
7846                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7847                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7848                                     details = "Xboard adjudication: perpetual checking";
7849                                 } else
7850                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7851                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7852                                 } else
7853                                 // Now check for perpetual chases
7854                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7855                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7856                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7857                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7858                                         static char resdet[MSG_SIZ];
7859                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7860                                         details = resdet;
7861                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
7862                                     } else
7863                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7864                                         break; // Abort repetition-checking loop.
7865                                 }
7866                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7867                              }
7868                              if(engineOpponent) {
7869                                SendToProgram("force\n", engineOpponent); // suppress reply
7870                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7871                              }
7872                              GameEnds( result, details, GE_XBOARD );
7873                              return 1;
7874                         }
7875                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7876                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7877                     }
7878                 }
7879
7880                 /* Now we test for 50-move draws. Determine ply count */
7881                 count = forwardMostMove;
7882                 /* look for last irreversble move */
7883                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7884                     count--;
7885                 /* if we hit starting position, add initial plies */
7886                 if( count == backwardMostMove )
7887                     count -= initialRulePlies;
7888                 count = forwardMostMove - count;
7889                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7890                         // adjust reversible move counter for checks in Xiangqi
7891                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7892                         if(i < backwardMostMove) i = backwardMostMove;
7893                         while(i <= forwardMostMove) {
7894                                 lastCheck = inCheck; // check evasion does not count
7895                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7896                                 if(inCheck || lastCheck) count--; // check does not count
7897                                 i++;
7898                         }
7899                 }
7900                 if( count >= 100)
7901                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7902                          /* this is used to judge if draw claims are legal */
7903                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7904                          if(engineOpponent) {
7905                            SendToProgram("force\n", engineOpponent); // suppress reply
7906                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7907                          }
7908                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7909                          return 1;
7910                 }
7911
7912                 /* if draw offer is pending, treat it as a draw claim
7913                  * when draw condition present, to allow engines a way to
7914                  * claim draws before making their move to avoid a race
7915                  * condition occurring after their move
7916                  */
7917                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7918                          char *p = NULL;
7919                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7920                              p = "Draw claim: 50-move rule";
7921                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7922                              p = "Draw claim: 3-fold repetition";
7923                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7924                              p = "Draw claim: insufficient mating material";
7925                          if( p != NULL && canAdjudicate) {
7926                              if(engineOpponent) {
7927                                SendToProgram("force\n", engineOpponent); // suppress reply
7928                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7929                              }
7930                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7931                              return 1;
7932                          }
7933                 }
7934
7935                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7936                     if(engineOpponent) {
7937                       SendToProgram("force\n", engineOpponent); // suppress reply
7938                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7939                     }
7940                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7941                     return 1;
7942                 }
7943         return 0;
7944 }
7945
7946 char *
7947 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
7948 {   // [HGM] book: this routine intercepts moves to simulate book replies
7949     char *bookHit = NULL;
7950
7951     //first determine if the incoming move brings opponent into his book
7952     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7953         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7954     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7955     if(bookHit != NULL && !cps->bookSuspend) {
7956         // make sure opponent is not going to reply after receiving move to book position
7957         SendToProgram("force\n", cps);
7958         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7959     }
7960     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7961     // now arrange restart after book miss
7962     if(bookHit) {
7963         // after a book hit we never send 'go', and the code after the call to this routine
7964         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7965         char buf[MSG_SIZ], *move = bookHit;
7966         if(cps->useSAN) {
7967             int fromX, fromY, toX, toY;
7968             char promoChar;
7969             ChessMove moveType;
7970             move = buf + 30;
7971             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
7972                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7973                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7974                                     PosFlags(forwardMostMove),
7975                                     fromY, fromX, toY, toX, promoChar, move);
7976             } else {
7977                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
7978                 bookHit = NULL;
7979             }
7980         }
7981         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
7982         SendToProgram(buf, cps);
7983         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7984     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7985         SendToProgram("go\n", cps);
7986         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7987     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7988         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7989             SendToProgram("go\n", cps);
7990         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7991     }
7992     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7993 }
7994
7995 int
7996 LoadError (char *errmess, ChessProgramState *cps)
7997 {   // unloads engine and switches back to -ncp mode if it was first
7998     if(cps->initDone) return FALSE;
7999     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8000     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8001     cps->pr = NoProc; 
8002     if(cps == &first) {
8003         appData.noChessProgram = TRUE;
8004         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8005         gameMode = BeginningOfGame; ModeHighlight();
8006         SetNCPMode();
8007     }
8008     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8009     DisplayMessage("", ""); // erase waiting message
8010     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8011     return TRUE;
8012 }
8013
8014 char *savedMessage;
8015 ChessProgramState *savedState;
8016 void
8017 DeferredBookMove (void)
8018 {
8019         if(savedState->lastPing != savedState->lastPong)
8020                     ScheduleDelayedEvent(DeferredBookMove, 10);
8021         else
8022         HandleMachineMove(savedMessage, savedState);
8023 }
8024
8025 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8026
8027 void
8028 HandleMachineMove (char *message, ChessProgramState *cps)
8029 {
8030     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8031     char realname[MSG_SIZ];
8032     int fromX, fromY, toX, toY;
8033     ChessMove moveType;
8034     char promoChar;
8035     char *p, *pv=buf1;
8036     int machineWhite, oldError;
8037     char *bookHit;
8038
8039     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8040         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8041         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8042             DisplayError(_("Invalid pairing from pairing engine"), 0);
8043             return;
8044         }
8045         pairingReceived = 1;
8046         NextMatchGame();
8047         return; // Skim the pairing messages here.
8048     }
8049
8050     oldError = cps->userError; cps->userError = 0;
8051
8052 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8053     /*
8054      * Kludge to ignore BEL characters
8055      */
8056     while (*message == '\007') message++;
8057
8058     /*
8059      * [HGM] engine debug message: ignore lines starting with '#' character
8060      */
8061     if(cps->debug && *message == '#') return;
8062
8063     /*
8064      * Look for book output
8065      */
8066     if (cps == &first && bookRequested) {
8067         if (message[0] == '\t' || message[0] == ' ') {
8068             /* Part of the book output is here; append it */
8069             strcat(bookOutput, message);
8070             strcat(bookOutput, "  \n");
8071             return;
8072         } else if (bookOutput[0] != NULLCHAR) {
8073             /* All of book output has arrived; display it */
8074             char *p = bookOutput;
8075             while (*p != NULLCHAR) {
8076                 if (*p == '\t') *p = ' ';
8077                 p++;
8078             }
8079             DisplayInformation(bookOutput);
8080             bookRequested = FALSE;
8081             /* Fall through to parse the current output */
8082         }
8083     }
8084
8085     /*
8086      * Look for machine move.
8087      */
8088     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8089         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8090     {
8091         /* This method is only useful on engines that support ping */
8092         if (cps->lastPing != cps->lastPong) {
8093           if (gameMode == BeginningOfGame) {
8094             /* Extra move from before last new; ignore */
8095             if (appData.debugMode) {
8096                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8097             }
8098           } else {
8099             if (appData.debugMode) {
8100                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8101                         cps->which, gameMode);
8102             }
8103
8104             SendToProgram("undo\n", cps);
8105           }
8106           return;
8107         }
8108
8109         switch (gameMode) {
8110           case BeginningOfGame:
8111             /* Extra move from before last reset; ignore */
8112             if (appData.debugMode) {
8113                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8114             }
8115             return;
8116
8117           case EndOfGame:
8118           case IcsIdle:
8119           default:
8120             /* Extra move after we tried to stop.  The mode test is
8121                not a reliable way of detecting this problem, but it's
8122                the best we can do on engines that don't support ping.
8123             */
8124             if (appData.debugMode) {
8125                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8126                         cps->which, gameMode);
8127             }
8128             SendToProgram("undo\n", cps);
8129             return;
8130
8131           case MachinePlaysWhite:
8132           case IcsPlayingWhite:
8133             machineWhite = TRUE;
8134             break;
8135
8136           case MachinePlaysBlack:
8137           case IcsPlayingBlack:
8138             machineWhite = FALSE;
8139             break;
8140
8141           case TwoMachinesPlay:
8142             machineWhite = (cps->twoMachinesColor[0] == 'w');
8143             break;
8144         }
8145         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8146             if (appData.debugMode) {
8147                 fprintf(debugFP,
8148                         "Ignoring move out of turn by %s, gameMode %d"
8149                         ", forwardMost %d\n",
8150                         cps->which, gameMode, forwardMostMove);
8151             }
8152             return;
8153         }
8154
8155         if(cps->alphaRank) AlphaRank(machineMove, 4);
8156         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8157                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8158             /* Machine move could not be parsed; ignore it. */
8159           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8160                     machineMove, _(cps->which));
8161             DisplayError(buf1, 0);
8162             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
8163                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
8164             if (gameMode == TwoMachinesPlay) {
8165               GameEnds(machineWhite ? BlackWins : WhiteWins,
8166                        buf1, GE_XBOARD);
8167             }
8168             return;
8169         }
8170
8171         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8172         /* So we have to redo legality test with true e.p. status here,  */
8173         /* to make sure an illegal e.p. capture does not slip through,   */
8174         /* to cause a forfeit on a justified illegal-move complaint      */
8175         /* of the opponent.                                              */
8176         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8177            ChessMove moveType;
8178            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8179                              fromY, fromX, toY, toX, promoChar);
8180             if(moveType == IllegalMove) {
8181               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8182                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8183                 GameEnds(machineWhite ? BlackWins : WhiteWins,
8184                            buf1, GE_XBOARD);
8185                 return;
8186            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
8187            /* [HGM] Kludge to handle engines that send FRC-style castling
8188               when they shouldn't (like TSCP-Gothic) */
8189            switch(moveType) {
8190              case WhiteASideCastleFR:
8191              case BlackASideCastleFR:
8192                toX+=2;
8193                currentMoveString[2]++;
8194                break;
8195              case WhiteHSideCastleFR:
8196              case BlackHSideCastleFR:
8197                toX--;
8198                currentMoveString[2]--;
8199                break;
8200              default: ; // nothing to do, but suppresses warning of pedantic compilers
8201            }
8202         }
8203         hintRequested = FALSE;
8204         lastHint[0] = NULLCHAR;
8205         bookRequested = FALSE;
8206         /* Program may be pondering now */
8207         cps->maybeThinking = TRUE;
8208         if (cps->sendTime == 2) cps->sendTime = 1;
8209         if (cps->offeredDraw) cps->offeredDraw--;
8210
8211         /* [AS] Save move info*/
8212         pvInfoList[ forwardMostMove ].score = programStats.score;
8213         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8214         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8215
8216         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8217
8218         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8219         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8220             int count = 0;
8221
8222             while( count < adjudicateLossPlies ) {
8223                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8224
8225                 if( count & 1 ) {
8226                     score = -score; /* Flip score for winning side */
8227                 }
8228
8229                 if( score > adjudicateLossThreshold ) {
8230                     break;
8231                 }
8232
8233                 count++;
8234             }
8235
8236             if( count >= adjudicateLossPlies ) {
8237                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8238
8239                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8240                     "Xboard adjudication",
8241                     GE_XBOARD );
8242
8243                 return;
8244             }
8245         }
8246
8247         if(Adjudicate(cps)) {
8248             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8249             return; // [HGM] adjudicate: for all automatic game ends
8250         }
8251
8252 #if ZIPPY
8253         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8254             first.initDone) {
8255           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8256                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8257                 SendToICS("draw ");
8258                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8259           }
8260           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8261           ics_user_moved = 1;
8262           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8263                 char buf[3*MSG_SIZ];
8264
8265                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8266                         programStats.score / 100.,
8267                         programStats.depth,
8268                         programStats.time / 100.,
8269                         (unsigned int)programStats.nodes,
8270                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8271                         programStats.movelist);
8272                 SendToICS(buf);
8273 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8274           }
8275         }
8276 #endif
8277
8278         /* [AS] Clear stats for next move */
8279         ClearProgramStats();
8280         thinkOutput[0] = NULLCHAR;
8281         hiddenThinkOutputState = 0;
8282
8283         bookHit = NULL;
8284         if (gameMode == TwoMachinesPlay) {
8285             /* [HGM] relaying draw offers moved to after reception of move */
8286             /* and interpreting offer as claim if it brings draw condition */
8287             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8288                 SendToProgram("draw\n", cps->other);
8289             }
8290             if (cps->other->sendTime) {
8291                 SendTimeRemaining(cps->other,
8292                                   cps->other->twoMachinesColor[0] == 'w');
8293             }
8294             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8295             if (firstMove && !bookHit) {
8296                 firstMove = FALSE;
8297                 if (cps->other->useColors) {
8298                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8299                 }
8300                 SendToProgram("go\n", cps->other);
8301             }
8302             cps->other->maybeThinking = TRUE;
8303         }
8304
8305         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8306
8307         if (!pausing && appData.ringBellAfterMoves) {
8308             RingBell();
8309         }
8310
8311         /*
8312          * Reenable menu items that were disabled while
8313          * machine was thinking
8314          */
8315         if (gameMode != TwoMachinesPlay)
8316             SetUserThinkingEnables();
8317
8318         // [HGM] book: after book hit opponent has received move and is now in force mode
8319         // force the book reply into it, and then fake that it outputted this move by jumping
8320         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8321         if(bookHit) {
8322                 static char bookMove[MSG_SIZ]; // a bit generous?
8323
8324                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8325                 strcat(bookMove, bookHit);
8326                 message = bookMove;
8327                 cps = cps->other;
8328                 programStats.nodes = programStats.depth = programStats.time =
8329                 programStats.score = programStats.got_only_move = 0;
8330                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8331
8332                 if(cps->lastPing != cps->lastPong) {
8333                     savedMessage = message; // args for deferred call
8334                     savedState = cps;
8335                     ScheduleDelayedEvent(DeferredBookMove, 10);
8336                     return;
8337                 }
8338                 goto FakeBookMove;
8339         }
8340
8341         return;
8342     }
8343
8344     /* Set special modes for chess engines.  Later something general
8345      *  could be added here; for now there is just one kludge feature,
8346      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8347      *  when "xboard" is given as an interactive command.
8348      */
8349     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8350         cps->useSigint = FALSE;
8351         cps->useSigterm = FALSE;
8352     }
8353     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8354       ParseFeatures(message+8, cps);
8355       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8356     }
8357
8358     if ((!appData.testLegality || gameInfo.variant == VariantFairy) && 
8359                                         !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
8360       int dummy, s=6; char buf[MSG_SIZ];
8361       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8362       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8363       if(startedFromSetupPosition) return;
8364       ParseFEN(boards[0], &dummy, message+s);
8365       DrawPosition(TRUE, boards[0]);
8366       startedFromSetupPosition = TRUE;
8367       return;
8368     }
8369     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8370      * want this, I was asked to put it in, and obliged.
8371      */
8372     if (!strncmp(message, "setboard ", 9)) {
8373         Board initial_position;
8374
8375         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8376
8377         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8378             DisplayError(_("Bad FEN received from engine"), 0);
8379             return ;
8380         } else {
8381            Reset(TRUE, FALSE);
8382            CopyBoard(boards[0], initial_position);
8383            initialRulePlies = FENrulePlies;
8384            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8385            else gameMode = MachinePlaysBlack;
8386            DrawPosition(FALSE, boards[currentMove]);
8387         }
8388         return;
8389     }
8390
8391     /*
8392      * Look for communication commands
8393      */
8394     if (!strncmp(message, "telluser ", 9)) {
8395         if(message[9] == '\\' && message[10] == '\\')
8396             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8397         PlayTellSound();
8398         DisplayNote(message + 9);
8399         return;
8400     }
8401     if (!strncmp(message, "tellusererror ", 14)) {
8402         cps->userError = 1;
8403         if(message[14] == '\\' && message[15] == '\\')
8404             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8405         PlayTellSound();
8406         DisplayError(message + 14, 0);
8407         return;
8408     }
8409     if (!strncmp(message, "tellopponent ", 13)) {
8410       if (appData.icsActive) {
8411         if (loggedOn) {
8412           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8413           SendToICS(buf1);
8414         }
8415       } else {
8416         DisplayNote(message + 13);
8417       }
8418       return;
8419     }
8420     if (!strncmp(message, "tellothers ", 11)) {
8421       if (appData.icsActive) {
8422         if (loggedOn) {
8423           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8424           SendToICS(buf1);
8425         }
8426       }
8427       return;
8428     }
8429     if (!strncmp(message, "tellall ", 8)) {
8430       if (appData.icsActive) {
8431         if (loggedOn) {
8432           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8433           SendToICS(buf1);
8434         }
8435       } else {
8436         DisplayNote(message + 8);
8437       }
8438       return;
8439     }
8440     if (strncmp(message, "warning", 7) == 0) {
8441         /* Undocumented feature, use tellusererror in new code */
8442         DisplayError(message, 0);
8443         return;
8444     }
8445     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8446         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8447         strcat(realname, " query");
8448         AskQuestion(realname, buf2, buf1, cps->pr);
8449         return;
8450     }
8451     /* Commands from the engine directly to ICS.  We don't allow these to be
8452      *  sent until we are logged on. Crafty kibitzes have been known to
8453      *  interfere with the login process.
8454      */
8455     if (loggedOn) {
8456         if (!strncmp(message, "tellics ", 8)) {
8457             SendToICS(message + 8);
8458             SendToICS("\n");
8459             return;
8460         }
8461         if (!strncmp(message, "tellicsnoalias ", 15)) {
8462             SendToICS(ics_prefix);
8463             SendToICS(message + 15);
8464             SendToICS("\n");
8465             return;
8466         }
8467         /* The following are for backward compatibility only */
8468         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8469             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8470             SendToICS(ics_prefix);
8471             SendToICS(message);
8472             SendToICS("\n");
8473             return;
8474         }
8475     }
8476     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8477         return;
8478     }
8479     /*
8480      * If the move is illegal, cancel it and redraw the board.
8481      * Also deal with other error cases.  Matching is rather loose
8482      * here to accommodate engines written before the spec.
8483      */
8484     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8485         strncmp(message, "Error", 5) == 0) {
8486         if (StrStr(message, "name") ||
8487             StrStr(message, "rating") || StrStr(message, "?") ||
8488             StrStr(message, "result") || StrStr(message, "board") ||
8489             StrStr(message, "bk") || StrStr(message, "computer") ||
8490             StrStr(message, "variant") || StrStr(message, "hint") ||
8491             StrStr(message, "random") || StrStr(message, "depth") ||
8492             StrStr(message, "accepted")) {
8493             return;
8494         }
8495         if (StrStr(message, "protover")) {
8496           /* Program is responding to input, so it's apparently done
8497              initializing, and this error message indicates it is
8498              protocol version 1.  So we don't need to wait any longer
8499              for it to initialize and send feature commands. */
8500           FeatureDone(cps, 1);
8501           cps->protocolVersion = 1;
8502           return;
8503         }
8504         cps->maybeThinking = FALSE;
8505
8506         if (StrStr(message, "draw")) {
8507             /* Program doesn't have "draw" command */
8508             cps->sendDrawOffers = 0;
8509             return;
8510         }
8511         if (cps->sendTime != 1 &&
8512             (StrStr(message, "time") || StrStr(message, "otim"))) {
8513           /* Program apparently doesn't have "time" or "otim" command */
8514           cps->sendTime = 0;
8515           return;
8516         }
8517         if (StrStr(message, "analyze")) {
8518             cps->analysisSupport = FALSE;
8519             cps->analyzing = FALSE;
8520 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8521             EditGameEvent(); // [HGM] try to preserve loaded game
8522             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8523             DisplayError(buf2, 0);
8524             return;
8525         }
8526         if (StrStr(message, "(no matching move)st")) {
8527           /* Special kludge for GNU Chess 4 only */
8528           cps->stKludge = TRUE;
8529           SendTimeControl(cps, movesPerSession, timeControl,
8530                           timeIncrement, appData.searchDepth,
8531                           searchTime);
8532           return;
8533         }
8534         if (StrStr(message, "(no matching move)sd")) {
8535           /* Special kludge for GNU Chess 4 only */
8536           cps->sdKludge = TRUE;
8537           SendTimeControl(cps, movesPerSession, timeControl,
8538                           timeIncrement, appData.searchDepth,
8539                           searchTime);
8540           return;
8541         }
8542         if (!StrStr(message, "llegal")) {
8543             return;
8544         }
8545         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8546             gameMode == IcsIdle) return;
8547         if (forwardMostMove <= backwardMostMove) return;
8548         if (pausing) PauseEvent();
8549       if(appData.forceIllegal) {
8550             // [HGM] illegal: machine refused move; force position after move into it
8551           SendToProgram("force\n", cps);
8552           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8553                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8554                 // when black is to move, while there might be nothing on a2 or black
8555                 // might already have the move. So send the board as if white has the move.
8556                 // But first we must change the stm of the engine, as it refused the last move
8557                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8558                 if(WhiteOnMove(forwardMostMove)) {
8559                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8560                     SendBoard(cps, forwardMostMove); // kludgeless board
8561                 } else {
8562                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8563                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8564                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8565                 }
8566           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8567             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8568                  gameMode == TwoMachinesPlay)
8569               SendToProgram("go\n", cps);
8570             return;
8571       } else
8572         if (gameMode == PlayFromGameFile) {
8573             /* Stop reading this game file */
8574             gameMode = EditGame;
8575             ModeHighlight();
8576         }
8577         /* [HGM] illegal-move claim should forfeit game when Xboard */
8578         /* only passes fully legal moves                            */
8579         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8580             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8581                                 "False illegal-move claim", GE_XBOARD );
8582             return; // do not take back move we tested as valid
8583         }
8584         currentMove = forwardMostMove-1;
8585         DisplayMove(currentMove-1); /* before DisplayMoveError */
8586         SwitchClocks(forwardMostMove-1); // [HGM] race
8587         DisplayBothClocks();
8588         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8589                 parseList[currentMove], _(cps->which));
8590         DisplayMoveError(buf1);
8591         DrawPosition(FALSE, boards[currentMove]);
8592
8593         SetUserThinkingEnables();
8594         return;
8595     }
8596     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8597         /* Program has a broken "time" command that
8598            outputs a string not ending in newline.
8599            Don't use it. */
8600         cps->sendTime = 0;
8601     }
8602
8603     /*
8604      * If chess program startup fails, exit with an error message.
8605      * Attempts to recover here are futile. [HGM] Well, we try anyway
8606      */
8607     if ((StrStr(message, "unknown host") != NULL)
8608         || (StrStr(message, "No remote directory") != NULL)
8609         || (StrStr(message, "not found") != NULL)
8610         || (StrStr(message, "No such file") != NULL)
8611         || (StrStr(message, "can't alloc") != NULL)
8612         || (StrStr(message, "Permission denied") != NULL)) {
8613
8614         cps->maybeThinking = FALSE;
8615         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8616                 _(cps->which), cps->program, cps->host, message);
8617         RemoveInputSource(cps->isr);
8618         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8619             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
8620             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
8621         }
8622         return;
8623     }
8624
8625     /*
8626      * Look for hint output
8627      */
8628     if (sscanf(message, "Hint: %s", buf1) == 1) {
8629         if (cps == &first && hintRequested) {
8630             hintRequested = FALSE;
8631             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8632                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8633                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8634                                     PosFlags(forwardMostMove),
8635                                     fromY, fromX, toY, toX, promoChar, buf1);
8636                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8637                 DisplayInformation(buf2);
8638             } else {
8639                 /* Hint move could not be parsed!? */
8640               snprintf(buf2, sizeof(buf2),
8641                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8642                         buf1, _(cps->which));
8643                 DisplayError(buf2, 0);
8644             }
8645         } else {
8646           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8647         }
8648         return;
8649     }
8650
8651     /*
8652      * Ignore other messages if game is not in progress
8653      */
8654     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8655         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8656
8657     /*
8658      * look for win, lose, draw, or draw offer
8659      */
8660     if (strncmp(message, "1-0", 3) == 0) {
8661         char *p, *q, *r = "";
8662         p = strchr(message, '{');
8663         if (p) {
8664             q = strchr(p, '}');
8665             if (q) {
8666                 *q = NULLCHAR;
8667                 r = p + 1;
8668             }
8669         }
8670         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8671         return;
8672     } else if (strncmp(message, "0-1", 3) == 0) {
8673         char *p, *q, *r = "";
8674         p = strchr(message, '{');
8675         if (p) {
8676             q = strchr(p, '}');
8677             if (q) {
8678                 *q = NULLCHAR;
8679                 r = p + 1;
8680             }
8681         }
8682         /* Kludge for Arasan 4.1 bug */
8683         if (strcmp(r, "Black resigns") == 0) {
8684             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8685             return;
8686         }
8687         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8688         return;
8689     } else if (strncmp(message, "1/2", 3) == 0) {
8690         char *p, *q, *r = "";
8691         p = strchr(message, '{');
8692         if (p) {
8693             q = strchr(p, '}');
8694             if (q) {
8695                 *q = NULLCHAR;
8696                 r = p + 1;
8697             }
8698         }
8699
8700         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8701         return;
8702
8703     } else if (strncmp(message, "White resign", 12) == 0) {
8704         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8705         return;
8706     } else if (strncmp(message, "Black resign", 12) == 0) {
8707         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8708         return;
8709     } else if (strncmp(message, "White matches", 13) == 0 ||
8710                strncmp(message, "Black matches", 13) == 0   ) {
8711         /* [HGM] ignore GNUShogi noises */
8712         return;
8713     } else if (strncmp(message, "White", 5) == 0 &&
8714                message[5] != '(' &&
8715                StrStr(message, "Black") == NULL) {
8716         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8717         return;
8718     } else if (strncmp(message, "Black", 5) == 0 &&
8719                message[5] != '(') {
8720         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8721         return;
8722     } else if (strcmp(message, "resign") == 0 ||
8723                strcmp(message, "computer resigns") == 0) {
8724         switch (gameMode) {
8725           case MachinePlaysBlack:
8726           case IcsPlayingBlack:
8727             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8728             break;
8729           case MachinePlaysWhite:
8730           case IcsPlayingWhite:
8731             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8732             break;
8733           case TwoMachinesPlay:
8734             if (cps->twoMachinesColor[0] == 'w')
8735               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8736             else
8737               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8738             break;
8739           default:
8740             /* can't happen */
8741             break;
8742         }
8743         return;
8744     } else if (strncmp(message, "opponent mates", 14) == 0) {
8745         switch (gameMode) {
8746           case MachinePlaysBlack:
8747           case IcsPlayingBlack:
8748             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8749             break;
8750           case MachinePlaysWhite:
8751           case IcsPlayingWhite:
8752             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8753             break;
8754           case TwoMachinesPlay:
8755             if (cps->twoMachinesColor[0] == 'w')
8756               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8757             else
8758               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8759             break;
8760           default:
8761             /* can't happen */
8762             break;
8763         }
8764         return;
8765     } else if (strncmp(message, "computer mates", 14) == 0) {
8766         switch (gameMode) {
8767           case MachinePlaysBlack:
8768           case IcsPlayingBlack:
8769             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8770             break;
8771           case MachinePlaysWhite:
8772           case IcsPlayingWhite:
8773             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8774             break;
8775           case TwoMachinesPlay:
8776             if (cps->twoMachinesColor[0] == 'w')
8777               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8778             else
8779               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8780             break;
8781           default:
8782             /* can't happen */
8783             break;
8784         }
8785         return;
8786     } else if (strncmp(message, "checkmate", 9) == 0) {
8787         if (WhiteOnMove(forwardMostMove)) {
8788             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8789         } else {
8790             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8791         }
8792         return;
8793     } else if (strstr(message, "Draw") != NULL ||
8794                strstr(message, "game is a draw") != NULL) {
8795         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8796         return;
8797     } else if (strstr(message, "offer") != NULL &&
8798                strstr(message, "draw") != NULL) {
8799 #if ZIPPY
8800         if (appData.zippyPlay && first.initDone) {
8801             /* Relay offer to ICS */
8802             SendToICS(ics_prefix);
8803             SendToICS("draw\n");
8804         }
8805 #endif
8806         cps->offeredDraw = 2; /* valid until this engine moves twice */
8807         if (gameMode == TwoMachinesPlay) {
8808             if (cps->other->offeredDraw) {
8809                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8810             /* [HGM] in two-machine mode we delay relaying draw offer      */
8811             /* until after we also have move, to see if it is really claim */
8812             }
8813         } else if (gameMode == MachinePlaysWhite ||
8814                    gameMode == MachinePlaysBlack) {
8815           if (userOfferedDraw) {
8816             DisplayInformation(_("Machine accepts your draw offer"));
8817             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8818           } else {
8819             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8820           }
8821         }
8822     }
8823
8824
8825     /*
8826      * Look for thinking output
8827      */
8828     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8829           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8830                                 ) {
8831         int plylev, mvleft, mvtot, curscore, time;
8832         char mvname[MOVE_LEN];
8833         u64 nodes; // [DM]
8834         char plyext;
8835         int ignore = FALSE;
8836         int prefixHint = FALSE;
8837         mvname[0] = NULLCHAR;
8838
8839         switch (gameMode) {
8840           case MachinePlaysBlack:
8841           case IcsPlayingBlack:
8842             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8843             break;
8844           case MachinePlaysWhite:
8845           case IcsPlayingWhite:
8846             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8847             break;
8848           case AnalyzeMode:
8849           case AnalyzeFile:
8850             break;
8851           case IcsObserving: /* [DM] icsEngineAnalyze */
8852             if (!appData.icsEngineAnalyze) ignore = TRUE;
8853             break;
8854           case TwoMachinesPlay:
8855             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8856                 ignore = TRUE;
8857             }
8858             break;
8859           default:
8860             ignore = TRUE;
8861             break;
8862         }
8863
8864         if (!ignore) {
8865             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8866             buf1[0] = NULLCHAR;
8867             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8868                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8869
8870                 if (plyext != ' ' && plyext != '\t') {
8871                     time *= 100;
8872                 }
8873
8874                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8875                 if( cps->scoreIsAbsolute &&
8876                     ( gameMode == MachinePlaysBlack ||
8877                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8878                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8879                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8880                      !WhiteOnMove(currentMove)
8881                     ) )
8882                 {
8883                     curscore = -curscore;
8884                 }
8885
8886                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
8887
8888                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
8889                         char buf[MSG_SIZ];
8890                         FILE *f;
8891                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
8892                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
8893                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
8894                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
8895                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
8896                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
8897                                 fclose(f);
8898                         } else DisplayError(_("failed writing PV"), 0);
8899                 }
8900
8901                 tempStats.depth = plylev;
8902                 tempStats.nodes = nodes;
8903                 tempStats.time = time;
8904                 tempStats.score = curscore;
8905                 tempStats.got_only_move = 0;
8906
8907                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8908                         int ticklen;
8909
8910                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8911                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8912                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8913                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8914                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8915                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8916                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8917                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8918                 }
8919
8920                 /* Buffer overflow protection */
8921                 if (pv[0] != NULLCHAR) {
8922                     if (strlen(pv) >= sizeof(tempStats.movelist)
8923                         && appData.debugMode) {
8924                         fprintf(debugFP,
8925                                 "PV is too long; using the first %u bytes.\n",
8926                                 (unsigned) sizeof(tempStats.movelist) - 1);
8927                     }
8928
8929                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8930                 } else {
8931                     sprintf(tempStats.movelist, " no PV\n");
8932                 }
8933
8934                 if (tempStats.seen_stat) {
8935                     tempStats.ok_to_send = 1;
8936                 }
8937
8938                 if (strchr(tempStats.movelist, '(') != NULL) {
8939                     tempStats.line_is_book = 1;
8940                     tempStats.nr_moves = 0;
8941                     tempStats.moves_left = 0;
8942                 } else {
8943                     tempStats.line_is_book = 0;
8944                 }
8945
8946                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8947                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8948
8949                 SendProgramStatsToFrontend( cps, &tempStats );
8950
8951                 /*
8952                     [AS] Protect the thinkOutput buffer from overflow... this
8953                     is only useful if buf1 hasn't overflowed first!
8954                 */
8955                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8956                          plylev,
8957                          (gameMode == TwoMachinesPlay ?
8958                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8959                          ((double) curscore) / 100.0,
8960                          prefixHint ? lastHint : "",
8961                          prefixHint ? " " : "" );
8962
8963                 if( buf1[0] != NULLCHAR ) {
8964                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8965
8966                     if( strlen(pv) > max_len ) {
8967                         if( appData.debugMode) {
8968                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8969                         }
8970                         pv[max_len+1] = '\0';
8971                     }
8972
8973                     strcat( thinkOutput, pv);
8974                 }
8975
8976                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8977                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8978                     DisplayMove(currentMove - 1);
8979                 }
8980                 return;
8981
8982             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8983                 /* crafty (9.25+) says "(only move) <move>"
8984                  * if there is only 1 legal move
8985                  */
8986                 sscanf(p, "(only move) %s", buf1);
8987                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8988                 sprintf(programStats.movelist, "%s (only move)", buf1);
8989                 programStats.depth = 1;
8990                 programStats.nr_moves = 1;
8991                 programStats.moves_left = 1;
8992                 programStats.nodes = 1;
8993                 programStats.time = 1;
8994                 programStats.got_only_move = 1;
8995
8996                 /* Not really, but we also use this member to
8997                    mean "line isn't going to change" (Crafty
8998                    isn't searching, so stats won't change) */
8999                 programStats.line_is_book = 1;
9000
9001                 SendProgramStatsToFrontend( cps, &programStats );
9002
9003                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9004                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9005                     DisplayMove(currentMove - 1);
9006                 }
9007                 return;
9008             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9009                               &time, &nodes, &plylev, &mvleft,
9010                               &mvtot, mvname) >= 5) {
9011                 /* The stat01: line is from Crafty (9.29+) in response
9012                    to the "." command */
9013                 programStats.seen_stat = 1;
9014                 cps->maybeThinking = TRUE;
9015
9016                 if (programStats.got_only_move || !appData.periodicUpdates)
9017                   return;
9018
9019                 programStats.depth = plylev;
9020                 programStats.time = time;
9021                 programStats.nodes = nodes;
9022                 programStats.moves_left = mvleft;
9023                 programStats.nr_moves = mvtot;
9024                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9025                 programStats.ok_to_send = 1;
9026                 programStats.movelist[0] = '\0';
9027
9028                 SendProgramStatsToFrontend( cps, &programStats );
9029
9030                 return;
9031
9032             } else if (strncmp(message,"++",2) == 0) {
9033                 /* Crafty 9.29+ outputs this */
9034                 programStats.got_fail = 2;
9035                 return;
9036
9037             } else if (strncmp(message,"--",2) == 0) {
9038                 /* Crafty 9.29+ outputs this */
9039                 programStats.got_fail = 1;
9040                 return;
9041
9042             } else if (thinkOutput[0] != NULLCHAR &&
9043                        strncmp(message, "    ", 4) == 0) {
9044                 unsigned message_len;
9045
9046                 p = message;
9047                 while (*p && *p == ' ') p++;
9048
9049                 message_len = strlen( p );
9050
9051                 /* [AS] Avoid buffer overflow */
9052                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9053                     strcat(thinkOutput, " ");
9054                     strcat(thinkOutput, p);
9055                 }
9056
9057                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9058                     strcat(programStats.movelist, " ");
9059                     strcat(programStats.movelist, p);
9060                 }
9061
9062                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9063                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9064                     DisplayMove(currentMove - 1);
9065                 }
9066                 return;
9067             }
9068         }
9069         else {
9070             buf1[0] = NULLCHAR;
9071
9072             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9073                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9074             {
9075                 ChessProgramStats cpstats;
9076
9077                 if (plyext != ' ' && plyext != '\t') {
9078                     time *= 100;
9079                 }
9080
9081                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9082                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9083                     curscore = -curscore;
9084                 }
9085
9086                 cpstats.depth = plylev;
9087                 cpstats.nodes = nodes;
9088                 cpstats.time = time;
9089                 cpstats.score = curscore;
9090                 cpstats.got_only_move = 0;
9091                 cpstats.movelist[0] = '\0';
9092
9093                 if (buf1[0] != NULLCHAR) {
9094                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9095                 }
9096
9097                 cpstats.ok_to_send = 0;
9098                 cpstats.line_is_book = 0;
9099                 cpstats.nr_moves = 0;
9100                 cpstats.moves_left = 0;
9101
9102                 SendProgramStatsToFrontend( cps, &cpstats );
9103             }
9104         }
9105     }
9106 }
9107
9108
9109 /* Parse a game score from the character string "game", and
9110    record it as the history of the current game.  The game
9111    score is NOT assumed to start from the standard position.
9112    The display is not updated in any way.
9113    */
9114 void
9115 ParseGameHistory (char *game)
9116 {
9117     ChessMove moveType;
9118     int fromX, fromY, toX, toY, boardIndex;
9119     char promoChar;
9120     char *p, *q;
9121     char buf[MSG_SIZ];
9122
9123     if (appData.debugMode)
9124       fprintf(debugFP, "Parsing game history: %s\n", game);
9125
9126     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9127     gameInfo.site = StrSave(appData.icsHost);
9128     gameInfo.date = PGNDate();
9129     gameInfo.round = StrSave("-");
9130
9131     /* Parse out names of players */
9132     while (*game == ' ') game++;
9133     p = buf;
9134     while (*game != ' ') *p++ = *game++;
9135     *p = NULLCHAR;
9136     gameInfo.white = StrSave(buf);
9137     while (*game == ' ') game++;
9138     p = buf;
9139     while (*game != ' ' && *game != '\n') *p++ = *game++;
9140     *p = NULLCHAR;
9141     gameInfo.black = StrSave(buf);
9142
9143     /* Parse moves */
9144     boardIndex = blackPlaysFirst ? 1 : 0;
9145     yynewstr(game);
9146     for (;;) {
9147         yyboardindex = boardIndex;
9148         moveType = (ChessMove) Myylex();
9149         switch (moveType) {
9150           case IllegalMove:             /* maybe suicide chess, etc. */
9151   if (appData.debugMode) {
9152     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9153     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9154     setbuf(debugFP, NULL);
9155   }
9156           case WhitePromotion:
9157           case BlackPromotion:
9158           case WhiteNonPromotion:
9159           case BlackNonPromotion:
9160           case NormalMove:
9161           case WhiteCapturesEnPassant:
9162           case BlackCapturesEnPassant:
9163           case WhiteKingSideCastle:
9164           case WhiteQueenSideCastle:
9165           case BlackKingSideCastle:
9166           case BlackQueenSideCastle:
9167           case WhiteKingSideCastleWild:
9168           case WhiteQueenSideCastleWild:
9169           case BlackKingSideCastleWild:
9170           case BlackQueenSideCastleWild:
9171           /* PUSH Fabien */
9172           case WhiteHSideCastleFR:
9173           case WhiteASideCastleFR:
9174           case BlackHSideCastleFR:
9175           case BlackASideCastleFR:
9176           /* POP Fabien */
9177             fromX = currentMoveString[0] - AAA;
9178             fromY = currentMoveString[1] - ONE;
9179             toX = currentMoveString[2] - AAA;
9180             toY = currentMoveString[3] - ONE;
9181             promoChar = currentMoveString[4];
9182             break;
9183           case WhiteDrop:
9184           case BlackDrop:
9185             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9186             fromX = moveType == WhiteDrop ?
9187               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9188             (int) CharToPiece(ToLower(currentMoveString[0]));
9189             fromY = DROP_RANK;
9190             toX = currentMoveString[2] - AAA;
9191             toY = currentMoveString[3] - ONE;
9192             promoChar = NULLCHAR;
9193             break;
9194           case AmbiguousMove:
9195             /* bug? */
9196             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9197   if (appData.debugMode) {
9198     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9199     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9200     setbuf(debugFP, NULL);
9201   }
9202             DisplayError(buf, 0);
9203             return;
9204           case ImpossibleMove:
9205             /* bug? */
9206             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9207   if (appData.debugMode) {
9208     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9209     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9210     setbuf(debugFP, NULL);
9211   }
9212             DisplayError(buf, 0);
9213             return;
9214           case EndOfFile:
9215             if (boardIndex < backwardMostMove) {
9216                 /* Oops, gap.  How did that happen? */
9217                 DisplayError(_("Gap in move list"), 0);
9218                 return;
9219             }
9220             backwardMostMove =  blackPlaysFirst ? 1 : 0;
9221             if (boardIndex > forwardMostMove) {
9222                 forwardMostMove = boardIndex;
9223             }
9224             return;
9225           case ElapsedTime:
9226             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9227                 strcat(parseList[boardIndex-1], " ");
9228                 strcat(parseList[boardIndex-1], yy_text);
9229             }
9230             continue;
9231           case Comment:
9232           case PGNTag:
9233           case NAG:
9234           default:
9235             /* ignore */
9236             continue;
9237           case WhiteWins:
9238           case BlackWins:
9239           case GameIsDrawn:
9240           case GameUnfinished:
9241             if (gameMode == IcsExamining) {
9242                 if (boardIndex < backwardMostMove) {
9243                     /* Oops, gap.  How did that happen? */
9244                     return;
9245                 }
9246                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9247                 return;
9248             }
9249             gameInfo.result = moveType;
9250             p = strchr(yy_text, '{');
9251             if (p == NULL) p = strchr(yy_text, '(');
9252             if (p == NULL) {
9253                 p = yy_text;
9254                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9255             } else {
9256                 q = strchr(p, *p == '{' ? '}' : ')');
9257                 if (q != NULL) *q = NULLCHAR;
9258                 p++;
9259             }
9260             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9261             gameInfo.resultDetails = StrSave(p);
9262             continue;
9263         }
9264         if (boardIndex >= forwardMostMove &&
9265             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9266             backwardMostMove = blackPlaysFirst ? 1 : 0;
9267             return;
9268         }
9269         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9270                                  fromY, fromX, toY, toX, promoChar,
9271                                  parseList[boardIndex]);
9272         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9273         /* currentMoveString is set as a side-effect of yylex */
9274         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9275         strcat(moveList[boardIndex], "\n");
9276         boardIndex++;
9277         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9278         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9279           case MT_NONE:
9280           case MT_STALEMATE:
9281           default:
9282             break;
9283           case MT_CHECK:
9284             if(gameInfo.variant != VariantShogi)
9285                 strcat(parseList[boardIndex - 1], "+");
9286             break;
9287           case MT_CHECKMATE:
9288           case MT_STAINMATE:
9289             strcat(parseList[boardIndex - 1], "#");
9290             break;
9291         }
9292     }
9293 }
9294
9295
9296 /* Apply a move to the given board  */
9297 void
9298 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9299 {
9300   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9301   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9302
9303     /* [HGM] compute & store e.p. status and castling rights for new position */
9304     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9305
9306       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9307       oldEP = (signed char)board[EP_STATUS];
9308       board[EP_STATUS] = EP_NONE;
9309
9310   if (fromY == DROP_RANK) {
9311         /* must be first */
9312         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9313             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9314             return;
9315         }
9316         piece = board[toY][toX] = (ChessSquare) fromX;
9317   } else {
9318       int i;
9319
9320       if( board[toY][toX] != EmptySquare )
9321            board[EP_STATUS] = EP_CAPTURE;
9322
9323       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9324            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9325                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9326       } else
9327       if( board[fromY][fromX] == WhitePawn ) {
9328            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9329                board[EP_STATUS] = EP_PAWN_MOVE;
9330            if( toY-fromY==2) {
9331                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9332                         gameInfo.variant != VariantBerolina || toX < fromX)
9333                       board[EP_STATUS] = toX | berolina;
9334                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9335                         gameInfo.variant != VariantBerolina || toX > fromX)
9336                       board[EP_STATUS] = toX;
9337            }
9338       } else
9339       if( board[fromY][fromX] == BlackPawn ) {
9340            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9341                board[EP_STATUS] = EP_PAWN_MOVE;
9342            if( toY-fromY== -2) {
9343                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9344                         gameInfo.variant != VariantBerolina || toX < fromX)
9345                       board[EP_STATUS] = toX | berolina;
9346                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9347                         gameInfo.variant != VariantBerolina || toX > fromX)
9348                       board[EP_STATUS] = toX;
9349            }
9350        }
9351
9352        for(i=0; i<nrCastlingRights; i++) {
9353            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9354               board[CASTLING][i] == toX   && castlingRank[i] == toY
9355              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9356        }
9357
9358        if(gameInfo.variant == VariantSChess) { // update virginity
9359            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
9360            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
9361            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
9362            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
9363        }
9364
9365      if (fromX == toX && fromY == toY) return;
9366
9367      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9368      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9369      if(gameInfo.variant == VariantKnightmate)
9370          king += (int) WhiteUnicorn - (int) WhiteKing;
9371
9372     /* Code added by Tord: */
9373     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9374     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9375         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9376       board[fromY][fromX] = EmptySquare;
9377       board[toY][toX] = EmptySquare;
9378       if((toX > fromX) != (piece == WhiteRook)) {
9379         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9380       } else {
9381         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9382       }
9383     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9384                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9385       board[fromY][fromX] = EmptySquare;
9386       board[toY][toX] = EmptySquare;
9387       if((toX > fromX) != (piece == BlackRook)) {
9388         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9389       } else {
9390         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9391       }
9392     /* End of code added by Tord */
9393
9394     } else if (board[fromY][fromX] == king
9395         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9396         && toY == fromY && toX > fromX+1) {
9397         board[fromY][fromX] = EmptySquare;
9398         board[toY][toX] = king;
9399         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9400         board[fromY][BOARD_RGHT-1] = EmptySquare;
9401     } else if (board[fromY][fromX] == king
9402         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9403                && toY == fromY && toX < fromX-1) {
9404         board[fromY][fromX] = EmptySquare;
9405         board[toY][toX] = king;
9406         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9407         board[fromY][BOARD_LEFT] = EmptySquare;
9408     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9409                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9410                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9411                ) {
9412         /* white pawn promotion */
9413         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9414         if(gameInfo.variant==VariantBughouse ||
9415            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9416             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9417         board[fromY][fromX] = EmptySquare;
9418     } else if ((fromY >= BOARD_HEIGHT>>1)
9419                && (toX != fromX)
9420                && gameInfo.variant != VariantXiangqi
9421                && gameInfo.variant != VariantBerolina
9422                && (board[fromY][fromX] == WhitePawn)
9423                && (board[toY][toX] == EmptySquare)) {
9424         board[fromY][fromX] = EmptySquare;
9425         board[toY][toX] = WhitePawn;
9426         captured = board[toY - 1][toX];
9427         board[toY - 1][toX] = EmptySquare;
9428     } else if ((fromY == BOARD_HEIGHT-4)
9429                && (toX == fromX)
9430                && gameInfo.variant == VariantBerolina
9431                && (board[fromY][fromX] == WhitePawn)
9432                && (board[toY][toX] == EmptySquare)) {
9433         board[fromY][fromX] = EmptySquare;
9434         board[toY][toX] = WhitePawn;
9435         if(oldEP & EP_BEROLIN_A) {
9436                 captured = board[fromY][fromX-1];
9437                 board[fromY][fromX-1] = EmptySquare;
9438         }else{  captured = board[fromY][fromX+1];
9439                 board[fromY][fromX+1] = EmptySquare;
9440         }
9441     } else if (board[fromY][fromX] == king
9442         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9443                && toY == fromY && toX > fromX+1) {
9444         board[fromY][fromX] = EmptySquare;
9445         board[toY][toX] = king;
9446         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9447         board[fromY][BOARD_RGHT-1] = EmptySquare;
9448     } else if (board[fromY][fromX] == king
9449         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9450                && toY == fromY && toX < fromX-1) {
9451         board[fromY][fromX] = EmptySquare;
9452         board[toY][toX] = king;
9453         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9454         board[fromY][BOARD_LEFT] = EmptySquare;
9455     } else if (fromY == 7 && fromX == 3
9456                && board[fromY][fromX] == BlackKing
9457                && toY == 7 && toX == 5) {
9458         board[fromY][fromX] = EmptySquare;
9459         board[toY][toX] = BlackKing;
9460         board[fromY][7] = EmptySquare;
9461         board[toY][4] = BlackRook;
9462     } else if (fromY == 7 && fromX == 3
9463                && board[fromY][fromX] == BlackKing
9464                && toY == 7 && toX == 1) {
9465         board[fromY][fromX] = EmptySquare;
9466         board[toY][toX] = BlackKing;
9467         board[fromY][0] = EmptySquare;
9468         board[toY][2] = BlackRook;
9469     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9470                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9471                && toY < promoRank && promoChar
9472                ) {
9473         /* black pawn promotion */
9474         board[toY][toX] = CharToPiece(ToLower(promoChar));
9475         if(gameInfo.variant==VariantBughouse ||
9476            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9477             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9478         board[fromY][fromX] = EmptySquare;
9479     } else if ((fromY < BOARD_HEIGHT>>1)
9480                && (toX != fromX)
9481                && gameInfo.variant != VariantXiangqi
9482                && gameInfo.variant != VariantBerolina
9483                && (board[fromY][fromX] == BlackPawn)
9484                && (board[toY][toX] == EmptySquare)) {
9485         board[fromY][fromX] = EmptySquare;
9486         board[toY][toX] = BlackPawn;
9487         captured = board[toY + 1][toX];
9488         board[toY + 1][toX] = EmptySquare;
9489     } else if ((fromY == 3)
9490                && (toX == fromX)
9491                && gameInfo.variant == VariantBerolina
9492                && (board[fromY][fromX] == BlackPawn)
9493                && (board[toY][toX] == EmptySquare)) {
9494         board[fromY][fromX] = EmptySquare;
9495         board[toY][toX] = BlackPawn;
9496         if(oldEP & EP_BEROLIN_A) {
9497                 captured = board[fromY][fromX-1];
9498                 board[fromY][fromX-1] = EmptySquare;
9499         }else{  captured = board[fromY][fromX+1];
9500                 board[fromY][fromX+1] = EmptySquare;
9501         }
9502     } else {
9503         board[toY][toX] = board[fromY][fromX];
9504         board[fromY][fromX] = EmptySquare;
9505     }
9506   }
9507
9508     if (gameInfo.holdingsWidth != 0) {
9509
9510       /* !!A lot more code needs to be written to support holdings  */
9511       /* [HGM] OK, so I have written it. Holdings are stored in the */
9512       /* penultimate board files, so they are automaticlly stored   */
9513       /* in the game history.                                       */
9514       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9515                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9516         /* Delete from holdings, by decreasing count */
9517         /* and erasing image if necessary            */
9518         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9519         if(p < (int) BlackPawn) { /* white drop */
9520              p -= (int)WhitePawn;
9521                  p = PieceToNumber((ChessSquare)p);
9522              if(p >= gameInfo.holdingsSize) p = 0;
9523              if(--board[p][BOARD_WIDTH-2] <= 0)
9524                   board[p][BOARD_WIDTH-1] = EmptySquare;
9525              if((int)board[p][BOARD_WIDTH-2] < 0)
9526                         board[p][BOARD_WIDTH-2] = 0;
9527         } else {                  /* black drop */
9528              p -= (int)BlackPawn;
9529                  p = PieceToNumber((ChessSquare)p);
9530              if(p >= gameInfo.holdingsSize) p = 0;
9531              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9532                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9533              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9534                         board[BOARD_HEIGHT-1-p][1] = 0;
9535         }
9536       }
9537       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9538           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9539         /* [HGM] holdings: Add to holdings, if holdings exist */
9540         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9541                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9542                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9543         }
9544         p = (int) captured;
9545         if (p >= (int) BlackPawn) {
9546           p -= (int)BlackPawn;
9547           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9548                   /* in Shogi restore piece to its original  first */
9549                   captured = (ChessSquare) (DEMOTED captured);
9550                   p = DEMOTED p;
9551           }
9552           p = PieceToNumber((ChessSquare)p);
9553           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9554           board[p][BOARD_WIDTH-2]++;
9555           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9556         } else {
9557           p -= (int)WhitePawn;
9558           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9559                   captured = (ChessSquare) (DEMOTED captured);
9560                   p = DEMOTED p;
9561           }
9562           p = PieceToNumber((ChessSquare)p);
9563           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9564           board[BOARD_HEIGHT-1-p][1]++;
9565           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9566         }
9567       }
9568     } else if (gameInfo.variant == VariantAtomic) {
9569       if (captured != EmptySquare) {
9570         int y, x;
9571         for (y = toY-1; y <= toY+1; y++) {
9572           for (x = toX-1; x <= toX+1; x++) {
9573             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9574                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9575               board[y][x] = EmptySquare;
9576             }
9577           }
9578         }
9579         board[toY][toX] = EmptySquare;
9580       }
9581     }
9582     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9583         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9584     } else
9585     if(promoChar == '+') {
9586         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
9587         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9588     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9589         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9590         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
9591            && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
9592         board[toY][toX] = newPiece;
9593     }
9594     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9595                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9596         // [HGM] superchess: take promotion piece out of holdings
9597         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9598         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9599             if(!--board[k][BOARD_WIDTH-2])
9600                 board[k][BOARD_WIDTH-1] = EmptySquare;
9601         } else {
9602             if(!--board[BOARD_HEIGHT-1-k][1])
9603                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9604         }
9605     }
9606
9607 }
9608
9609 /* Updates forwardMostMove */
9610 void
9611 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
9612 {
9613 //    forwardMostMove++; // [HGM] bare: moved downstream
9614
9615     (void) CoordsToAlgebraic(boards[forwardMostMove],
9616                              PosFlags(forwardMostMove),
9617                              fromY, fromX, toY, toX, promoChar,
9618                              parseList[forwardMostMove]);
9619
9620     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9621         int timeLeft; static int lastLoadFlag=0; int king, piece;
9622         piece = boards[forwardMostMove][fromY][fromX];
9623         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9624         if(gameInfo.variant == VariantKnightmate)
9625             king += (int) WhiteUnicorn - (int) WhiteKing;
9626         if(forwardMostMove == 0) {
9627             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9628                 fprintf(serverMoves, "%s;", UserName());
9629             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9630                 fprintf(serverMoves, "%s;", second.tidy);
9631             fprintf(serverMoves, "%s;", first.tidy);
9632             if(gameMode == MachinePlaysWhite)
9633                 fprintf(serverMoves, "%s;", UserName());
9634             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9635                 fprintf(serverMoves, "%s;", second.tidy);
9636         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9637         lastLoadFlag = loadFlag;
9638         // print base move
9639         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9640         // print castling suffix
9641         if( toY == fromY && piece == king ) {
9642             if(toX-fromX > 1)
9643                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9644             if(fromX-toX >1)
9645                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9646         }
9647         // e.p. suffix
9648         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9649              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9650              boards[forwardMostMove][toY][toX] == EmptySquare
9651              && fromX != toX && fromY != toY)
9652                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9653         // promotion suffix
9654         if(promoChar != NULLCHAR) {
9655             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
9656                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
9657                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
9658             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
9659         }
9660         if(!loadFlag) {
9661                 char buf[MOVE_LEN*2], *p; int len;
9662             fprintf(serverMoves, "/%d/%d",
9663                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9664             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9665             else                      timeLeft = blackTimeRemaining/1000;
9666             fprintf(serverMoves, "/%d", timeLeft);
9667                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
9668                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
9669                 if(p = strchr(buf, '=')) *p = NULLCHAR;
9670                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
9671             fprintf(serverMoves, "/%s", buf);
9672         }
9673         fflush(serverMoves);
9674     }
9675
9676     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
9677         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
9678       return;
9679     }
9680     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9681     if (commentList[forwardMostMove+1] != NULL) {
9682         free(commentList[forwardMostMove+1]);
9683         commentList[forwardMostMove+1] = NULL;
9684     }
9685     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9686     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9687     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9688     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9689     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9690     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9691     adjustedClock = FALSE;
9692     gameInfo.result = GameUnfinished;
9693     if (gameInfo.resultDetails != NULL) {
9694         free(gameInfo.resultDetails);
9695         gameInfo.resultDetails = NULL;
9696     }
9697     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9698                               moveList[forwardMostMove - 1]);
9699     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9700       case MT_NONE:
9701       case MT_STALEMATE:
9702       default:
9703         break;
9704       case MT_CHECK:
9705         if(gameInfo.variant != VariantShogi)
9706             strcat(parseList[forwardMostMove - 1], "+");
9707         break;
9708       case MT_CHECKMATE:
9709       case MT_STAINMATE:
9710         strcat(parseList[forwardMostMove - 1], "#");
9711         break;
9712     }
9713
9714 }
9715
9716 /* Updates currentMove if not pausing */
9717 void
9718 ShowMove (int fromX, int fromY, int toX, int toY)
9719 {
9720     int instant = (gameMode == PlayFromGameFile) ?
9721         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9722     if(appData.noGUI) return;
9723     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9724         if (!instant) {
9725             if (forwardMostMove == currentMove + 1) {
9726                 AnimateMove(boards[forwardMostMove - 1],
9727                             fromX, fromY, toX, toY);
9728             }
9729         }
9730         currentMove = forwardMostMove;
9731     }
9732
9733     if (instant) return;
9734
9735     DisplayMove(currentMove - 1);
9736     DrawPosition(FALSE, boards[currentMove]);
9737     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9738             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
9739                 SetHighlights(fromX, fromY, toX, toY);
9740             }
9741     }
9742     DisplayBothClocks();
9743     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9744 }
9745
9746 void
9747 SendEgtPath (ChessProgramState *cps)
9748 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9749         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9750
9751         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9752
9753         while(*p) {
9754             char c, *q = name+1, *r, *s;
9755
9756             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9757             while(*p && *p != ',') *q++ = *p++;
9758             *q++ = ':'; *q = 0;
9759             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9760                 strcmp(name, ",nalimov:") == 0 ) {
9761                 // take nalimov path from the menu-changeable option first, if it is defined
9762               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9763                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9764             } else
9765             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9766                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9767                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9768                 s = r = StrStr(s, ":") + 1; // beginning of path info
9769                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9770                 c = *r; *r = 0;             // temporarily null-terminate path info
9771                     *--q = 0;               // strip of trailig ':' from name
9772                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9773                 *r = c;
9774                 SendToProgram(buf,cps);     // send egtbpath command for this format
9775             }
9776             if(*p == ',') p++; // read away comma to position for next format name
9777         }
9778 }
9779
9780 void
9781 InitChessProgram (ChessProgramState *cps, int setup)
9782 /* setup needed to setup FRC opening position */
9783 {
9784     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9785     if (appData.noChessProgram) return;
9786     hintRequested = FALSE;
9787     bookRequested = FALSE;
9788
9789     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
9790     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9791     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9792     if(cps->memSize) { /* [HGM] memory */
9793       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9794         SendToProgram(buf, cps);
9795     }
9796     SendEgtPath(cps); /* [HGM] EGT */
9797     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9798       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9799         SendToProgram(buf, cps);
9800     }
9801
9802     SendToProgram(cps->initString, cps);
9803     if (gameInfo.variant != VariantNormal &&
9804         gameInfo.variant != VariantLoadable
9805         /* [HGM] also send variant if board size non-standard */
9806         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9807                                             ) {
9808       char *v = VariantName(gameInfo.variant);
9809       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9810         /* [HGM] in protocol 1 we have to assume all variants valid */
9811         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9812         DisplayFatalError(buf, 0, 1);
9813         return;
9814       }
9815
9816       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9817       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9818       if( gameInfo.variant == VariantXiangqi )
9819            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9820       if( gameInfo.variant == VariantShogi )
9821            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9822       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9823            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9824       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9825           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9826            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9827       if( gameInfo.variant == VariantCourier )
9828            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9829       if( gameInfo.variant == VariantSuper )
9830            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9831       if( gameInfo.variant == VariantGreat )
9832            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9833       if( gameInfo.variant == VariantSChess )
9834            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9835       if( gameInfo.variant == VariantGrand )
9836            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
9837
9838       if(overruled) {
9839         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9840                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9841            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9842            if(StrStr(cps->variants, b) == NULL) {
9843                // specific sized variant not known, check if general sizing allowed
9844                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9845                    if(StrStr(cps->variants, "boardsize") == NULL) {
9846                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9847                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9848                        DisplayFatalError(buf, 0, 1);
9849                        return;
9850                    }
9851                    /* [HGM] here we really should compare with the maximum supported board size */
9852                }
9853            }
9854       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9855       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9856       SendToProgram(buf, cps);
9857     }
9858     currentlyInitializedVariant = gameInfo.variant;
9859
9860     /* [HGM] send opening position in FRC to first engine */
9861     if(setup) {
9862           SendToProgram("force\n", cps);
9863           SendBoard(cps, 0);
9864           /* engine is now in force mode! Set flag to wake it up after first move. */
9865           setboardSpoiledMachineBlack = 1;
9866     }
9867
9868     if (cps->sendICS) {
9869       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9870       SendToProgram(buf, cps);
9871     }
9872     cps->maybeThinking = FALSE;
9873     cps->offeredDraw = 0;
9874     if (!appData.icsActive) {
9875         SendTimeControl(cps, movesPerSession, timeControl,
9876                         timeIncrement, appData.searchDepth,
9877                         searchTime);
9878     }
9879     if (appData.showThinking
9880         // [HGM] thinking: four options require thinking output to be sent
9881         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9882                                 ) {
9883         SendToProgram("post\n", cps);
9884     }
9885     SendToProgram("hard\n", cps);
9886     if (!appData.ponderNextMove) {
9887         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9888            it without being sure what state we are in first.  "hard"
9889            is not a toggle, so that one is OK.
9890          */
9891         SendToProgram("easy\n", cps);
9892     }
9893     if (cps->usePing) {
9894       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9895       SendToProgram(buf, cps);
9896     }
9897     cps->initDone = TRUE;
9898     ClearEngineOutputPane(cps == &second);
9899 }
9900
9901
9902 void
9903 StartChessProgram (ChessProgramState *cps)
9904 {
9905     char buf[MSG_SIZ];
9906     int err;
9907
9908     if (appData.noChessProgram) return;
9909     cps->initDone = FALSE;
9910
9911     if (strcmp(cps->host, "localhost") == 0) {
9912         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9913     } else if (*appData.remoteShell == NULLCHAR) {
9914         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9915     } else {
9916         if (*appData.remoteUser == NULLCHAR) {
9917           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9918                     cps->program);
9919         } else {
9920           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9921                     cps->host, appData.remoteUser, cps->program);
9922         }
9923         err = StartChildProcess(buf, "", &cps->pr);
9924     }
9925
9926     if (err != 0) {
9927       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9928         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
9929         if(cps != &first) return;
9930         appData.noChessProgram = TRUE;
9931         ThawUI();
9932         SetNCPMode();
9933 //      DisplayFatalError(buf, err, 1);
9934 //      cps->pr = NoProc;
9935 //      cps->isr = NULL;
9936         return;
9937     }
9938
9939     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9940     if (cps->protocolVersion > 1) {
9941       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9942       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9943       cps->comboCnt = 0;  //                and values of combo boxes
9944       SendToProgram(buf, cps);
9945     } else {
9946       SendToProgram("xboard\n", cps);
9947     }
9948 }
9949
9950 void
9951 TwoMachinesEventIfReady P((void))
9952 {
9953   static int curMess = 0;
9954   if (first.lastPing != first.lastPong) {
9955     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9956     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9957     return;
9958   }
9959   if (second.lastPing != second.lastPong) {
9960     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9961     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9962     return;
9963   }
9964   DisplayMessage("", ""); curMess = 0;
9965   ThawUI();
9966   TwoMachinesEvent();
9967 }
9968
9969 char *
9970 MakeName (char *template)
9971 {
9972     time_t clock;
9973     struct tm *tm;
9974     static char buf[MSG_SIZ];
9975     char *p = buf;
9976     int i;
9977
9978     clock = time((time_t *)NULL);
9979     tm = localtime(&clock);
9980
9981     while(*p++ = *template++) if(p[-1] == '%') {
9982         switch(*template++) {
9983           case 0:   *p = 0; return buf;
9984           case 'Y': i = tm->tm_year+1900; break;
9985           case 'y': i = tm->tm_year-100; break;
9986           case 'M': i = tm->tm_mon+1; break;
9987           case 'd': i = tm->tm_mday; break;
9988           case 'h': i = tm->tm_hour; break;
9989           case 'm': i = tm->tm_min; break;
9990           case 's': i = tm->tm_sec; break;
9991           default:  i = 0;
9992         }
9993         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
9994     }
9995     return buf;
9996 }
9997
9998 int
9999 CountPlayers (char *p)
10000 {
10001     int n = 0;
10002     while(p = strchr(p, '\n')) p++, n++; // count participants
10003     return n;
10004 }
10005
10006 FILE *
10007 WriteTourneyFile (char *results, FILE *f)
10008 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10009     if(f == NULL) f = fopen(appData.tourneyFile, "w");
10010     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10011         // create a file with tournament description
10012         fprintf(f, "-participants {%s}\n", appData.participants);
10013         fprintf(f, "-seedBase %d\n", appData.seedBase);
10014         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10015         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10016         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10017         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10018         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10019         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10020         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10021         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10022         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10023         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10024         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10025         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10026         if(searchTime > 0)
10027                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10028         else {
10029                 fprintf(f, "-mps %d\n", appData.movesPerSession);
10030                 fprintf(f, "-tc %s\n", appData.timeControl);
10031                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10032         }
10033         fprintf(f, "-results \"%s\"\n", results);
10034     }
10035     return f;
10036 }
10037
10038 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10039
10040 void
10041 Substitute (char *participants, int expunge)
10042 {
10043     int i, changed, changes=0, nPlayers=0;
10044     char *p, *q, *r, buf[MSG_SIZ];
10045     if(participants == NULL) return;
10046     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10047     r = p = participants; q = appData.participants;
10048     while(*p && *p == *q) {
10049         if(*p == '\n') r = p+1, nPlayers++;
10050         p++; q++;
10051     }
10052     if(*p) { // difference
10053         while(*p && *p++ != '\n');
10054         while(*q && *q++ != '\n');
10055       changed = nPlayers;
10056         changes = 1 + (strcmp(p, q) != 0);
10057     }
10058     if(changes == 1) { // a single engine mnemonic was changed
10059         q = r; while(*q) nPlayers += (*q++ == '\n');
10060         p = buf; while(*r && (*p = *r++) != '\n') p++;
10061         *p = NULLCHAR;
10062         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10063         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10064         if(mnemonic[i]) { // The substitute is valid
10065             FILE *f;
10066             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10067                 flock(fileno(f), LOCK_EX);
10068                 ParseArgsFromFile(f);
10069                 fseek(f, 0, SEEK_SET);
10070                 FREE(appData.participants); appData.participants = participants;
10071                 if(expunge) { // erase results of replaced engine
10072                     int len = strlen(appData.results), w, b, dummy;
10073                     for(i=0; i<len; i++) {
10074                         Pairing(i, nPlayers, &w, &b, &dummy);
10075                         if((w == changed || b == changed) && appData.results[i] == '*') {
10076                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10077                             fclose(f);
10078                             return;
10079                         }
10080                     }
10081                     for(i=0; i<len; i++) {
10082                         Pairing(i, nPlayers, &w, &b, &dummy);
10083                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10084                     }
10085                 }
10086                 WriteTourneyFile(appData.results, f);
10087                 fclose(f); // release lock
10088                 return;
10089             }
10090         } else DisplayError(_("No engine with the name you gave is installed"), 0);
10091     }
10092     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10093     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
10094     free(participants);
10095     return;
10096 }
10097
10098 int
10099 CheckPlayers (char *participants)
10100 {
10101         int i;
10102         char buf[MSG_SIZ], *p;
10103         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10104         while(p = strchr(participants, '\n')) {
10105             *p = NULLCHAR;
10106             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10107             if(!mnemonic[i]) {
10108                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10109                 *p = '\n';
10110                 DisplayError(buf, 0);
10111                 return 1;
10112             }
10113             *p = '\n';
10114             participants = p + 1;
10115         }
10116         return 0;
10117 }
10118
10119 int
10120 CreateTourney (char *name)
10121 {
10122         FILE *f;
10123         if(matchMode && strcmp(name, appData.tourneyFile)) {
10124              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10125         }
10126         if(name[0] == NULLCHAR) {
10127             if(appData.participants[0])
10128                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10129             return 0;
10130         }
10131         f = fopen(name, "r");
10132         if(f) { // file exists
10133             ASSIGN(appData.tourneyFile, name);
10134             ParseArgsFromFile(f); // parse it
10135         } else {
10136             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10137             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10138                 DisplayError(_("Not enough participants"), 0);
10139                 return 0;
10140             }
10141             if(CheckPlayers(appData.participants)) return 0;
10142             ASSIGN(appData.tourneyFile, name);
10143             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10144             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10145         }
10146         fclose(f);
10147         appData.noChessProgram = FALSE;
10148         appData.clockMode = TRUE;
10149         SetGNUMode();
10150         return 1;
10151 }
10152
10153 int
10154 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10155 {
10156     char buf[MSG_SIZ], *p, *q;
10157     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10158     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10159     skip = !all && group[0]; // if group requested, we start in skip mode
10160     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10161         p = names; q = buf; header = 0;
10162         while(*p && *p != '\n') *q++ = *p++;
10163         *q = 0;
10164         if(*p == '\n') p++;
10165         if(buf[0] == '#') {
10166             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10167             depth++; // we must be entering a new group
10168             if(all) continue; // suppress printing group headers when complete list requested
10169             header = 1;
10170             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10171         }
10172         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10173         if(engineList[i]) free(engineList[i]);
10174         engineList[i] = strdup(buf);
10175         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10176         if(engineMnemonic[i]) free(engineMnemonic[i]);
10177         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10178             strcat(buf, " (");
10179             sscanf(q + 8, "%s", buf + strlen(buf));
10180             strcat(buf, ")");
10181         }
10182         engineMnemonic[i] = strdup(buf);
10183         i++;
10184     }
10185     engineList[i] = engineMnemonic[i] = NULL;
10186     return i;
10187 }
10188
10189 // following implemented as macro to avoid type limitations
10190 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10191
10192 void
10193 SwapEngines (int n)
10194 {   // swap settings for first engine and other engine (so far only some selected options)
10195     int h;
10196     char *p;
10197     if(n == 0) return;
10198     SWAP(directory, p)
10199     SWAP(chessProgram, p)
10200     SWAP(isUCI, h)
10201     SWAP(hasOwnBookUCI, h)
10202     SWAP(protocolVersion, h)
10203     SWAP(reuse, h)
10204     SWAP(scoreIsAbsolute, h)
10205     SWAP(timeOdds, h)
10206     SWAP(logo, p)
10207     SWAP(pgnName, p)
10208     SWAP(pvSAN, h)
10209     SWAP(engOptions, p)
10210     SWAP(engInitString, p)
10211     SWAP(computerString, p)
10212     SWAP(features, p)
10213     SWAP(fenOverride, p)
10214     SWAP(NPS, h)
10215     SWAP(accumulateTC, h)
10216     SWAP(host, p)
10217 }
10218
10219 int
10220 GetEngineLine (char *s, int n)
10221 {
10222     int i;
10223     char buf[MSG_SIZ];
10224     extern char *icsNames;
10225     if(!s || !*s) return 0;
10226     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
10227     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
10228     if(!mnemonic[i]) return 0;
10229     if(n == 11) return 1; // just testing if there was a match
10230     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
10231     if(n == 1) SwapEngines(n);
10232     ParseArgsFromString(buf);
10233     if(n == 1) SwapEngines(n);
10234     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
10235         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
10236         ParseArgsFromString(buf);
10237     }
10238     return 1;
10239 }
10240
10241 int
10242 SetPlayer (int player, char *p)
10243 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
10244     int i;
10245     char buf[MSG_SIZ], *engineName;
10246     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10247     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10248     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10249     if(mnemonic[i]) {
10250         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10251         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10252         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10253         ParseArgsFromString(buf);
10254     }
10255     free(engineName);
10256     return i;
10257 }
10258
10259 char *recentEngines;
10260
10261 void
10262 RecentEngineEvent (int nr)
10263 {
10264     int n;
10265 //    SwapEngines(1); // bump first to second
10266 //    ReplaceEngine(&second, 1); // and load it there
10267     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10268     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10269     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10270         ReplaceEngine(&first, 0);
10271         FloatToFront(&appData.recentEngineList, command[n]);
10272     }
10273 }
10274
10275 int
10276 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10277 {   // determine players from game number
10278     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10279
10280     if(appData.tourneyType == 0) {
10281         roundsPerCycle = (nPlayers - 1) | 1;
10282         pairingsPerRound = nPlayers / 2;
10283     } else if(appData.tourneyType > 0) {
10284         roundsPerCycle = nPlayers - appData.tourneyType;
10285         pairingsPerRound = appData.tourneyType;
10286     }
10287     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10288     gamesPerCycle = gamesPerRound * roundsPerCycle;
10289     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10290     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10291     curRound = nr / gamesPerRound; nr %= gamesPerRound;
10292     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10293     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10294     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10295
10296     if(appData.cycleSync) *syncInterval = gamesPerCycle;
10297     if(appData.roundSync) *syncInterval = gamesPerRound;
10298
10299     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10300
10301     if(appData.tourneyType == 0) {
10302         if(curPairing == (nPlayers-1)/2 ) {
10303             *whitePlayer = curRound;
10304             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10305         } else {
10306             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10307             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10308             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10309             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10310         }
10311     } else if(appData.tourneyType > 1) {
10312         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10313         *whitePlayer = curRound + appData.tourneyType;
10314     } else if(appData.tourneyType > 0) {
10315         *whitePlayer = curPairing;
10316         *blackPlayer = curRound + appData.tourneyType;
10317     }
10318
10319     // take care of white/black alternation per round. 
10320     // For cycles and games this is already taken care of by default, derived from matchGame!
10321     return curRound & 1;
10322 }
10323
10324 int
10325 NextTourneyGame (int nr, int *swapColors)
10326 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10327     char *p, *q;
10328     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers;
10329     FILE *tf;
10330     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10331     tf = fopen(appData.tourneyFile, "r");
10332     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10333     ParseArgsFromFile(tf); fclose(tf);
10334     InitTimeControls(); // TC might be altered from tourney file
10335
10336     nPlayers = CountPlayers(appData.participants); // count participants
10337     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10338     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10339
10340     if(syncInterval) {
10341         p = q = appData.results;
10342         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10343         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10344             DisplayMessage(_("Waiting for other game(s)"),"");
10345             waitingForGame = TRUE;
10346             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10347             return 0;
10348         }
10349         waitingForGame = FALSE;
10350     }
10351
10352     if(appData.tourneyType < 0) {
10353         if(nr>=0 && !pairingReceived) {
10354             char buf[1<<16];
10355             if(pairing.pr == NoProc) {
10356                 if(!appData.pairingEngine[0]) {
10357                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10358                     return 0;
10359                 }
10360                 StartChessProgram(&pairing); // starts the pairing engine
10361             }
10362             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10363             SendToProgram(buf, &pairing);
10364             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10365             SendToProgram(buf, &pairing);
10366             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10367         }
10368         pairingReceived = 0;                              // ... so we continue here 
10369         *swapColors = 0;
10370         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10371         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10372         matchGame = 1; roundNr = nr / syncInterval + 1;
10373     }
10374
10375     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10376
10377     // redefine engines, engine dir, etc.
10378     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10379     if(first.pr == NoProc) {
10380       SetPlayer(whitePlayer, appData.participants); // find white player amongst it, and parse its engine line
10381       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10382     }
10383     if(second.pr == NoProc) {
10384       SwapEngines(1);
10385       SetPlayer(blackPlayer, appData.participants); // find black player amongst it, and parse its engine line
10386       SwapEngines(1);         // and make that valid for second engine by swapping
10387       InitEngine(&second, 1);
10388     }
10389     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10390     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10391     return 1;
10392 }
10393
10394 void
10395 NextMatchGame ()
10396 {   // performs game initialization that does not invoke engines, and then tries to start the game
10397     int res, firstWhite, swapColors = 0;
10398     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10399     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
10400         char buf[MSG_SIZ];
10401         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
10402         if(strcmp(buf, currentDebugFile)) { // name has changed
10403             FILE *f = fopen(buf, "w");
10404             if(f) { // if opening the new file failed, just keep using the old one
10405                 ASSIGN(currentDebugFile, buf);
10406                 fclose(debugFP);
10407                 debugFP = f;
10408             }
10409             if(appData.serverFileName) {
10410                 if(serverFP) fclose(serverFP);
10411                 serverFP = fopen(appData.serverFileName, "w");
10412                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
10413                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
10414             }
10415         }
10416     }
10417     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10418     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10419     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
10420     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10421     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10422     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10423     Reset(FALSE, first.pr != NoProc);
10424     res = LoadGameOrPosition(matchGame); // setup game
10425     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10426     if(!res) return; // abort when bad game/pos file
10427     TwoMachinesEvent();
10428 }
10429
10430 void
10431 UserAdjudicationEvent (int result)
10432 {
10433     ChessMove gameResult = GameIsDrawn;
10434
10435     if( result > 0 ) {
10436         gameResult = WhiteWins;
10437     }
10438     else if( result < 0 ) {
10439         gameResult = BlackWins;
10440     }
10441
10442     if( gameMode == TwoMachinesPlay ) {
10443         GameEnds( gameResult, "User adjudication", GE_XBOARD );
10444     }
10445 }
10446
10447
10448 // [HGM] save: calculate checksum of game to make games easily identifiable
10449 int
10450 StringCheckSum (char *s)
10451 {
10452         int i = 0;
10453         if(s==NULL) return 0;
10454         while(*s) i = i*259 + *s++;
10455         return i;
10456 }
10457
10458 int
10459 GameCheckSum ()
10460 {
10461         int i, sum=0;
10462         for(i=backwardMostMove; i<forwardMostMove; i++) {
10463                 sum += pvInfoList[i].depth;
10464                 sum += StringCheckSum(parseList[i]);
10465                 sum += StringCheckSum(commentList[i]);
10466                 sum *= 261;
10467         }
10468         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10469         return sum + StringCheckSum(commentList[i]);
10470 } // end of save patch
10471
10472 void
10473 GameEnds (ChessMove result, char *resultDetails, int whosays)
10474 {
10475     GameMode nextGameMode;
10476     int isIcsGame;
10477     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10478
10479     if(endingGame) return; /* [HGM] crash: forbid recursion */
10480     endingGame = 1;
10481     if(twoBoards) { // [HGM] dual: switch back to one board
10482         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10483         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10484     }
10485     if (appData.debugMode) {
10486       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10487               result, resultDetails ? resultDetails : "(null)", whosays);
10488     }
10489
10490     fromX = fromY = -1; // [HGM] abort any move the user is entering.
10491
10492     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10493         /* If we are playing on ICS, the server decides when the
10494            game is over, but the engine can offer to draw, claim
10495            a draw, or resign.
10496          */
10497 #if ZIPPY
10498         if (appData.zippyPlay && first.initDone) {
10499             if (result == GameIsDrawn) {
10500                 /* In case draw still needs to be claimed */
10501                 SendToICS(ics_prefix);
10502                 SendToICS("draw\n");
10503             } else if (StrCaseStr(resultDetails, "resign")) {
10504                 SendToICS(ics_prefix);
10505                 SendToICS("resign\n");
10506             }
10507         }
10508 #endif
10509         endingGame = 0; /* [HGM] crash */
10510         return;
10511     }
10512
10513     /* If we're loading the game from a file, stop */
10514     if (whosays == GE_FILE) {
10515       (void) StopLoadGameTimer();
10516       gameFileFP = NULL;
10517     }
10518
10519     /* Cancel draw offers */
10520     first.offeredDraw = second.offeredDraw = 0;
10521
10522     /* If this is an ICS game, only ICS can really say it's done;
10523        if not, anyone can. */
10524     isIcsGame = (gameMode == IcsPlayingWhite ||
10525                  gameMode == IcsPlayingBlack ||
10526                  gameMode == IcsObserving    ||
10527                  gameMode == IcsExamining);
10528
10529     if (!isIcsGame || whosays == GE_ICS) {
10530         /* OK -- not an ICS game, or ICS said it was done */
10531         StopClocks();
10532         if (!isIcsGame && !appData.noChessProgram)
10533           SetUserThinkingEnables();
10534
10535         /* [HGM] if a machine claims the game end we verify this claim */
10536         if(gameMode == TwoMachinesPlay && appData.testClaims) {
10537             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10538                 char claimer;
10539                 ChessMove trueResult = (ChessMove) -1;
10540
10541                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
10542                                             first.twoMachinesColor[0] :
10543                                             second.twoMachinesColor[0] ;
10544
10545                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10546                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10547                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10548                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10549                 } else
10550                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10551                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10552                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10553                 } else
10554                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10555                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10556                 }
10557
10558                 // now verify win claims, but not in drop games, as we don't understand those yet
10559                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10560                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10561                     (result == WhiteWins && claimer == 'w' ||
10562                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
10563                       if (appData.debugMode) {
10564                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
10565                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10566                       }
10567                       if(result != trueResult) {
10568                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10569                               result = claimer == 'w' ? BlackWins : WhiteWins;
10570                               resultDetails = buf;
10571                       }
10572                 } else
10573                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10574                     && (forwardMostMove <= backwardMostMove ||
10575                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10576                         (claimer=='b')==(forwardMostMove&1))
10577                                                                                   ) {
10578                       /* [HGM] verify: draws that were not flagged are false claims */
10579                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10580                       result = claimer == 'w' ? BlackWins : WhiteWins;
10581                       resultDetails = buf;
10582                 }
10583                 /* (Claiming a loss is accepted no questions asked!) */
10584             }
10585             /* [HGM] bare: don't allow bare King to win */
10586             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10587                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10588                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10589                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10590                && result != GameIsDrawn)
10591             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10592                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10593                         int p = (signed char)boards[forwardMostMove][i][j] - color;
10594                         if(p >= 0 && p <= (int)WhiteKing) k++;
10595                 }
10596                 if (appData.debugMode) {
10597                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10598                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10599                 }
10600                 if(k <= 1) {
10601                         result = GameIsDrawn;
10602                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10603                         resultDetails = buf;
10604                 }
10605             }
10606         }
10607
10608
10609         if(serverMoves != NULL && !loadFlag) { char c = '=';
10610             if(result==WhiteWins) c = '+';
10611             if(result==BlackWins) c = '-';
10612             if(resultDetails != NULL)
10613                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
10614         }
10615         if (resultDetails != NULL) {
10616             gameInfo.result = result;
10617             gameInfo.resultDetails = StrSave(resultDetails);
10618
10619             /* display last move only if game was not loaded from file */
10620             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10621                 DisplayMove(currentMove - 1);
10622
10623             if (forwardMostMove != 0) {
10624                 if (gameMode != PlayFromGameFile && gameMode != EditGame
10625                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10626                                                                 ) {
10627                     if (*appData.saveGameFile != NULLCHAR) {
10628                         SaveGameToFile(appData.saveGameFile, TRUE);
10629                     } else if (appData.autoSaveGames) {
10630                         AutoSaveGame();
10631                     }
10632                     if (*appData.savePositionFile != NULLCHAR) {
10633                         SavePositionToFile(appData.savePositionFile);
10634                     }
10635                 }
10636             }
10637
10638             /* Tell program how game ended in case it is learning */
10639             /* [HGM] Moved this to after saving the PGN, just in case */
10640             /* engine died and we got here through time loss. In that */
10641             /* case we will get a fatal error writing the pipe, which */
10642             /* would otherwise lose us the PGN.                       */
10643             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
10644             /* output during GameEnds should never be fatal anymore   */
10645             if (gameMode == MachinePlaysWhite ||
10646                 gameMode == MachinePlaysBlack ||
10647                 gameMode == TwoMachinesPlay ||
10648                 gameMode == IcsPlayingWhite ||
10649                 gameMode == IcsPlayingBlack ||
10650                 gameMode == BeginningOfGame) {
10651                 char buf[MSG_SIZ];
10652                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10653                         resultDetails);
10654                 if (first.pr != NoProc) {
10655                     SendToProgram(buf, &first);
10656                 }
10657                 if (second.pr != NoProc &&
10658                     gameMode == TwoMachinesPlay) {
10659                     SendToProgram(buf, &second);
10660                 }
10661             }
10662         }
10663
10664         if (appData.icsActive) {
10665             if (appData.quietPlay &&
10666                 (gameMode == IcsPlayingWhite ||
10667                  gameMode == IcsPlayingBlack)) {
10668                 SendToICS(ics_prefix);
10669                 SendToICS("set shout 1\n");
10670             }
10671             nextGameMode = IcsIdle;
10672             ics_user_moved = FALSE;
10673             /* clean up premove.  It's ugly when the game has ended and the
10674              * premove highlights are still on the board.
10675              */
10676             if (gotPremove) {
10677               gotPremove = FALSE;
10678               ClearPremoveHighlights();
10679               DrawPosition(FALSE, boards[currentMove]);
10680             }
10681             if (whosays == GE_ICS) {
10682                 switch (result) {
10683                 case WhiteWins:
10684                     if (gameMode == IcsPlayingWhite)
10685                         PlayIcsWinSound();
10686                     else if(gameMode == IcsPlayingBlack)
10687                         PlayIcsLossSound();
10688                     break;
10689                 case BlackWins:
10690                     if (gameMode == IcsPlayingBlack)
10691                         PlayIcsWinSound();
10692                     else if(gameMode == IcsPlayingWhite)
10693                         PlayIcsLossSound();
10694                     break;
10695                 case GameIsDrawn:
10696                     PlayIcsDrawSound();
10697                     break;
10698                 default:
10699                     PlayIcsUnfinishedSound();
10700                 }
10701             }
10702         } else if (gameMode == EditGame ||
10703                    gameMode == PlayFromGameFile ||
10704                    gameMode == AnalyzeMode ||
10705                    gameMode == AnalyzeFile) {
10706             nextGameMode = gameMode;
10707         } else {
10708             nextGameMode = EndOfGame;
10709         }
10710         pausing = FALSE;
10711         ModeHighlight();
10712     } else {
10713         nextGameMode = gameMode;
10714     }
10715
10716     if (appData.noChessProgram) {
10717         gameMode = nextGameMode;
10718         ModeHighlight();
10719         endingGame = 0; /* [HGM] crash */
10720         return;
10721     }
10722
10723     if (first.reuse) {
10724         /* Put first chess program into idle state */
10725         if (first.pr != NoProc &&
10726             (gameMode == MachinePlaysWhite ||
10727              gameMode == MachinePlaysBlack ||
10728              gameMode == TwoMachinesPlay ||
10729              gameMode == IcsPlayingWhite ||
10730              gameMode == IcsPlayingBlack ||
10731              gameMode == BeginningOfGame)) {
10732             SendToProgram("force\n", &first);
10733             if (first.usePing) {
10734               char buf[MSG_SIZ];
10735               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10736               SendToProgram(buf, &first);
10737             }
10738         }
10739     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10740         /* Kill off first chess program */
10741         if (first.isr != NULL)
10742           RemoveInputSource(first.isr);
10743         first.isr = NULL;
10744
10745         if (first.pr != NoProc) {
10746             ExitAnalyzeMode();
10747             DoSleep( appData.delayBeforeQuit );
10748             SendToProgram("quit\n", &first);
10749             DoSleep( appData.delayAfterQuit );
10750             DestroyChildProcess(first.pr, first.useSigterm);
10751         }
10752         first.pr = NoProc;
10753     }
10754     if (second.reuse) {
10755         /* Put second chess program into idle state */
10756         if (second.pr != NoProc &&
10757             gameMode == TwoMachinesPlay) {
10758             SendToProgram("force\n", &second);
10759             if (second.usePing) {
10760               char buf[MSG_SIZ];
10761               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10762               SendToProgram(buf, &second);
10763             }
10764         }
10765     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10766         /* Kill off second chess program */
10767         if (second.isr != NULL)
10768           RemoveInputSource(second.isr);
10769         second.isr = NULL;
10770
10771         if (second.pr != NoProc) {
10772             DoSleep( appData.delayBeforeQuit );
10773             SendToProgram("quit\n", &second);
10774             DoSleep( appData.delayAfterQuit );
10775             DestroyChildProcess(second.pr, second.useSigterm);
10776         }
10777         second.pr = NoProc;
10778     }
10779
10780     if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10781         char resChar = '=';
10782         switch (result) {
10783         case WhiteWins:
10784           resChar = '+';
10785           if (first.twoMachinesColor[0] == 'w') {
10786             first.matchWins++;
10787           } else {
10788             second.matchWins++;
10789           }
10790           break;
10791         case BlackWins:
10792           resChar = '-';
10793           if (first.twoMachinesColor[0] == 'b') {
10794             first.matchWins++;
10795           } else {
10796             second.matchWins++;
10797           }
10798           break;
10799         case GameUnfinished:
10800           resChar = ' ';
10801         default:
10802           break;
10803         }
10804
10805         if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10806         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10807             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
10808             ReserveGame(nextGame, resChar); // sets nextGame
10809             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10810             else ranking = strdup("busy"); //suppress popup when aborted but not finished
10811         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10812
10813         if (nextGame <= appData.matchGames && !abortMatch) {
10814             gameMode = nextGameMode;
10815             matchGame = nextGame; // this will be overruled in tourney mode!
10816             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10817             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10818             endingGame = 0; /* [HGM] crash */
10819             return;
10820         } else {
10821             gameMode = nextGameMode;
10822             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10823                      first.tidy, second.tidy,
10824                      first.matchWins, second.matchWins,
10825                      appData.matchGames - (first.matchWins + second.matchWins));
10826             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
10827             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
10828             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10829             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10830                 first.twoMachinesColor = "black\n";
10831                 second.twoMachinesColor = "white\n";
10832             } else {
10833                 first.twoMachinesColor = "white\n";
10834                 second.twoMachinesColor = "black\n";
10835             }
10836         }
10837     }
10838     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10839         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10840       ExitAnalyzeMode();
10841     gameMode = nextGameMode;
10842     ModeHighlight();
10843     endingGame = 0;  /* [HGM] crash */
10844     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10845         if(matchMode == TRUE) { // match through command line: exit with or without popup
10846             if(ranking) {
10847                 ToNrEvent(forwardMostMove);
10848                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10849                 else ExitEvent(0);
10850             } else DisplayFatalError(buf, 0, 0);
10851         } else { // match through menu; just stop, with or without popup
10852             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10853             ModeHighlight();
10854             if(ranking){
10855                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10856             } else DisplayNote(buf);
10857       }
10858       if(ranking) free(ranking);
10859     }
10860 }
10861
10862 /* Assumes program was just initialized (initString sent).
10863    Leaves program in force mode. */
10864 void
10865 FeedMovesToProgram (ChessProgramState *cps, int upto)
10866 {
10867     int i;
10868
10869     if (appData.debugMode)
10870       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10871               startedFromSetupPosition ? "position and " : "",
10872               backwardMostMove, upto, cps->which);
10873     if(currentlyInitializedVariant != gameInfo.variant) {
10874       char buf[MSG_SIZ];
10875         // [HGM] variantswitch: make engine aware of new variant
10876         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10877                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10878         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10879         SendToProgram(buf, cps);
10880         currentlyInitializedVariant = gameInfo.variant;
10881     }
10882     SendToProgram("force\n", cps);
10883     if (startedFromSetupPosition) {
10884         SendBoard(cps, backwardMostMove);
10885     if (appData.debugMode) {
10886         fprintf(debugFP, "feedMoves\n");
10887     }
10888     }
10889     for (i = backwardMostMove; i < upto; i++) {
10890         SendMoveToProgram(i, cps);
10891     }
10892 }
10893
10894
10895 int
10896 ResurrectChessProgram ()
10897 {
10898      /* The chess program may have exited.
10899         If so, restart it and feed it all the moves made so far. */
10900     static int doInit = 0;
10901
10902     if (appData.noChessProgram) return 1;
10903
10904     if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10905         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10906         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10907         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10908     } else {
10909         if (first.pr != NoProc) return 1;
10910         StartChessProgram(&first);
10911     }
10912     InitChessProgram(&first, FALSE);
10913     FeedMovesToProgram(&first, currentMove);
10914
10915     if (!first.sendTime) {
10916         /* can't tell gnuchess what its clock should read,
10917            so we bow to its notion. */
10918         ResetClocks();
10919         timeRemaining[0][currentMove] = whiteTimeRemaining;
10920         timeRemaining[1][currentMove] = blackTimeRemaining;
10921     }
10922
10923     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10924                 appData.icsEngineAnalyze) && first.analysisSupport) {
10925       SendToProgram("analyze\n", &first);
10926       first.analyzing = TRUE;
10927     }
10928     return 1;
10929 }
10930
10931 /*
10932  * Button procedures
10933  */
10934 void
10935 Reset (int redraw, int init)
10936 {
10937     int i;
10938
10939     if (appData.debugMode) {
10940         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10941                 redraw, init, gameMode);
10942     }
10943     CleanupTail(); // [HGM] vari: delete any stored variations
10944     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
10945     pausing = pauseExamInvalid = FALSE;
10946     startedFromSetupPosition = blackPlaysFirst = FALSE;
10947     firstMove = TRUE;
10948     whiteFlag = blackFlag = FALSE;
10949     userOfferedDraw = FALSE;
10950     hintRequested = bookRequested = FALSE;
10951     first.maybeThinking = FALSE;
10952     second.maybeThinking = FALSE;
10953     first.bookSuspend = FALSE; // [HGM] book
10954     second.bookSuspend = FALSE;
10955     thinkOutput[0] = NULLCHAR;
10956     lastHint[0] = NULLCHAR;
10957     ClearGameInfo(&gameInfo);
10958     gameInfo.variant = StringToVariant(appData.variant);
10959     ics_user_moved = ics_clock_paused = FALSE;
10960     ics_getting_history = H_FALSE;
10961     ics_gamenum = -1;
10962     white_holding[0] = black_holding[0] = NULLCHAR;
10963     ClearProgramStats();
10964     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10965
10966     ResetFrontEnd();
10967     ClearHighlights();
10968     flipView = appData.flipView;
10969     ClearPremoveHighlights();
10970     gotPremove = FALSE;
10971     alarmSounded = FALSE;
10972
10973     GameEnds(EndOfFile, NULL, GE_PLAYER);
10974     if(appData.serverMovesName != NULL) {
10975         /* [HGM] prepare to make moves file for broadcasting */
10976         clock_t t = clock();
10977         if(serverMoves != NULL) fclose(serverMoves);
10978         serverMoves = fopen(appData.serverMovesName, "r");
10979         if(serverMoves != NULL) {
10980             fclose(serverMoves);
10981             /* delay 15 sec before overwriting, so all clients can see end */
10982             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10983         }
10984         serverMoves = fopen(appData.serverMovesName, "w");
10985     }
10986
10987     ExitAnalyzeMode();
10988     gameMode = BeginningOfGame;
10989     ModeHighlight();
10990     if(appData.icsActive) gameInfo.variant = VariantNormal;
10991     currentMove = forwardMostMove = backwardMostMove = 0;
10992     MarkTargetSquares(1);
10993     InitPosition(redraw);
10994     for (i = 0; i < MAX_MOVES; i++) {
10995         if (commentList[i] != NULL) {
10996             free(commentList[i]);
10997             commentList[i] = NULL;
10998         }
10999     }
11000     ResetClocks();
11001     timeRemaining[0][0] = whiteTimeRemaining;
11002     timeRemaining[1][0] = blackTimeRemaining;
11003
11004     if (first.pr == NoProc) {
11005         StartChessProgram(&first);
11006     }
11007     if (init) {
11008             InitChessProgram(&first, startedFromSetupPosition);
11009     }
11010     DisplayTitle("");
11011     DisplayMessage("", "");
11012     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11013     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11014     ClearMap();        // [HGM] exclude: invalidate map
11015 }
11016
11017 void
11018 AutoPlayGameLoop ()
11019 {
11020     for (;;) {
11021         if (!AutoPlayOneMove())
11022           return;
11023         if (matchMode || appData.timeDelay == 0)
11024           continue;
11025         if (appData.timeDelay < 0)
11026           return;
11027         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11028         break;
11029     }
11030 }
11031
11032 void
11033 AnalyzeNextGame()
11034 {
11035     ReloadGame(1); // next game
11036 }
11037
11038 int
11039 AutoPlayOneMove ()
11040 {
11041     int fromX, fromY, toX, toY;
11042
11043     if (appData.debugMode) {
11044       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11045     }
11046
11047     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11048       return FALSE;
11049
11050     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
11051       pvInfoList[currentMove].depth = programStats.depth;
11052       pvInfoList[currentMove].score = programStats.score;
11053       pvInfoList[currentMove].time  = 0;
11054       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11055     }
11056
11057     if (currentMove >= forwardMostMove) {
11058       if(gameMode == AnalyzeFile) {
11059           if(appData.loadGameIndex == -1) {
11060             GameEnds(EndOfFile, NULL, GE_FILE);
11061           ScheduleDelayedEvent(AnalyzeNextGame, 10);
11062           } else {
11063           ExitAnalyzeMode(); SendToProgram("force\n", &first);
11064         }
11065       }
11066 //      gameMode = EndOfGame;
11067 //      ModeHighlight();
11068
11069       /* [AS] Clear current move marker at the end of a game */
11070       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11071
11072       return FALSE;
11073     }
11074
11075     toX = moveList[currentMove][2] - AAA;
11076     toY = moveList[currentMove][3] - ONE;
11077
11078     if (moveList[currentMove][1] == '@') {
11079         if (appData.highlightLastMove) {
11080             SetHighlights(-1, -1, toX, toY);
11081         }
11082     } else {
11083         fromX = moveList[currentMove][0] - AAA;
11084         fromY = moveList[currentMove][1] - ONE;
11085
11086         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11087
11088         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11089
11090         if (appData.highlightLastMove) {
11091             SetHighlights(fromX, fromY, toX, toY);
11092         }
11093     }
11094     DisplayMove(currentMove);
11095     SendMoveToProgram(currentMove++, &first);
11096     DisplayBothClocks();
11097     DrawPosition(FALSE, boards[currentMove]);
11098     // [HGM] PV info: always display, routine tests if empty
11099     DisplayComment(currentMove - 1, commentList[currentMove]);
11100     return TRUE;
11101 }
11102
11103
11104 int
11105 LoadGameOneMove (ChessMove readAhead)
11106 {
11107     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11108     char promoChar = NULLCHAR;
11109     ChessMove moveType;
11110     char move[MSG_SIZ];
11111     char *p, *q;
11112
11113     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11114         gameMode != AnalyzeMode && gameMode != Training) {
11115         gameFileFP = NULL;
11116         return FALSE;
11117     }
11118
11119     yyboardindex = forwardMostMove;
11120     if (readAhead != EndOfFile) {
11121       moveType = readAhead;
11122     } else {
11123       if (gameFileFP == NULL)
11124           return FALSE;
11125       moveType = (ChessMove) Myylex();
11126     }
11127
11128     done = FALSE;
11129     switch (moveType) {
11130       case Comment:
11131         if (appData.debugMode)
11132           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11133         p = yy_text;
11134
11135         /* append the comment but don't display it */
11136         AppendComment(currentMove, p, FALSE);
11137         return TRUE;
11138
11139       case WhiteCapturesEnPassant:
11140       case BlackCapturesEnPassant:
11141       case WhitePromotion:
11142       case BlackPromotion:
11143       case WhiteNonPromotion:
11144       case BlackNonPromotion:
11145       case NormalMove:
11146       case WhiteKingSideCastle:
11147       case WhiteQueenSideCastle:
11148       case BlackKingSideCastle:
11149       case BlackQueenSideCastle:
11150       case WhiteKingSideCastleWild:
11151       case WhiteQueenSideCastleWild:
11152       case BlackKingSideCastleWild:
11153       case BlackQueenSideCastleWild:
11154       /* PUSH Fabien */
11155       case WhiteHSideCastleFR:
11156       case WhiteASideCastleFR:
11157       case BlackHSideCastleFR:
11158       case BlackASideCastleFR:
11159       /* POP Fabien */
11160         if (appData.debugMode)
11161           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11162         fromX = currentMoveString[0] - AAA;
11163         fromY = currentMoveString[1] - ONE;
11164         toX = currentMoveString[2] - AAA;
11165         toY = currentMoveString[3] - ONE;
11166         promoChar = currentMoveString[4];
11167         break;
11168
11169       case WhiteDrop:
11170       case BlackDrop:
11171         if (appData.debugMode)
11172           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11173         fromX = moveType == WhiteDrop ?
11174           (int) CharToPiece(ToUpper(currentMoveString[0])) :
11175         (int) CharToPiece(ToLower(currentMoveString[0]));
11176         fromY = DROP_RANK;
11177         toX = currentMoveString[2] - AAA;
11178         toY = currentMoveString[3] - ONE;
11179         break;
11180
11181       case WhiteWins:
11182       case BlackWins:
11183       case GameIsDrawn:
11184       case GameUnfinished:
11185         if (appData.debugMode)
11186           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11187         p = strchr(yy_text, '{');
11188         if (p == NULL) p = strchr(yy_text, '(');
11189         if (p == NULL) {
11190             p = yy_text;
11191             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11192         } else {
11193             q = strchr(p, *p == '{' ? '}' : ')');
11194             if (q != NULL) *q = NULLCHAR;
11195             p++;
11196         }
11197         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11198         GameEnds(moveType, p, GE_FILE);
11199         done = TRUE;
11200         if (cmailMsgLoaded) {
11201             ClearHighlights();
11202             flipView = WhiteOnMove(currentMove);
11203             if (moveType == GameUnfinished) flipView = !flipView;
11204             if (appData.debugMode)
11205               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11206         }
11207         break;
11208
11209       case EndOfFile:
11210         if (appData.debugMode)
11211           fprintf(debugFP, "Parser hit end of file\n");
11212         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11213           case MT_NONE:
11214           case MT_CHECK:
11215             break;
11216           case MT_CHECKMATE:
11217           case MT_STAINMATE:
11218             if (WhiteOnMove(currentMove)) {
11219                 GameEnds(BlackWins, "Black mates", GE_FILE);
11220             } else {
11221                 GameEnds(WhiteWins, "White mates", GE_FILE);
11222             }
11223             break;
11224           case MT_STALEMATE:
11225             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11226             break;
11227         }
11228         done = TRUE;
11229         break;
11230
11231       case MoveNumberOne:
11232         if (lastLoadGameStart == GNUChessGame) {
11233             /* GNUChessGames have numbers, but they aren't move numbers */
11234             if (appData.debugMode)
11235               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11236                       yy_text, (int) moveType);
11237             return LoadGameOneMove(EndOfFile); /* tail recursion */
11238         }
11239         /* else fall thru */
11240
11241       case XBoardGame:
11242       case GNUChessGame:
11243       case PGNTag:
11244         /* Reached start of next game in file */
11245         if (appData.debugMode)
11246           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11247         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11248           case MT_NONE:
11249           case MT_CHECK:
11250             break;
11251           case MT_CHECKMATE:
11252           case MT_STAINMATE:
11253             if (WhiteOnMove(currentMove)) {
11254                 GameEnds(BlackWins, "Black mates", GE_FILE);
11255             } else {
11256                 GameEnds(WhiteWins, "White mates", GE_FILE);
11257             }
11258             break;
11259           case MT_STALEMATE:
11260             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11261             break;
11262         }
11263         done = TRUE;
11264         break;
11265
11266       case PositionDiagram:     /* should not happen; ignore */
11267       case ElapsedTime:         /* ignore */
11268       case NAG:                 /* ignore */
11269         if (appData.debugMode)
11270           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11271                   yy_text, (int) moveType);
11272         return LoadGameOneMove(EndOfFile); /* tail recursion */
11273
11274       case IllegalMove:
11275         if (appData.testLegality) {
11276             if (appData.debugMode)
11277               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11278             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11279                     (forwardMostMove / 2) + 1,
11280                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11281             DisplayError(move, 0);
11282             done = TRUE;
11283         } else {
11284             if (appData.debugMode)
11285               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11286                       yy_text, currentMoveString);
11287             fromX = currentMoveString[0] - AAA;
11288             fromY = currentMoveString[1] - ONE;
11289             toX = currentMoveString[2] - AAA;
11290             toY = currentMoveString[3] - ONE;
11291             promoChar = currentMoveString[4];
11292         }
11293         break;
11294
11295       case AmbiguousMove:
11296         if (appData.debugMode)
11297           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11298         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11299                 (forwardMostMove / 2) + 1,
11300                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11301         DisplayError(move, 0);
11302         done = TRUE;
11303         break;
11304
11305       default:
11306       case ImpossibleMove:
11307         if (appData.debugMode)
11308           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11309         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11310                 (forwardMostMove / 2) + 1,
11311                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11312         DisplayError(move, 0);
11313         done = TRUE;
11314         break;
11315     }
11316
11317     if (done) {
11318         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11319             DrawPosition(FALSE, boards[currentMove]);
11320             DisplayBothClocks();
11321             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11322               DisplayComment(currentMove - 1, commentList[currentMove]);
11323         }
11324         (void) StopLoadGameTimer();
11325         gameFileFP = NULL;
11326         cmailOldMove = forwardMostMove;
11327         return FALSE;
11328     } else {
11329         /* currentMoveString is set as a side-effect of yylex */
11330
11331         thinkOutput[0] = NULLCHAR;
11332         MakeMove(fromX, fromY, toX, toY, promoChar);
11333         currentMove = forwardMostMove;
11334         return TRUE;
11335     }
11336 }
11337
11338 /* Load the nth game from the given file */
11339 int
11340 LoadGameFromFile (char *filename, int n, char *title, int useList)
11341 {
11342     FILE *f;
11343     char buf[MSG_SIZ];
11344
11345     if (strcmp(filename, "-") == 0) {
11346         f = stdin;
11347         title = "stdin";
11348     } else {
11349         f = fopen(filename, "rb");
11350         if (f == NULL) {
11351           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
11352             DisplayError(buf, errno);
11353             return FALSE;
11354         }
11355     }
11356     if (fseek(f, 0, 0) == -1) {
11357         /* f is not seekable; probably a pipe */
11358         useList = FALSE;
11359     }
11360     if (useList && n == 0) {
11361         int error = GameListBuild(f);
11362         if (error) {
11363             DisplayError(_("Cannot build game list"), error);
11364         } else if (!ListEmpty(&gameList) &&
11365                    ((ListGame *) gameList.tailPred)->number > 1) {
11366             GameListPopUp(f, title);
11367             return TRUE;
11368         }
11369         GameListDestroy();
11370         n = 1;
11371     }
11372     if (n == 0) n = 1;
11373     return LoadGame(f, n, title, FALSE);
11374 }
11375
11376
11377 void
11378 MakeRegisteredMove ()
11379 {
11380     int fromX, fromY, toX, toY;
11381     char promoChar;
11382     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11383         switch (cmailMoveType[lastLoadGameNumber - 1]) {
11384           case CMAIL_MOVE:
11385           case CMAIL_DRAW:
11386             if (appData.debugMode)
11387               fprintf(debugFP, "Restoring %s for game %d\n",
11388                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11389
11390             thinkOutput[0] = NULLCHAR;
11391             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11392             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11393             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11394             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11395             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11396             promoChar = cmailMove[lastLoadGameNumber - 1][4];
11397             MakeMove(fromX, fromY, toX, toY, promoChar);
11398             ShowMove(fromX, fromY, toX, toY);
11399
11400             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11401               case MT_NONE:
11402               case MT_CHECK:
11403                 break;
11404
11405               case MT_CHECKMATE:
11406               case MT_STAINMATE:
11407                 if (WhiteOnMove(currentMove)) {
11408                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
11409                 } else {
11410                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
11411                 }
11412                 break;
11413
11414               case MT_STALEMATE:
11415                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11416                 break;
11417             }
11418
11419             break;
11420
11421           case CMAIL_RESIGN:
11422             if (WhiteOnMove(currentMove)) {
11423                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11424             } else {
11425                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11426             }
11427             break;
11428
11429           case CMAIL_ACCEPT:
11430             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11431             break;
11432
11433           default:
11434             break;
11435         }
11436     }
11437
11438     return;
11439 }
11440
11441 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11442 int
11443 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11444 {
11445     int retVal;
11446
11447     if (gameNumber > nCmailGames) {
11448         DisplayError(_("No more games in this message"), 0);
11449         return FALSE;
11450     }
11451     if (f == lastLoadGameFP) {
11452         int offset = gameNumber - lastLoadGameNumber;
11453         if (offset == 0) {
11454             cmailMsg[0] = NULLCHAR;
11455             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11456                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11457                 nCmailMovesRegistered--;
11458             }
11459             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11460             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11461                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11462             }
11463         } else {
11464             if (! RegisterMove()) return FALSE;
11465         }
11466     }
11467
11468     retVal = LoadGame(f, gameNumber, title, useList);
11469
11470     /* Make move registered during previous look at this game, if any */
11471     MakeRegisteredMove();
11472
11473     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11474         commentList[currentMove]
11475           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11476         DisplayComment(currentMove - 1, commentList[currentMove]);
11477     }
11478
11479     return retVal;
11480 }
11481
11482 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11483 int
11484 ReloadGame (int offset)
11485 {
11486     int gameNumber = lastLoadGameNumber + offset;
11487     if (lastLoadGameFP == NULL) {
11488         DisplayError(_("No game has been loaded yet"), 0);
11489         return FALSE;
11490     }
11491     if (gameNumber <= 0) {
11492         DisplayError(_("Can't back up any further"), 0);
11493         return FALSE;
11494     }
11495     if (cmailMsgLoaded) {
11496         return CmailLoadGame(lastLoadGameFP, gameNumber,
11497                              lastLoadGameTitle, lastLoadGameUseList);
11498     } else {
11499         return LoadGame(lastLoadGameFP, gameNumber,
11500                         lastLoadGameTitle, lastLoadGameUseList);
11501     }
11502 }
11503
11504 int keys[EmptySquare+1];
11505
11506 int
11507 PositionMatches (Board b1, Board b2)
11508 {
11509     int r, f, sum=0;
11510     switch(appData.searchMode) {
11511         case 1: return CompareWithRights(b1, b2);
11512         case 2:
11513             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11514                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11515             }
11516             return TRUE;
11517         case 3:
11518             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11519               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11520                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11521             }
11522             return sum==0;
11523         case 4:
11524             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11525                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11526             }
11527             return sum==0;
11528     }
11529     return TRUE;
11530 }
11531
11532 #define Q_PROMO  4
11533 #define Q_EP     3
11534 #define Q_BCASTL 2
11535 #define Q_WCASTL 1
11536
11537 int pieceList[256], quickBoard[256];
11538 ChessSquare pieceType[256] = { EmptySquare };
11539 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11540 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11541 int soughtTotal, turn;
11542 Boolean epOK, flipSearch;
11543
11544 typedef struct {
11545     unsigned char piece, to;
11546 } Move;
11547
11548 #define DSIZE (250000)
11549
11550 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11551 Move *moveDatabase = initialSpace;
11552 unsigned int movePtr, dataSize = DSIZE;
11553
11554 int
11555 MakePieceList (Board board, int *counts)
11556 {
11557     int r, f, n=Q_PROMO, total=0;
11558     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11559     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11560         int sq = f + (r<<4);
11561         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
11562             quickBoard[sq] = ++n;
11563             pieceList[n] = sq;
11564             pieceType[n] = board[r][f];
11565             counts[board[r][f]]++;
11566             if(board[r][f] == WhiteKing) pieceList[1] = n; else
11567             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
11568             total++;
11569         }
11570     }
11571     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
11572     return total;
11573 }
11574
11575 void
11576 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
11577 {
11578     int sq = fromX + (fromY<<4);
11579     int piece = quickBoard[sq];
11580     quickBoard[sq] = 0;
11581     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
11582     if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11583         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
11584         moveDatabase[movePtr++].piece = Q_WCASTL;
11585         quickBoard[sq] = piece;
11586         piece = quickBoard[from]; quickBoard[from] = 0;
11587         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11588     } else
11589     if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11590         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
11591         moveDatabase[movePtr++].piece = Q_BCASTL;
11592         quickBoard[sq] = piece;
11593         piece = quickBoard[from]; quickBoard[from] = 0;
11594         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11595     } else
11596     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
11597         quickBoard[(fromY<<4)+toX] = 0;
11598         moveDatabase[movePtr].piece = Q_EP;
11599         moveDatabase[movePtr++].to = (fromY<<4)+toX;
11600         moveDatabase[movePtr].to = sq;
11601     } else
11602     if(promoPiece != pieceType[piece]) {
11603         moveDatabase[movePtr++].piece = Q_PROMO;
11604         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
11605     }
11606     moveDatabase[movePtr].piece = piece;
11607     quickBoard[sq] = piece;
11608     movePtr++;
11609 }
11610
11611 int
11612 PackGame (Board board)
11613 {
11614     Move *newSpace = NULL;
11615     moveDatabase[movePtr].piece = 0; // terminate previous game
11616     if(movePtr > dataSize) {
11617         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
11618         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
11619         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
11620         if(newSpace) {
11621             int i;
11622             Move *p = moveDatabase, *q = newSpace;
11623             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
11624             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
11625             moveDatabase = newSpace;
11626         } else { // calloc failed, we must be out of memory. Too bad...
11627             dataSize = 0; // prevent calloc events for all subsequent games
11628             return 0;     // and signal this one isn't cached
11629         }
11630     }
11631     movePtr++;
11632     MakePieceList(board, counts);
11633     return movePtr;
11634 }
11635
11636 int
11637 QuickCompare (Board board, int *minCounts, int *maxCounts)
11638 {   // compare according to search mode
11639     int r, f;
11640     switch(appData.searchMode)
11641     {
11642       case 1: // exact position match
11643         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
11644         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11645             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11646         }
11647         break;
11648       case 2: // can have extra material on empty squares
11649         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11650             if(board[r][f] == EmptySquare) continue;
11651             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11652         }
11653         break;
11654       case 3: // material with exact Pawn structure
11655         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11656             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
11657             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11658         } // fall through to material comparison
11659       case 4: // exact material
11660         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
11661         break;
11662       case 6: // material range with given imbalance
11663         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
11664         // fall through to range comparison
11665       case 5: // material range
11666         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
11667     }
11668     return TRUE;
11669 }
11670
11671 int
11672 QuickScan (Board board, Move *move)
11673 {   // reconstruct game,and compare all positions in it
11674     int cnt=0, stretch=0, total = MakePieceList(board, counts);
11675     do {
11676         int piece = move->piece;
11677         int to = move->to, from = pieceList[piece];
11678         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
11679           if(!piece) return -1;
11680           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
11681             piece = (++move)->piece;
11682             from = pieceList[piece];
11683             counts[pieceType[piece]]--;
11684             pieceType[piece] = (ChessSquare) move->to;
11685             counts[move->to]++;
11686           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
11687             counts[pieceType[quickBoard[to]]]--;
11688             quickBoard[to] = 0; total--;
11689             move++;
11690             continue;
11691           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
11692             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
11693             from  = pieceList[piece]; // so this must be King
11694             quickBoard[from] = 0;
11695             pieceList[piece] = to;
11696             from = pieceList[(++move)->piece]; // for FRC this has to be done here
11697             quickBoard[from] = 0; // rook
11698             quickBoard[to] = piece;
11699             to = move->to; piece = move->piece;
11700             goto aftercastle;
11701           }
11702         }
11703         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
11704         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
11705         quickBoard[from] = 0;
11706       aftercastle:
11707         quickBoard[to] = piece;
11708         pieceList[piece] = to;
11709         cnt++; turn ^= 3;
11710         if(QuickCompare(soughtBoard, minSought, maxSought) ||
11711            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
11712            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
11713                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
11714           ) {
11715             static int lastCounts[EmptySquare+1];
11716             int i;
11717             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
11718             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
11719         } else stretch = 0;
11720         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
11721         move++;
11722     } while(1);
11723 }
11724
11725 void
11726 InitSearch ()
11727 {
11728     int r, f;
11729     flipSearch = FALSE;
11730     CopyBoard(soughtBoard, boards[currentMove]);
11731     soughtTotal = MakePieceList(soughtBoard, maxSought);
11732     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
11733     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
11734     CopyBoard(reverseBoard, boards[currentMove]);
11735     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11736         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
11737         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
11738         reverseBoard[r][f] = piece;
11739     }
11740     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3; 
11741     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
11742     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
11743                  || (boards[currentMove][CASTLING][2] == NoRights || 
11744                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
11745                  && (boards[currentMove][CASTLING][5] == NoRights || 
11746                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
11747       ) {
11748         flipSearch = TRUE;
11749         CopyBoard(flipBoard, soughtBoard);
11750         CopyBoard(rotateBoard, reverseBoard);
11751         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11752             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
11753             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
11754         }
11755     }
11756     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
11757     if(appData.searchMode >= 5) {
11758         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
11759         MakePieceList(soughtBoard, minSought);
11760         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
11761     }
11762     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
11763         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
11764 }
11765
11766 GameInfo dummyInfo;
11767
11768 int
11769 GameContainsPosition (FILE *f, ListGame *lg)
11770 {
11771     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
11772     int fromX, fromY, toX, toY;
11773     char promoChar;
11774     static int initDone=FALSE;
11775
11776     // weed out games based on numerical tag comparison
11777     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
11778     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
11779     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
11780     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
11781     if(!initDone) {
11782         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
11783         initDone = TRUE;
11784     }
11785     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen);
11786     else CopyBoard(boards[scratch], initialPosition); // default start position
11787     if(lg->moves) {
11788         turn = btm + 1;
11789         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
11790         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
11791     }
11792     if(btm) plyNr++;
11793     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11794     fseek(f, lg->offset, 0);
11795     yynewfile(f);
11796     while(1) {
11797         yyboardindex = scratch;
11798         quickFlag = plyNr+1;
11799         next = Myylex();
11800         quickFlag = 0;
11801         switch(next) {
11802             case PGNTag:
11803                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
11804             default:
11805                 continue;
11806
11807             case XBoardGame:
11808             case GNUChessGame:
11809                 if(plyNr) return -1; // after we have seen moves, this is for new game
11810               continue;
11811
11812             case AmbiguousMove: // we cannot reconstruct the game beyond these two
11813             case ImpossibleMove:
11814             case WhiteWins: // game ends here with these four
11815             case BlackWins:
11816             case GameIsDrawn:
11817             case GameUnfinished:
11818                 return -1;
11819
11820             case IllegalMove:
11821                 if(appData.testLegality) return -1;
11822             case WhiteCapturesEnPassant:
11823             case BlackCapturesEnPassant:
11824             case WhitePromotion:
11825             case BlackPromotion:
11826             case WhiteNonPromotion:
11827             case BlackNonPromotion:
11828             case NormalMove:
11829             case WhiteKingSideCastle:
11830             case WhiteQueenSideCastle:
11831             case BlackKingSideCastle:
11832             case BlackQueenSideCastle:
11833             case WhiteKingSideCastleWild:
11834             case WhiteQueenSideCastleWild:
11835             case BlackKingSideCastleWild:
11836             case BlackQueenSideCastleWild:
11837             case WhiteHSideCastleFR:
11838             case WhiteASideCastleFR:
11839             case BlackHSideCastleFR:
11840             case BlackASideCastleFR:
11841                 fromX = currentMoveString[0] - AAA;
11842                 fromY = currentMoveString[1] - ONE;
11843                 toX = currentMoveString[2] - AAA;
11844                 toY = currentMoveString[3] - ONE;
11845                 promoChar = currentMoveString[4];
11846                 break;
11847             case WhiteDrop:
11848             case BlackDrop:
11849                 fromX = next == WhiteDrop ?
11850                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
11851                   (int) CharToPiece(ToLower(currentMoveString[0]));
11852                 fromY = DROP_RANK;
11853                 toX = currentMoveString[2] - AAA;
11854                 toY = currentMoveString[3] - ONE;
11855                 promoChar = 0;
11856                 break;
11857         }
11858         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
11859         plyNr++;
11860         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
11861         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11862         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
11863         if(appData.findMirror) {
11864             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
11865             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
11866         }
11867     }
11868 }
11869
11870 /* Load the nth game from open file f */
11871 int
11872 LoadGame (FILE *f, int gameNumber, char *title, int useList)
11873 {
11874     ChessMove cm;
11875     char buf[MSG_SIZ];
11876     int gn = gameNumber;
11877     ListGame *lg = NULL;
11878     int numPGNTags = 0;
11879     int err, pos = -1;
11880     GameMode oldGameMode;
11881     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
11882
11883     if (appData.debugMode)
11884         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
11885
11886     if (gameMode == Training )
11887         SetTrainingModeOff();
11888
11889     oldGameMode = gameMode;
11890     if (gameMode != BeginningOfGame) {
11891       Reset(FALSE, TRUE);
11892     }
11893
11894     gameFileFP = f;
11895     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
11896         fclose(lastLoadGameFP);
11897     }
11898
11899     if (useList) {
11900         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
11901
11902         if (lg) {
11903             fseek(f, lg->offset, 0);
11904             GameListHighlight(gameNumber);
11905             pos = lg->position;
11906             gn = 1;
11907         }
11908         else {
11909             if(gameMode == AnalyzeFile && appData.loadGameIndex == -1)
11910               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
11911             else
11912             DisplayError(_("Game number out of range"), 0);
11913             return FALSE;
11914         }
11915     } else {
11916         GameListDestroy();
11917         if (fseek(f, 0, 0) == -1) {
11918             if (f == lastLoadGameFP ?
11919                 gameNumber == lastLoadGameNumber + 1 :
11920                 gameNumber == 1) {
11921                 gn = 1;
11922             } else {
11923                 DisplayError(_("Can't seek on game file"), 0);
11924                 return FALSE;
11925             }
11926         }
11927     }
11928     lastLoadGameFP = f;
11929     lastLoadGameNumber = gameNumber;
11930     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
11931     lastLoadGameUseList = useList;
11932
11933     yynewfile(f);
11934
11935     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
11936       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
11937                 lg->gameInfo.black);
11938             DisplayTitle(buf);
11939     } else if (*title != NULLCHAR) {
11940         if (gameNumber > 1) {
11941           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
11942             DisplayTitle(buf);
11943         } else {
11944             DisplayTitle(title);
11945         }
11946     }
11947
11948     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
11949         gameMode = PlayFromGameFile;
11950         ModeHighlight();
11951     }
11952
11953     currentMove = forwardMostMove = backwardMostMove = 0;
11954     CopyBoard(boards[0], initialPosition);
11955     StopClocks();
11956
11957     /*
11958      * Skip the first gn-1 games in the file.
11959      * Also skip over anything that precedes an identifiable
11960      * start of game marker, to avoid being confused by
11961      * garbage at the start of the file.  Currently
11962      * recognized start of game markers are the move number "1",
11963      * the pattern "gnuchess .* game", the pattern
11964      * "^[#;%] [^ ]* game file", and a PGN tag block.
11965      * A game that starts with one of the latter two patterns
11966      * will also have a move number 1, possibly
11967      * following a position diagram.
11968      * 5-4-02: Let's try being more lenient and allowing a game to
11969      * start with an unnumbered move.  Does that break anything?
11970      */
11971     cm = lastLoadGameStart = EndOfFile;
11972     while (gn > 0) {
11973         yyboardindex = forwardMostMove;
11974         cm = (ChessMove) Myylex();
11975         switch (cm) {
11976           case EndOfFile:
11977             if (cmailMsgLoaded) {
11978                 nCmailGames = CMAIL_MAX_GAMES - gn;
11979             } else {
11980                 Reset(TRUE, TRUE);
11981                 DisplayError(_("Game not found in file"), 0);
11982             }
11983             return FALSE;
11984
11985           case GNUChessGame:
11986           case XBoardGame:
11987             gn--;
11988             lastLoadGameStart = cm;
11989             break;
11990
11991           case MoveNumberOne:
11992             switch (lastLoadGameStart) {
11993               case GNUChessGame:
11994               case XBoardGame:
11995               case PGNTag:
11996                 break;
11997               case MoveNumberOne:
11998               case EndOfFile:
11999                 gn--;           /* count this game */
12000                 lastLoadGameStart = cm;
12001                 break;
12002               default:
12003                 /* impossible */
12004                 break;
12005             }
12006             break;
12007
12008           case PGNTag:
12009             switch (lastLoadGameStart) {
12010               case GNUChessGame:
12011               case PGNTag:
12012               case MoveNumberOne:
12013               case EndOfFile:
12014                 gn--;           /* count this game */
12015                 lastLoadGameStart = cm;
12016                 break;
12017               case XBoardGame:
12018                 lastLoadGameStart = cm; /* game counted already */
12019                 break;
12020               default:
12021                 /* impossible */
12022                 break;
12023             }
12024             if (gn > 0) {
12025                 do {
12026                     yyboardindex = forwardMostMove;
12027                     cm = (ChessMove) Myylex();
12028                 } while (cm == PGNTag || cm == Comment);
12029             }
12030             break;
12031
12032           case WhiteWins:
12033           case BlackWins:
12034           case GameIsDrawn:
12035             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
12036                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
12037                     != CMAIL_OLD_RESULT) {
12038                     nCmailResults ++ ;
12039                     cmailResult[  CMAIL_MAX_GAMES
12040                                 - gn - 1] = CMAIL_OLD_RESULT;
12041                 }
12042             }
12043             break;
12044
12045           case NormalMove:
12046             /* Only a NormalMove can be at the start of a game
12047              * without a position diagram. */
12048             if (lastLoadGameStart == EndOfFile ) {
12049               gn--;
12050               lastLoadGameStart = MoveNumberOne;
12051             }
12052             break;
12053
12054           default:
12055             break;
12056         }
12057     }
12058
12059     if (appData.debugMode)
12060       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
12061
12062     if (cm == XBoardGame) {
12063         /* Skip any header junk before position diagram and/or move 1 */
12064         for (;;) {
12065             yyboardindex = forwardMostMove;
12066             cm = (ChessMove) Myylex();
12067
12068             if (cm == EndOfFile ||
12069                 cm == GNUChessGame || cm == XBoardGame) {
12070                 /* Empty game; pretend end-of-file and handle later */
12071                 cm = EndOfFile;
12072                 break;
12073             }
12074
12075             if (cm == MoveNumberOne || cm == PositionDiagram ||
12076                 cm == PGNTag || cm == Comment)
12077               break;
12078         }
12079     } else if (cm == GNUChessGame) {
12080         if (gameInfo.event != NULL) {
12081             free(gameInfo.event);
12082         }
12083         gameInfo.event = StrSave(yy_text);
12084     }
12085
12086     startedFromSetupPosition = FALSE;
12087     while (cm == PGNTag) {
12088         if (appData.debugMode)
12089           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
12090         err = ParsePGNTag(yy_text, &gameInfo);
12091         if (!err) numPGNTags++;
12092
12093         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
12094         if(gameInfo.variant != oldVariant) {
12095             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
12096             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
12097             InitPosition(TRUE);
12098             oldVariant = gameInfo.variant;
12099             if (appData.debugMode)
12100               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
12101         }
12102
12103
12104         if (gameInfo.fen != NULL) {
12105           Board initial_position;
12106           startedFromSetupPosition = TRUE;
12107           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
12108             Reset(TRUE, TRUE);
12109             DisplayError(_("Bad FEN position in file"), 0);
12110             return FALSE;
12111           }
12112           CopyBoard(boards[0], initial_position);
12113           if (blackPlaysFirst) {
12114             currentMove = forwardMostMove = backwardMostMove = 1;
12115             CopyBoard(boards[1], initial_position);
12116             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12117             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12118             timeRemaining[0][1] = whiteTimeRemaining;
12119             timeRemaining[1][1] = blackTimeRemaining;
12120             if (commentList[0] != NULL) {
12121               commentList[1] = commentList[0];
12122               commentList[0] = NULL;
12123             }
12124           } else {
12125             currentMove = forwardMostMove = backwardMostMove = 0;
12126           }
12127           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12128           {   int i;
12129               initialRulePlies = FENrulePlies;
12130               for( i=0; i< nrCastlingRights; i++ )
12131                   initialRights[i] = initial_position[CASTLING][i];
12132           }
12133           yyboardindex = forwardMostMove;
12134           free(gameInfo.fen);
12135           gameInfo.fen = NULL;
12136         }
12137
12138         yyboardindex = forwardMostMove;
12139         cm = (ChessMove) Myylex();
12140
12141         /* Handle comments interspersed among the tags */
12142         while (cm == Comment) {
12143             char *p;
12144             if (appData.debugMode)
12145               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12146             p = yy_text;
12147             AppendComment(currentMove, p, FALSE);
12148             yyboardindex = forwardMostMove;
12149             cm = (ChessMove) Myylex();
12150         }
12151     }
12152
12153     /* don't rely on existence of Event tag since if game was
12154      * pasted from clipboard the Event tag may not exist
12155      */
12156     if (numPGNTags > 0){
12157         char *tags;
12158         if (gameInfo.variant == VariantNormal) {
12159           VariantClass v = StringToVariant(gameInfo.event);
12160           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12161           if(v < VariantShogi) gameInfo.variant = v;
12162         }
12163         if (!matchMode) {
12164           if( appData.autoDisplayTags ) {
12165             tags = PGNTags(&gameInfo);
12166             TagsPopUp(tags, CmailMsg());
12167             free(tags);
12168           }
12169         }
12170     } else {
12171         /* Make something up, but don't display it now */
12172         SetGameInfo();
12173         TagsPopDown();
12174     }
12175
12176     if (cm == PositionDiagram) {
12177         int i, j;
12178         char *p;
12179         Board initial_position;
12180
12181         if (appData.debugMode)
12182           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12183
12184         if (!startedFromSetupPosition) {
12185             p = yy_text;
12186             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12187               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12188                 switch (*p) {
12189                   case '{':
12190                   case '[':
12191                   case '-':
12192                   case ' ':
12193                   case '\t':
12194                   case '\n':
12195                   case '\r':
12196                     break;
12197                   default:
12198                     initial_position[i][j++] = CharToPiece(*p);
12199                     break;
12200                 }
12201             while (*p == ' ' || *p == '\t' ||
12202                    *p == '\n' || *p == '\r') p++;
12203
12204             if (strncmp(p, "black", strlen("black"))==0)
12205               blackPlaysFirst = TRUE;
12206             else
12207               blackPlaysFirst = FALSE;
12208             startedFromSetupPosition = TRUE;
12209
12210             CopyBoard(boards[0], initial_position);
12211             if (blackPlaysFirst) {
12212                 currentMove = forwardMostMove = backwardMostMove = 1;
12213                 CopyBoard(boards[1], initial_position);
12214                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12215                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12216                 timeRemaining[0][1] = whiteTimeRemaining;
12217                 timeRemaining[1][1] = blackTimeRemaining;
12218                 if (commentList[0] != NULL) {
12219                     commentList[1] = commentList[0];
12220                     commentList[0] = NULL;
12221                 }
12222             } else {
12223                 currentMove = forwardMostMove = backwardMostMove = 0;
12224             }
12225         }
12226         yyboardindex = forwardMostMove;
12227         cm = (ChessMove) Myylex();
12228     }
12229
12230     if (first.pr == NoProc) {
12231         StartChessProgram(&first);
12232     }
12233     InitChessProgram(&first, FALSE);
12234     SendToProgram("force\n", &first);
12235     if (startedFromSetupPosition) {
12236         SendBoard(&first, forwardMostMove);
12237     if (appData.debugMode) {
12238         fprintf(debugFP, "Load Game\n");
12239     }
12240         DisplayBothClocks();
12241     }
12242
12243     /* [HGM] server: flag to write setup moves in broadcast file as one */
12244     loadFlag = appData.suppressLoadMoves;
12245
12246     while (cm == Comment) {
12247         char *p;
12248         if (appData.debugMode)
12249           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12250         p = yy_text;
12251         AppendComment(currentMove, p, FALSE);
12252         yyboardindex = forwardMostMove;
12253         cm = (ChessMove) Myylex();
12254     }
12255
12256     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12257         cm == WhiteWins || cm == BlackWins ||
12258         cm == GameIsDrawn || cm == GameUnfinished) {
12259         DisplayMessage("", _("No moves in game"));
12260         if (cmailMsgLoaded) {
12261             if (appData.debugMode)
12262               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12263             ClearHighlights();
12264             flipView = FALSE;
12265         }
12266         DrawPosition(FALSE, boards[currentMove]);
12267         DisplayBothClocks();
12268         gameMode = EditGame;
12269         ModeHighlight();
12270         gameFileFP = NULL;
12271         cmailOldMove = 0;
12272         return TRUE;
12273     }
12274
12275     // [HGM] PV info: routine tests if comment empty
12276     if (!matchMode && (pausing || appData.timeDelay != 0)) {
12277         DisplayComment(currentMove - 1, commentList[currentMove]);
12278     }
12279     if (!matchMode && appData.timeDelay != 0)
12280       DrawPosition(FALSE, boards[currentMove]);
12281
12282     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12283       programStats.ok_to_send = 1;
12284     }
12285
12286     /* if the first token after the PGN tags is a move
12287      * and not move number 1, retrieve it from the parser
12288      */
12289     if (cm != MoveNumberOne)
12290         LoadGameOneMove(cm);
12291
12292     /* load the remaining moves from the file */
12293     while (LoadGameOneMove(EndOfFile)) {
12294       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12295       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12296     }
12297
12298     /* rewind to the start of the game */
12299     currentMove = backwardMostMove;
12300
12301     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12302
12303     if (oldGameMode == AnalyzeFile ||
12304         oldGameMode == AnalyzeMode) {
12305       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
12306       keepInfo = 1;
12307       AnalyzeFileEvent();
12308       keepInfo = 0;
12309     }
12310
12311     if (!matchMode && pos > 0) {
12312         ToNrEvent(pos); // [HGM] no autoplay if selected on position
12313     } else
12314     if (matchMode || appData.timeDelay == 0) {
12315       ToEndEvent();
12316     } else if (appData.timeDelay > 0) {
12317       AutoPlayGameLoop();
12318     }
12319
12320     if (appData.debugMode)
12321         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
12322
12323     loadFlag = 0; /* [HGM] true game starts */
12324     return TRUE;
12325 }
12326
12327 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
12328 int
12329 ReloadPosition (int offset)
12330 {
12331     int positionNumber = lastLoadPositionNumber + offset;
12332     if (lastLoadPositionFP == NULL) {
12333         DisplayError(_("No position has been loaded yet"), 0);
12334         return FALSE;
12335     }
12336     if (positionNumber <= 0) {
12337         DisplayError(_("Can't back up any further"), 0);
12338         return FALSE;
12339     }
12340     return LoadPosition(lastLoadPositionFP, positionNumber,
12341                         lastLoadPositionTitle);
12342 }
12343
12344 /* Load the nth position from the given file */
12345 int
12346 LoadPositionFromFile (char *filename, int n, char *title)
12347 {
12348     FILE *f;
12349     char buf[MSG_SIZ];
12350
12351     if (strcmp(filename, "-") == 0) {
12352         return LoadPosition(stdin, n, "stdin");
12353     } else {
12354         f = fopen(filename, "rb");
12355         if (f == NULL) {
12356             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12357             DisplayError(buf, errno);
12358             return FALSE;
12359         } else {
12360             return LoadPosition(f, n, title);
12361         }
12362     }
12363 }
12364
12365 /* Load the nth position from the given open file, and close it */
12366 int
12367 LoadPosition (FILE *f, int positionNumber, char *title)
12368 {
12369     char *p, line[MSG_SIZ];
12370     Board initial_position;
12371     int i, j, fenMode, pn;
12372
12373     if (gameMode == Training )
12374         SetTrainingModeOff();
12375
12376     if (gameMode != BeginningOfGame) {
12377         Reset(FALSE, TRUE);
12378     }
12379     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12380         fclose(lastLoadPositionFP);
12381     }
12382     if (positionNumber == 0) positionNumber = 1;
12383     lastLoadPositionFP = f;
12384     lastLoadPositionNumber = positionNumber;
12385     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12386     if (first.pr == NoProc && !appData.noChessProgram) {
12387       StartChessProgram(&first);
12388       InitChessProgram(&first, FALSE);
12389     }
12390     pn = positionNumber;
12391     if (positionNumber < 0) {
12392         /* Negative position number means to seek to that byte offset */
12393         if (fseek(f, -positionNumber, 0) == -1) {
12394             DisplayError(_("Can't seek on position file"), 0);
12395             return FALSE;
12396         };
12397         pn = 1;
12398     } else {
12399         if (fseek(f, 0, 0) == -1) {
12400             if (f == lastLoadPositionFP ?
12401                 positionNumber == lastLoadPositionNumber + 1 :
12402                 positionNumber == 1) {
12403                 pn = 1;
12404             } else {
12405                 DisplayError(_("Can't seek on position file"), 0);
12406                 return FALSE;
12407             }
12408         }
12409     }
12410     /* See if this file is FEN or old-style xboard */
12411     if (fgets(line, MSG_SIZ, f) == NULL) {
12412         DisplayError(_("Position not found in file"), 0);
12413         return FALSE;
12414     }
12415     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12416     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12417
12418     if (pn >= 2) {
12419         if (fenMode || line[0] == '#') pn--;
12420         while (pn > 0) {
12421             /* skip positions before number pn */
12422             if (fgets(line, MSG_SIZ, f) == NULL) {
12423                 Reset(TRUE, TRUE);
12424                 DisplayError(_("Position not found in file"), 0);
12425                 return FALSE;
12426             }
12427             if (fenMode || line[0] == '#') pn--;
12428         }
12429     }
12430
12431     if (fenMode) {
12432         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
12433             DisplayError(_("Bad FEN position in file"), 0);
12434             return FALSE;
12435         }
12436     } else {
12437         (void) fgets(line, MSG_SIZ, f);
12438         (void) fgets(line, MSG_SIZ, f);
12439
12440         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12441             (void) fgets(line, MSG_SIZ, f);
12442             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12443                 if (*p == ' ')
12444                   continue;
12445                 initial_position[i][j++] = CharToPiece(*p);
12446             }
12447         }
12448
12449         blackPlaysFirst = FALSE;
12450         if (!feof(f)) {
12451             (void) fgets(line, MSG_SIZ, f);
12452             if (strncmp(line, "black", strlen("black"))==0)
12453               blackPlaysFirst = TRUE;
12454         }
12455     }
12456     startedFromSetupPosition = TRUE;
12457
12458     CopyBoard(boards[0], initial_position);
12459     if (blackPlaysFirst) {
12460         currentMove = forwardMostMove = backwardMostMove = 1;
12461         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12462         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12463         CopyBoard(boards[1], initial_position);
12464         DisplayMessage("", _("Black to play"));
12465     } else {
12466         currentMove = forwardMostMove = backwardMostMove = 0;
12467         DisplayMessage("", _("White to play"));
12468     }
12469     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12470     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12471         SendToProgram("force\n", &first);
12472         SendBoard(&first, forwardMostMove);
12473     }
12474     if (appData.debugMode) {
12475 int i, j;
12476   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12477   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12478         fprintf(debugFP, "Load Position\n");
12479     }
12480
12481     if (positionNumber > 1) {
12482       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12483         DisplayTitle(line);
12484     } else {
12485         DisplayTitle(title);
12486     }
12487     gameMode = EditGame;
12488     ModeHighlight();
12489     ResetClocks();
12490     timeRemaining[0][1] = whiteTimeRemaining;
12491     timeRemaining[1][1] = blackTimeRemaining;
12492     DrawPosition(FALSE, boards[currentMove]);
12493
12494     return TRUE;
12495 }
12496
12497
12498 void
12499 CopyPlayerNameIntoFileName (char **dest, char *src)
12500 {
12501     while (*src != NULLCHAR && *src != ',') {
12502         if (*src == ' ') {
12503             *(*dest)++ = '_';
12504             src++;
12505         } else {
12506             *(*dest)++ = *src++;
12507         }
12508     }
12509 }
12510
12511 char *
12512 DefaultFileName (char *ext)
12513 {
12514     static char def[MSG_SIZ];
12515     char *p;
12516
12517     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12518         p = def;
12519         CopyPlayerNameIntoFileName(&p, gameInfo.white);
12520         *p++ = '-';
12521         CopyPlayerNameIntoFileName(&p, gameInfo.black);
12522         *p++ = '.';
12523         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12524     } else {
12525         def[0] = NULLCHAR;
12526     }
12527     return def;
12528 }
12529
12530 /* Save the current game to the given file */
12531 int
12532 SaveGameToFile (char *filename, int append)
12533 {
12534     FILE *f;
12535     char buf[MSG_SIZ];
12536     int result, i, t,tot=0;
12537
12538     if (strcmp(filename, "-") == 0) {
12539         return SaveGame(stdout, 0, NULL);
12540     } else {
12541         for(i=0; i<10; i++) { // upto 10 tries
12542              f = fopen(filename, append ? "a" : "w");
12543              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12544              if(f || errno != 13) break;
12545              DoSleep(t = 5 + random()%11); // wait 5-15 msec
12546              tot += t;
12547         }
12548         if (f == NULL) {
12549             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12550             DisplayError(buf, errno);
12551             return FALSE;
12552         } else {
12553             safeStrCpy(buf, lastMsg, MSG_SIZ);
12554             DisplayMessage(_("Waiting for access to save file"), "");
12555             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
12556             DisplayMessage(_("Saving game"), "");
12557             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
12558             result = SaveGame(f, 0, NULL);
12559             DisplayMessage(buf, "");
12560             return result;
12561         }
12562     }
12563 }
12564
12565 char *
12566 SavePart (char *str)
12567 {
12568     static char buf[MSG_SIZ];
12569     char *p;
12570
12571     p = strchr(str, ' ');
12572     if (p == NULL) return str;
12573     strncpy(buf, str, p - str);
12574     buf[p - str] = NULLCHAR;
12575     return buf;
12576 }
12577
12578 #define PGN_MAX_LINE 75
12579
12580 #define PGN_SIDE_WHITE  0
12581 #define PGN_SIDE_BLACK  1
12582
12583 static int
12584 FindFirstMoveOutOfBook (int side)
12585 {
12586     int result = -1;
12587
12588     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12589         int index = backwardMostMove;
12590         int has_book_hit = 0;
12591
12592         if( (index % 2) != side ) {
12593             index++;
12594         }
12595
12596         while( index < forwardMostMove ) {
12597             /* Check to see if engine is in book */
12598             int depth = pvInfoList[index].depth;
12599             int score = pvInfoList[index].score;
12600             int in_book = 0;
12601
12602             if( depth <= 2 ) {
12603                 in_book = 1;
12604             }
12605             else if( score == 0 && depth == 63 ) {
12606                 in_book = 1; /* Zappa */
12607             }
12608             else if( score == 2 && depth == 99 ) {
12609                 in_book = 1; /* Abrok */
12610             }
12611
12612             has_book_hit += in_book;
12613
12614             if( ! in_book ) {
12615                 result = index;
12616
12617                 break;
12618             }
12619
12620             index += 2;
12621         }
12622     }
12623
12624     return result;
12625 }
12626
12627 void
12628 GetOutOfBookInfo (char * buf)
12629 {
12630     int oob[2];
12631     int i;
12632     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12633
12634     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12635     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12636
12637     *buf = '\0';
12638
12639     if( oob[0] >= 0 || oob[1] >= 0 ) {
12640         for( i=0; i<2; i++ ) {
12641             int idx = oob[i];
12642
12643             if( idx >= 0 ) {
12644                 if( i > 0 && oob[0] >= 0 ) {
12645                     strcat( buf, "   " );
12646                 }
12647
12648                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12649                 sprintf( buf+strlen(buf), "%s%.2f",
12650                     pvInfoList[idx].score >= 0 ? "+" : "",
12651                     pvInfoList[idx].score / 100.0 );
12652             }
12653         }
12654     }
12655 }
12656
12657 /* Save game in PGN style and close the file */
12658 int
12659 SaveGamePGN (FILE *f)
12660 {
12661     int i, offset, linelen, newblock;
12662 //    char *movetext;
12663     char numtext[32];
12664     int movelen, numlen, blank;
12665     char move_buffer[100]; /* [AS] Buffer for move+PV info */
12666
12667     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12668
12669     PrintPGNTags(f, &gameInfo);
12670
12671     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
12672
12673     if (backwardMostMove > 0 || startedFromSetupPosition) {
12674         char *fen = PositionToFEN(backwardMostMove, NULL);
12675         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12676         fprintf(f, "\n{--------------\n");
12677         PrintPosition(f, backwardMostMove);
12678         fprintf(f, "--------------}\n");
12679         free(fen);
12680     }
12681     else {
12682         /* [AS] Out of book annotation */
12683         if( appData.saveOutOfBookInfo ) {
12684             char buf[64];
12685
12686             GetOutOfBookInfo( buf );
12687
12688             if( buf[0] != '\0' ) {
12689                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12690             }
12691         }
12692
12693         fprintf(f, "\n");
12694     }
12695
12696     i = backwardMostMove;
12697     linelen = 0;
12698     newblock = TRUE;
12699
12700     while (i < forwardMostMove) {
12701         /* Print comments preceding this move */
12702         if (commentList[i] != NULL) {
12703             if (linelen > 0) fprintf(f, "\n");
12704             fprintf(f, "%s", commentList[i]);
12705             linelen = 0;
12706             newblock = TRUE;
12707         }
12708
12709         /* Format move number */
12710         if ((i % 2) == 0)
12711           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
12712         else
12713           if (newblock)
12714             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
12715           else
12716             numtext[0] = NULLCHAR;
12717
12718         numlen = strlen(numtext);
12719         newblock = FALSE;
12720
12721         /* Print move number */
12722         blank = linelen > 0 && numlen > 0;
12723         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
12724             fprintf(f, "\n");
12725             linelen = 0;
12726             blank = 0;
12727         }
12728         if (blank) {
12729             fprintf(f, " ");
12730             linelen++;
12731         }
12732         fprintf(f, "%s", numtext);
12733         linelen += numlen;
12734
12735         /* Get move */
12736         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
12737         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
12738
12739         /* Print move */
12740         blank = linelen > 0 && movelen > 0;
12741         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12742             fprintf(f, "\n");
12743             linelen = 0;
12744             blank = 0;
12745         }
12746         if (blank) {
12747             fprintf(f, " ");
12748             linelen++;
12749         }
12750         fprintf(f, "%s", move_buffer);
12751         linelen += movelen;
12752
12753         /* [AS] Add PV info if present */
12754         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
12755             /* [HGM] add time */
12756             char buf[MSG_SIZ]; int seconds;
12757
12758             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
12759
12760             if( seconds <= 0)
12761               buf[0] = 0;
12762             else
12763               if( seconds < 30 )
12764                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
12765               else
12766                 {
12767                   seconds = (seconds + 4)/10; // round to full seconds
12768                   if( seconds < 60 )
12769                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
12770                   else
12771                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
12772                 }
12773
12774             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
12775                       pvInfoList[i].score >= 0 ? "+" : "",
12776                       pvInfoList[i].score / 100.0,
12777                       pvInfoList[i].depth,
12778                       buf );
12779
12780             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
12781
12782             /* Print score/depth */
12783             blank = linelen > 0 && movelen > 0;
12784             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12785                 fprintf(f, "\n");
12786                 linelen = 0;
12787                 blank = 0;
12788             }
12789             if (blank) {
12790                 fprintf(f, " ");
12791                 linelen++;
12792             }
12793             fprintf(f, "%s", move_buffer);
12794             linelen += movelen;
12795         }
12796
12797         i++;
12798     }
12799
12800     /* Start a new line */
12801     if (linelen > 0) fprintf(f, "\n");
12802
12803     /* Print comments after last move */
12804     if (commentList[i] != NULL) {
12805         fprintf(f, "%s\n", commentList[i]);
12806     }
12807
12808     /* Print result */
12809     if (gameInfo.resultDetails != NULL &&
12810         gameInfo.resultDetails[0] != NULLCHAR) {
12811         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
12812                 PGNResult(gameInfo.result));
12813     } else {
12814         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12815     }
12816
12817     fclose(f);
12818     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12819     return TRUE;
12820 }
12821
12822 /* Save game in old style and close the file */
12823 int
12824 SaveGameOldStyle (FILE *f)
12825 {
12826     int i, offset;
12827     time_t tm;
12828
12829     tm = time((time_t *) NULL);
12830
12831     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
12832     PrintOpponents(f);
12833
12834     if (backwardMostMove > 0 || startedFromSetupPosition) {
12835         fprintf(f, "\n[--------------\n");
12836         PrintPosition(f, backwardMostMove);
12837         fprintf(f, "--------------]\n");
12838     } else {
12839         fprintf(f, "\n");
12840     }
12841
12842     i = backwardMostMove;
12843     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12844
12845     while (i < forwardMostMove) {
12846         if (commentList[i] != NULL) {
12847             fprintf(f, "[%s]\n", commentList[i]);
12848         }
12849
12850         if ((i % 2) == 1) {
12851             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
12852             i++;
12853         } else {
12854             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
12855             i++;
12856             if (commentList[i] != NULL) {
12857                 fprintf(f, "\n");
12858                 continue;
12859             }
12860             if (i >= forwardMostMove) {
12861                 fprintf(f, "\n");
12862                 break;
12863             }
12864             fprintf(f, "%s\n", parseList[i]);
12865             i++;
12866         }
12867     }
12868
12869     if (commentList[i] != NULL) {
12870         fprintf(f, "[%s]\n", commentList[i]);
12871     }
12872
12873     /* This isn't really the old style, but it's close enough */
12874     if (gameInfo.resultDetails != NULL &&
12875         gameInfo.resultDetails[0] != NULLCHAR) {
12876         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
12877                 gameInfo.resultDetails);
12878     } else {
12879         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12880     }
12881
12882     fclose(f);
12883     return TRUE;
12884 }
12885
12886 /* Save the current game to open file f and close the file */
12887 int
12888 SaveGame (FILE *f, int dummy, char *dummy2)
12889 {
12890     if (gameMode == EditPosition) EditPositionDone(TRUE);
12891     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12892     if (appData.oldSaveStyle)
12893       return SaveGameOldStyle(f);
12894     else
12895       return SaveGamePGN(f);
12896 }
12897
12898 /* Save the current position to the given file */
12899 int
12900 SavePositionToFile (char *filename)
12901 {
12902     FILE *f;
12903     char buf[MSG_SIZ];
12904
12905     if (strcmp(filename, "-") == 0) {
12906         return SavePosition(stdout, 0, NULL);
12907     } else {
12908         f = fopen(filename, "a");
12909         if (f == NULL) {
12910             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12911             DisplayError(buf, errno);
12912             return FALSE;
12913         } else {
12914             safeStrCpy(buf, lastMsg, MSG_SIZ);
12915             DisplayMessage(_("Waiting for access to save file"), "");
12916             flock(fileno(f), LOCK_EX); // [HGM] lock
12917             DisplayMessage(_("Saving position"), "");
12918             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
12919             SavePosition(f, 0, NULL);
12920             DisplayMessage(buf, "");
12921             return TRUE;
12922         }
12923     }
12924 }
12925
12926 /* Save the current position to the given open file and close the file */
12927 int
12928 SavePosition (FILE *f, int dummy, char *dummy2)
12929 {
12930     time_t tm;
12931     char *fen;
12932
12933     if (gameMode == EditPosition) EditPositionDone(TRUE);
12934     if (appData.oldSaveStyle) {
12935         tm = time((time_t *) NULL);
12936
12937         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
12938         PrintOpponents(f);
12939         fprintf(f, "[--------------\n");
12940         PrintPosition(f, currentMove);
12941         fprintf(f, "--------------]\n");
12942     } else {
12943         fen = PositionToFEN(currentMove, NULL);
12944         fprintf(f, "%s\n", fen);
12945         free(fen);
12946     }
12947     fclose(f);
12948     return TRUE;
12949 }
12950
12951 void
12952 ReloadCmailMsgEvent (int unregister)
12953 {
12954 #if !WIN32
12955     static char *inFilename = NULL;
12956     static char *outFilename;
12957     int i;
12958     struct stat inbuf, outbuf;
12959     int status;
12960
12961     /* Any registered moves are unregistered if unregister is set, */
12962     /* i.e. invoked by the signal handler */
12963     if (unregister) {
12964         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12965             cmailMoveRegistered[i] = FALSE;
12966             if (cmailCommentList[i] != NULL) {
12967                 free(cmailCommentList[i]);
12968                 cmailCommentList[i] = NULL;
12969             }
12970         }
12971         nCmailMovesRegistered = 0;
12972     }
12973
12974     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12975         cmailResult[i] = CMAIL_NOT_RESULT;
12976     }
12977     nCmailResults = 0;
12978
12979     if (inFilename == NULL) {
12980         /* Because the filenames are static they only get malloced once  */
12981         /* and they never get freed                                      */
12982         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
12983         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
12984
12985         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
12986         sprintf(outFilename, "%s.out", appData.cmailGameName);
12987     }
12988
12989     status = stat(outFilename, &outbuf);
12990     if (status < 0) {
12991         cmailMailedMove = FALSE;
12992     } else {
12993         status = stat(inFilename, &inbuf);
12994         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
12995     }
12996
12997     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
12998        counts the games, notes how each one terminated, etc.
12999
13000        It would be nice to remove this kludge and instead gather all
13001        the information while building the game list.  (And to keep it
13002        in the game list nodes instead of having a bunch of fixed-size
13003        parallel arrays.)  Note this will require getting each game's
13004        termination from the PGN tags, as the game list builder does
13005        not process the game moves.  --mann
13006        */
13007     cmailMsgLoaded = TRUE;
13008     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
13009
13010     /* Load first game in the file or popup game menu */
13011     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
13012
13013 #endif /* !WIN32 */
13014     return;
13015 }
13016
13017 int
13018 RegisterMove ()
13019 {
13020     FILE *f;
13021     char string[MSG_SIZ];
13022
13023     if (   cmailMailedMove
13024         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
13025         return TRUE;            /* Allow free viewing  */
13026     }
13027
13028     /* Unregister move to ensure that we don't leave RegisterMove        */
13029     /* with the move registered when the conditions for registering no   */
13030     /* longer hold                                                       */
13031     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
13032         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
13033         nCmailMovesRegistered --;
13034
13035         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
13036           {
13037               free(cmailCommentList[lastLoadGameNumber - 1]);
13038               cmailCommentList[lastLoadGameNumber - 1] = NULL;
13039           }
13040     }
13041
13042     if (cmailOldMove == -1) {
13043         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
13044         return FALSE;
13045     }
13046
13047     if (currentMove > cmailOldMove + 1) {
13048         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
13049         return FALSE;
13050     }
13051
13052     if (currentMove < cmailOldMove) {
13053         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
13054         return FALSE;
13055     }
13056
13057     if (forwardMostMove > currentMove) {
13058         /* Silently truncate extra moves */
13059         TruncateGame();
13060     }
13061
13062     if (   (currentMove == cmailOldMove + 1)
13063         || (   (currentMove == cmailOldMove)
13064             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
13065                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
13066         if (gameInfo.result != GameUnfinished) {
13067             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
13068         }
13069
13070         if (commentList[currentMove] != NULL) {
13071             cmailCommentList[lastLoadGameNumber - 1]
13072               = StrSave(commentList[currentMove]);
13073         }
13074         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
13075
13076         if (appData.debugMode)
13077           fprintf(debugFP, "Saving %s for game %d\n",
13078                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
13079
13080         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
13081
13082         f = fopen(string, "w");
13083         if (appData.oldSaveStyle) {
13084             SaveGameOldStyle(f); /* also closes the file */
13085
13086             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
13087             f = fopen(string, "w");
13088             SavePosition(f, 0, NULL); /* also closes the file */
13089         } else {
13090             fprintf(f, "{--------------\n");
13091             PrintPosition(f, currentMove);
13092             fprintf(f, "--------------}\n\n");
13093
13094             SaveGame(f, 0, NULL); /* also closes the file*/
13095         }
13096
13097         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
13098         nCmailMovesRegistered ++;
13099     } else if (nCmailGames == 1) {
13100         DisplayError(_("You have not made a move yet"), 0);
13101         return FALSE;
13102     }
13103
13104     return TRUE;
13105 }
13106
13107 void
13108 MailMoveEvent ()
13109 {
13110 #if !WIN32
13111     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
13112     FILE *commandOutput;
13113     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
13114     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
13115     int nBuffers;
13116     int i;
13117     int archived;
13118     char *arcDir;
13119
13120     if (! cmailMsgLoaded) {
13121         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
13122         return;
13123     }
13124
13125     if (nCmailGames == nCmailResults) {
13126         DisplayError(_("No unfinished games"), 0);
13127         return;
13128     }
13129
13130 #if CMAIL_PROHIBIT_REMAIL
13131     if (cmailMailedMove) {
13132       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);
13133         DisplayError(msg, 0);
13134         return;
13135     }
13136 #endif
13137
13138     if (! (cmailMailedMove || RegisterMove())) return;
13139
13140     if (   cmailMailedMove
13141         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13142       snprintf(string, MSG_SIZ, partCommandString,
13143                appData.debugMode ? " -v" : "", appData.cmailGameName);
13144         commandOutput = popen(string, "r");
13145
13146         if (commandOutput == NULL) {
13147             DisplayError(_("Failed to invoke cmail"), 0);
13148         } else {
13149             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13150                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13151             }
13152             if (nBuffers > 1) {
13153                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13154                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13155                 nBytes = MSG_SIZ - 1;
13156             } else {
13157                 (void) memcpy(msg, buffer, nBytes);
13158             }
13159             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13160
13161             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13162                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
13163
13164                 archived = TRUE;
13165                 for (i = 0; i < nCmailGames; i ++) {
13166                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
13167                         archived = FALSE;
13168                     }
13169                 }
13170                 if (   archived
13171                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13172                         != NULL)) {
13173                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13174                            arcDir,
13175                            appData.cmailGameName,
13176                            gameInfo.date);
13177                     LoadGameFromFile(buffer, 1, buffer, FALSE);
13178                     cmailMsgLoaded = FALSE;
13179                 }
13180             }
13181
13182             DisplayInformation(msg);
13183             pclose(commandOutput);
13184         }
13185     } else {
13186         if ((*cmailMsg) != '\0') {
13187             DisplayInformation(cmailMsg);
13188         }
13189     }
13190
13191     return;
13192 #endif /* !WIN32 */
13193 }
13194
13195 char *
13196 CmailMsg ()
13197 {
13198 #if WIN32
13199     return NULL;
13200 #else
13201     int  prependComma = 0;
13202     char number[5];
13203     char string[MSG_SIZ];       /* Space for game-list */
13204     int  i;
13205
13206     if (!cmailMsgLoaded) return "";
13207
13208     if (cmailMailedMove) {
13209       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13210     } else {
13211         /* Create a list of games left */
13212       snprintf(string, MSG_SIZ, "[");
13213         for (i = 0; i < nCmailGames; i ++) {
13214             if (! (   cmailMoveRegistered[i]
13215                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13216                 if (prependComma) {
13217                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13218                 } else {
13219                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13220                     prependComma = 1;
13221                 }
13222
13223                 strcat(string, number);
13224             }
13225         }
13226         strcat(string, "]");
13227
13228         if (nCmailMovesRegistered + nCmailResults == 0) {
13229             switch (nCmailGames) {
13230               case 1:
13231                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13232                 break;
13233
13234               case 2:
13235                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13236                 break;
13237
13238               default:
13239                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13240                          nCmailGames);
13241                 break;
13242             }
13243         } else {
13244             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13245               case 1:
13246                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13247                          string);
13248                 break;
13249
13250               case 0:
13251                 if (nCmailResults == nCmailGames) {
13252                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13253                 } else {
13254                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13255                 }
13256                 break;
13257
13258               default:
13259                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13260                          string);
13261             }
13262         }
13263     }
13264     return cmailMsg;
13265 #endif /* WIN32 */
13266 }
13267
13268 void
13269 ResetGameEvent ()
13270 {
13271     if (gameMode == Training)
13272       SetTrainingModeOff();
13273
13274     Reset(TRUE, TRUE);
13275     cmailMsgLoaded = FALSE;
13276     if (appData.icsActive) {
13277       SendToICS(ics_prefix);
13278       SendToICS("refresh\n");
13279     }
13280 }
13281
13282 void
13283 ExitEvent (int status)
13284 {
13285     exiting++;
13286     if (exiting > 2) {
13287       /* Give up on clean exit */
13288       exit(status);
13289     }
13290     if (exiting > 1) {
13291       /* Keep trying for clean exit */
13292       return;
13293     }
13294
13295     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
13296
13297     if (telnetISR != NULL) {
13298       RemoveInputSource(telnetISR);
13299     }
13300     if (icsPR != NoProc) {
13301       DestroyChildProcess(icsPR, TRUE);
13302     }
13303
13304     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
13305     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
13306
13307     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
13308     /* make sure this other one finishes before killing it!                  */
13309     if(endingGame) { int count = 0;
13310         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
13311         while(endingGame && count++ < 10) DoSleep(1);
13312         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
13313     }
13314
13315     /* Kill off chess programs */
13316     if (first.pr != NoProc) {
13317         ExitAnalyzeMode();
13318
13319         DoSleep( appData.delayBeforeQuit );
13320         SendToProgram("quit\n", &first);
13321         DoSleep( appData.delayAfterQuit );
13322         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
13323     }
13324     if (second.pr != NoProc) {
13325         DoSleep( appData.delayBeforeQuit );
13326         SendToProgram("quit\n", &second);
13327         DoSleep( appData.delayAfterQuit );
13328         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
13329     }
13330     if (first.isr != NULL) {
13331         RemoveInputSource(first.isr);
13332     }
13333     if (second.isr != NULL) {
13334         RemoveInputSource(second.isr);
13335     }
13336
13337     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13338     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13339
13340     ShutDownFrontEnd();
13341     exit(status);
13342 }
13343
13344 void
13345 PauseEvent ()
13346 {
13347     if (appData.debugMode)
13348         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13349     if (pausing) {
13350         pausing = FALSE;
13351         ModeHighlight();
13352         if (gameMode == MachinePlaysWhite ||
13353             gameMode == MachinePlaysBlack) {
13354             StartClocks();
13355         } else {
13356             DisplayBothClocks();
13357         }
13358         if (gameMode == PlayFromGameFile) {
13359             if (appData.timeDelay >= 0)
13360                 AutoPlayGameLoop();
13361         } else if (gameMode == IcsExamining && pauseExamInvalid) {
13362             Reset(FALSE, TRUE);
13363             SendToICS(ics_prefix);
13364             SendToICS("refresh\n");
13365         } else if (currentMove < forwardMostMove) {
13366             ForwardInner(forwardMostMove);
13367         }
13368         pauseExamInvalid = FALSE;
13369     } else {
13370         switch (gameMode) {
13371           default:
13372             return;
13373           case IcsExamining:
13374             pauseExamForwardMostMove = forwardMostMove;
13375             pauseExamInvalid = FALSE;
13376             /* fall through */
13377           case IcsObserving:
13378           case IcsPlayingWhite:
13379           case IcsPlayingBlack:
13380             pausing = TRUE;
13381             ModeHighlight();
13382             return;
13383           case PlayFromGameFile:
13384             (void) StopLoadGameTimer();
13385             pausing = TRUE;
13386             ModeHighlight();
13387             break;
13388           case BeginningOfGame:
13389             if (appData.icsActive) return;
13390             /* else fall through */
13391           case MachinePlaysWhite:
13392           case MachinePlaysBlack:
13393           case TwoMachinesPlay:
13394             if (forwardMostMove == 0)
13395               return;           /* don't pause if no one has moved */
13396             if ((gameMode == MachinePlaysWhite &&
13397                  !WhiteOnMove(forwardMostMove)) ||
13398                 (gameMode == MachinePlaysBlack &&
13399                  WhiteOnMove(forwardMostMove))) {
13400                 StopClocks();
13401             }
13402           case AnalyzeMode:
13403             pausing = TRUE;
13404             ModeHighlight();
13405             break;
13406         }
13407     }
13408 }
13409
13410 void
13411 EditCommentEvent ()
13412 {
13413     char title[MSG_SIZ];
13414
13415     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13416       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13417     } else {
13418       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13419                WhiteOnMove(currentMove - 1) ? " " : ".. ",
13420                parseList[currentMove - 1]);
13421     }
13422
13423     EditCommentPopUp(currentMove, title, commentList[currentMove]);
13424 }
13425
13426
13427 void
13428 EditTagsEvent ()
13429 {
13430     char *tags = PGNTags(&gameInfo);
13431     bookUp = FALSE;
13432     EditTagsPopUp(tags, NULL);
13433     free(tags);
13434 }
13435
13436 void
13437 ToggleSecond ()
13438 {
13439   if(second.analyzing) {
13440     SendToProgram("exit\n", &second);
13441     second.analyzing = FALSE;
13442   } else {
13443     if (second.pr == NoProc) StartChessProgram(&second);
13444     InitChessProgram(&second, FALSE);
13445     FeedMovesToProgram(&second, currentMove);
13446
13447     SendToProgram("analyze\n", &second);
13448     second.analyzing = TRUE;
13449   }
13450 }
13451
13452 void
13453 AnalyzeModeEvent ()
13454 {
13455     if (gameMode == AnalyzeMode) { ToggleSecond(); return; }
13456     if (appData.noChessProgram || gameMode == AnalyzeMode)
13457       return;
13458
13459     if (gameMode != AnalyzeFile) {
13460         if (!appData.icsEngineAnalyze) {
13461                EditGameEvent();
13462                if (gameMode != EditGame) return;
13463         }
13464         ResurrectChessProgram();
13465         SendToProgram("analyze\n", &first);
13466         first.analyzing = TRUE;
13467         /*first.maybeThinking = TRUE;*/
13468         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13469         EngineOutputPopUp();
13470     }
13471     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
13472     pausing = FALSE;
13473     ModeHighlight();
13474     SetGameInfo();
13475
13476     StartAnalysisClock();
13477     GetTimeMark(&lastNodeCountTime);
13478     lastNodeCount = 0;
13479 }
13480
13481 void
13482 AnalyzeFileEvent ()
13483 {
13484     if (appData.noChessProgram || gameMode == AnalyzeFile)
13485       return;
13486
13487     if (gameMode != AnalyzeMode) {
13488         EditGameEvent();
13489         if (gameMode != EditGame) return;
13490         ResurrectChessProgram();
13491         SendToProgram("analyze\n", &first);
13492         first.analyzing = TRUE;
13493         /*first.maybeThinking = TRUE;*/
13494         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13495         EngineOutputPopUp();
13496     }
13497     gameMode = AnalyzeFile;
13498     pausing = FALSE;
13499     ModeHighlight();
13500     SetGameInfo();
13501
13502     StartAnalysisClock();
13503     GetTimeMark(&lastNodeCountTime);
13504     lastNodeCount = 0;
13505     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
13506 }
13507
13508 void
13509 MachineWhiteEvent ()
13510 {
13511     char buf[MSG_SIZ];
13512     char *bookHit = NULL;
13513
13514     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
13515       return;
13516
13517
13518     if (gameMode == PlayFromGameFile ||
13519         gameMode == TwoMachinesPlay  ||
13520         gameMode == Training         ||
13521         gameMode == AnalyzeMode      ||
13522         gameMode == EndOfGame)
13523         EditGameEvent();
13524
13525     if (gameMode == EditPosition)
13526         EditPositionDone(TRUE);
13527
13528     if (!WhiteOnMove(currentMove)) {
13529         DisplayError(_("It is not White's turn"), 0);
13530         return;
13531     }
13532
13533     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13534       ExitAnalyzeMode();
13535
13536     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13537         gameMode == AnalyzeFile)
13538         TruncateGame();
13539
13540     ResurrectChessProgram();    /* in case it isn't running */
13541     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
13542         gameMode = MachinePlaysWhite;
13543         ResetClocks();
13544     } else
13545     gameMode = MachinePlaysWhite;
13546     pausing = FALSE;
13547     ModeHighlight();
13548     SetGameInfo();
13549     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13550     DisplayTitle(buf);
13551     if (first.sendName) {
13552       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
13553       SendToProgram(buf, &first);
13554     }
13555     if (first.sendTime) {
13556       if (first.useColors) {
13557         SendToProgram("black\n", &first); /*gnu kludge*/
13558       }
13559       SendTimeRemaining(&first, TRUE);
13560     }
13561     if (first.useColors) {
13562       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
13563     }
13564     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13565     SetMachineThinkingEnables();
13566     first.maybeThinking = TRUE;
13567     StartClocks();
13568     firstMove = FALSE;
13569
13570     if (appData.autoFlipView && !flipView) {
13571       flipView = !flipView;
13572       DrawPosition(FALSE, NULL);
13573       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13574     }
13575
13576     if(bookHit) { // [HGM] book: simulate book reply
13577         static char bookMove[MSG_SIZ]; // a bit generous?
13578
13579         programStats.nodes = programStats.depth = programStats.time =
13580         programStats.score = programStats.got_only_move = 0;
13581         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13582
13583         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13584         strcat(bookMove, bookHit);
13585         HandleMachineMove(bookMove, &first);
13586     }
13587 }
13588
13589 void
13590 MachineBlackEvent ()
13591 {
13592   char buf[MSG_SIZ];
13593   char *bookHit = NULL;
13594
13595     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
13596         return;
13597
13598
13599     if (gameMode == PlayFromGameFile ||
13600         gameMode == TwoMachinesPlay  ||
13601         gameMode == Training         ||
13602         gameMode == AnalyzeMode      ||
13603         gameMode == EndOfGame)
13604         EditGameEvent();
13605
13606     if (gameMode == EditPosition)
13607         EditPositionDone(TRUE);
13608
13609     if (WhiteOnMove(currentMove)) {
13610         DisplayError(_("It is not Black's turn"), 0);
13611         return;
13612     }
13613
13614     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13615       ExitAnalyzeMode();
13616
13617     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13618         gameMode == AnalyzeFile)
13619         TruncateGame();
13620
13621     ResurrectChessProgram();    /* in case it isn't running */
13622     gameMode = MachinePlaysBlack;
13623     pausing = FALSE;
13624     ModeHighlight();
13625     SetGameInfo();
13626     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13627     DisplayTitle(buf);
13628     if (first.sendName) {
13629       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
13630       SendToProgram(buf, &first);
13631     }
13632     if (first.sendTime) {
13633       if (first.useColors) {
13634         SendToProgram("white\n", &first); /*gnu kludge*/
13635       }
13636       SendTimeRemaining(&first, FALSE);
13637     }
13638     if (first.useColors) {
13639       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
13640     }
13641     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13642     SetMachineThinkingEnables();
13643     first.maybeThinking = TRUE;
13644     StartClocks();
13645
13646     if (appData.autoFlipView && flipView) {
13647       flipView = !flipView;
13648       DrawPosition(FALSE, NULL);
13649       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13650     }
13651     if(bookHit) { // [HGM] book: simulate book reply
13652         static char bookMove[MSG_SIZ]; // a bit generous?
13653
13654         programStats.nodes = programStats.depth = programStats.time =
13655         programStats.score = programStats.got_only_move = 0;
13656         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13657
13658         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13659         strcat(bookMove, bookHit);
13660         HandleMachineMove(bookMove, &first);
13661     }
13662 }
13663
13664
13665 void
13666 DisplayTwoMachinesTitle ()
13667 {
13668     char buf[MSG_SIZ];
13669     if (appData.matchGames > 0) {
13670         if(appData.tourneyFile[0]) {
13671           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
13672                    gameInfo.white, _("vs."), gameInfo.black,
13673                    nextGame+1, appData.matchGames+1,
13674                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
13675         } else 
13676         if (first.twoMachinesColor[0] == 'w') {
13677           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13678                    gameInfo.white, _("vs."),  gameInfo.black,
13679                    first.matchWins, second.matchWins,
13680                    matchGame - 1 - (first.matchWins + second.matchWins));
13681         } else {
13682           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13683                    gameInfo.white, _("vs."), gameInfo.black,
13684                    second.matchWins, first.matchWins,
13685                    matchGame - 1 - (first.matchWins + second.matchWins));
13686         }
13687     } else {
13688       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13689     }
13690     DisplayTitle(buf);
13691 }
13692
13693 void
13694 SettingsMenuIfReady ()
13695 {
13696   if (second.lastPing != second.lastPong) {
13697     DisplayMessage("", _("Waiting for second chess program"));
13698     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
13699     return;
13700   }
13701   ThawUI();
13702   DisplayMessage("", "");
13703   SettingsPopUp(&second);
13704 }
13705
13706 int
13707 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
13708 {
13709     char buf[MSG_SIZ];
13710     if (cps->pr == NoProc) {
13711         StartChessProgram(cps);
13712         if (cps->protocolVersion == 1) {
13713           retry();
13714         } else {
13715           /* kludge: allow timeout for initial "feature" command */
13716           FreezeUI();
13717           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
13718           DisplayMessage("", buf);
13719           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
13720         }
13721         return 1;
13722     }
13723     return 0;
13724 }
13725
13726 void
13727 TwoMachinesEvent P((void))
13728 {
13729     int i;
13730     char buf[MSG_SIZ];
13731     ChessProgramState *onmove;
13732     char *bookHit = NULL;
13733     static int stalling = 0;
13734     TimeMark now;
13735     long wait;
13736
13737     if (appData.noChessProgram) return;
13738
13739     switch (gameMode) {
13740       case TwoMachinesPlay:
13741         return;
13742       case MachinePlaysWhite:
13743       case MachinePlaysBlack:
13744         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13745             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13746             return;
13747         }
13748         /* fall through */
13749       case BeginningOfGame:
13750       case PlayFromGameFile:
13751       case EndOfGame:
13752         EditGameEvent();
13753         if (gameMode != EditGame) return;
13754         break;
13755       case EditPosition:
13756         EditPositionDone(TRUE);
13757         break;
13758       case AnalyzeMode:
13759       case AnalyzeFile:
13760         ExitAnalyzeMode();
13761         break;
13762       case EditGame:
13763       default:
13764         break;
13765     }
13766
13767 //    forwardMostMove = currentMove;
13768     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
13769
13770     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
13771
13772     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
13773     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
13774       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13775       return;
13776     }
13777
13778     if(second.protocolVersion >= 2 && !strstr(second.variants, VariantName(gameInfo.variant))) {
13779         DisplayError("second engine does not play this", 0);
13780         return;
13781     }
13782
13783     if(!stalling) {
13784       InitChessProgram(&second, FALSE); // unbalances ping of second engine
13785       SendToProgram("force\n", &second);
13786       stalling = 1;
13787       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13788       return;
13789     }
13790     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
13791     if(appData.matchPause>10000 || appData.matchPause<10)
13792                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
13793     wait = SubtractTimeMarks(&now, &pauseStart);
13794     if(wait < appData.matchPause) {
13795         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
13796         return;
13797     }
13798     // we are now committed to starting the game
13799     stalling = 0;
13800     DisplayMessage("", "");
13801     if (startedFromSetupPosition) {
13802         SendBoard(&second, backwardMostMove);
13803     if (appData.debugMode) {
13804         fprintf(debugFP, "Two Machines\n");
13805     }
13806     }
13807     for (i = backwardMostMove; i < forwardMostMove; i++) {
13808         SendMoveToProgram(i, &second);
13809     }
13810
13811     gameMode = TwoMachinesPlay;
13812     pausing = FALSE;
13813     ModeHighlight(); // [HGM] logo: this triggers display update of logos
13814     SetGameInfo();
13815     DisplayTwoMachinesTitle();
13816     firstMove = TRUE;
13817     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
13818         onmove = &first;
13819     } else {
13820         onmove = &second;
13821     }
13822     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
13823     SendToProgram(first.computerString, &first);
13824     if (first.sendName) {
13825       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
13826       SendToProgram(buf, &first);
13827     }
13828     SendToProgram(second.computerString, &second);
13829     if (second.sendName) {
13830       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
13831       SendToProgram(buf, &second);
13832     }
13833
13834     ResetClocks();
13835     if (!first.sendTime || !second.sendTime) {
13836         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13837         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13838     }
13839     if (onmove->sendTime) {
13840       if (onmove->useColors) {
13841         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
13842       }
13843       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
13844     }
13845     if (onmove->useColors) {
13846       SendToProgram(onmove->twoMachinesColor, onmove);
13847     }
13848     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
13849 //    SendToProgram("go\n", onmove);
13850     onmove->maybeThinking = TRUE;
13851     SetMachineThinkingEnables();
13852
13853     StartClocks();
13854
13855     if(bookHit) { // [HGM] book: simulate book reply
13856         static char bookMove[MSG_SIZ]; // a bit generous?
13857
13858         programStats.nodes = programStats.depth = programStats.time =
13859         programStats.score = programStats.got_only_move = 0;
13860         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13861
13862         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13863         strcat(bookMove, bookHit);
13864         savedMessage = bookMove; // args for deferred call
13865         savedState = onmove;
13866         ScheduleDelayedEvent(DeferredBookMove, 1);
13867     }
13868 }
13869
13870 void
13871 TrainingEvent ()
13872 {
13873     if (gameMode == Training) {
13874       SetTrainingModeOff();
13875       gameMode = PlayFromGameFile;
13876       DisplayMessage("", _("Training mode off"));
13877     } else {
13878       gameMode = Training;
13879       animateTraining = appData.animate;
13880
13881       /* make sure we are not already at the end of the game */
13882       if (currentMove < forwardMostMove) {
13883         SetTrainingModeOn();
13884         DisplayMessage("", _("Training mode on"));
13885       } else {
13886         gameMode = PlayFromGameFile;
13887         DisplayError(_("Already at end of game"), 0);
13888       }
13889     }
13890     ModeHighlight();
13891 }
13892
13893 void
13894 IcsClientEvent ()
13895 {
13896     if (!appData.icsActive) return;
13897     switch (gameMode) {
13898       case IcsPlayingWhite:
13899       case IcsPlayingBlack:
13900       case IcsObserving:
13901       case IcsIdle:
13902       case BeginningOfGame:
13903       case IcsExamining:
13904         return;
13905
13906       case EditGame:
13907         break;
13908
13909       case EditPosition:
13910         EditPositionDone(TRUE);
13911         break;
13912
13913       case AnalyzeMode:
13914       case AnalyzeFile:
13915         ExitAnalyzeMode();
13916         break;
13917
13918       default:
13919         EditGameEvent();
13920         break;
13921     }
13922
13923     gameMode = IcsIdle;
13924     ModeHighlight();
13925     return;
13926 }
13927
13928 void
13929 EditGameEvent ()
13930 {
13931     int i;
13932
13933     switch (gameMode) {
13934       case Training:
13935         SetTrainingModeOff();
13936         break;
13937       case MachinePlaysWhite:
13938       case MachinePlaysBlack:
13939       case BeginningOfGame:
13940         SendToProgram("force\n", &first);
13941         SetUserThinkingEnables();
13942         break;
13943       case PlayFromGameFile:
13944         (void) StopLoadGameTimer();
13945         if (gameFileFP != NULL) {
13946             gameFileFP = NULL;
13947         }
13948         break;
13949       case EditPosition:
13950         EditPositionDone(TRUE);
13951         break;
13952       case AnalyzeMode:
13953       case AnalyzeFile:
13954         ExitAnalyzeMode();
13955         SendToProgram("force\n", &first);
13956         break;
13957       case TwoMachinesPlay:
13958         GameEnds(EndOfFile, NULL, GE_PLAYER);
13959         ResurrectChessProgram();
13960         SetUserThinkingEnables();
13961         break;
13962       case EndOfGame:
13963         ResurrectChessProgram();
13964         break;
13965       case IcsPlayingBlack:
13966       case IcsPlayingWhite:
13967         DisplayError(_("Warning: You are still playing a game"), 0);
13968         break;
13969       case IcsObserving:
13970         DisplayError(_("Warning: You are still observing a game"), 0);
13971         break;
13972       case IcsExamining:
13973         DisplayError(_("Warning: You are still examining a game"), 0);
13974         break;
13975       case IcsIdle:
13976         break;
13977       case EditGame:
13978       default:
13979         return;
13980     }
13981
13982     pausing = FALSE;
13983     StopClocks();
13984     first.offeredDraw = second.offeredDraw = 0;
13985
13986     if (gameMode == PlayFromGameFile) {
13987         whiteTimeRemaining = timeRemaining[0][currentMove];
13988         blackTimeRemaining = timeRemaining[1][currentMove];
13989         DisplayTitle("");
13990     }
13991
13992     if (gameMode == MachinePlaysWhite ||
13993         gameMode == MachinePlaysBlack ||
13994         gameMode == TwoMachinesPlay ||
13995         gameMode == EndOfGame) {
13996         i = forwardMostMove;
13997         while (i > currentMove) {
13998             SendToProgram("undo\n", &first);
13999             i--;
14000         }
14001         if(!adjustedClock) {
14002         whiteTimeRemaining = timeRemaining[0][currentMove];
14003         blackTimeRemaining = timeRemaining[1][currentMove];
14004         DisplayBothClocks();
14005         }
14006         if (whiteFlag || blackFlag) {
14007             whiteFlag = blackFlag = 0;
14008         }
14009         DisplayTitle("");
14010     }
14011
14012     gameMode = EditGame;
14013     ModeHighlight();
14014     SetGameInfo();
14015 }
14016
14017
14018 void
14019 EditPositionEvent ()
14020 {
14021     if (gameMode == EditPosition) {
14022         EditGameEvent();
14023         return;
14024     }
14025
14026     EditGameEvent();
14027     if (gameMode != EditGame) return;
14028
14029     gameMode = EditPosition;
14030     ModeHighlight();
14031     SetGameInfo();
14032     if (currentMove > 0)
14033       CopyBoard(boards[0], boards[currentMove]);
14034
14035     blackPlaysFirst = !WhiteOnMove(currentMove);
14036     ResetClocks();
14037     currentMove = forwardMostMove = backwardMostMove = 0;
14038     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14039     DisplayMove(-1);
14040     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
14041 }
14042
14043 void
14044 ExitAnalyzeMode ()
14045 {
14046     /* [DM] icsEngineAnalyze - possible call from other functions */
14047     if (appData.icsEngineAnalyze) {
14048         appData.icsEngineAnalyze = FALSE;
14049
14050         DisplayMessage("",_("Close ICS engine analyze..."));
14051     }
14052     if (first.analysisSupport && first.analyzing) {
14053       SendToBoth("exit\n");
14054       first.analyzing = second.analyzing = FALSE;
14055     }
14056     thinkOutput[0] = NULLCHAR;
14057 }
14058
14059 void
14060 EditPositionDone (Boolean fakeRights)
14061 {
14062     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
14063
14064     startedFromSetupPosition = TRUE;
14065     InitChessProgram(&first, FALSE);
14066     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
14067       boards[0][EP_STATUS] = EP_NONE;
14068       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
14069       if(boards[0][0][BOARD_WIDTH>>1] == king) {
14070         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
14071         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
14072       } else boards[0][CASTLING][2] = NoRights;
14073       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
14074         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
14075         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
14076       } else boards[0][CASTLING][5] = NoRights;
14077       if(gameInfo.variant == VariantSChess) {
14078         int i;
14079         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
14080           boards[0][VIRGIN][i] = 0;
14081           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
14082           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
14083         }
14084       }
14085     }
14086     SendToProgram("force\n", &first);
14087     if (blackPlaysFirst) {
14088         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
14089         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
14090         currentMove = forwardMostMove = backwardMostMove = 1;
14091         CopyBoard(boards[1], boards[0]);
14092     } else {
14093         currentMove = forwardMostMove = backwardMostMove = 0;
14094     }
14095     SendBoard(&first, forwardMostMove);
14096     if (appData.debugMode) {
14097         fprintf(debugFP, "EditPosDone\n");
14098     }
14099     DisplayTitle("");
14100     DisplayMessage("", "");
14101     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14102     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14103     gameMode = EditGame;
14104     ModeHighlight();
14105     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14106     ClearHighlights(); /* [AS] */
14107 }
14108
14109 /* Pause for `ms' milliseconds */
14110 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14111 void
14112 TimeDelay (long ms)
14113 {
14114     TimeMark m1, m2;
14115
14116     GetTimeMark(&m1);
14117     do {
14118         GetTimeMark(&m2);
14119     } while (SubtractTimeMarks(&m2, &m1) < ms);
14120 }
14121
14122 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14123 void
14124 SendMultiLineToICS (char *buf)
14125 {
14126     char temp[MSG_SIZ+1], *p;
14127     int len;
14128
14129     len = strlen(buf);
14130     if (len > MSG_SIZ)
14131       len = MSG_SIZ;
14132
14133     strncpy(temp, buf, len);
14134     temp[len] = 0;
14135
14136     p = temp;
14137     while (*p) {
14138         if (*p == '\n' || *p == '\r')
14139           *p = ' ';
14140         ++p;
14141     }
14142
14143     strcat(temp, "\n");
14144     SendToICS(temp);
14145     SendToPlayer(temp, strlen(temp));
14146 }
14147
14148 void
14149 SetWhiteToPlayEvent ()
14150 {
14151     if (gameMode == EditPosition) {
14152         blackPlaysFirst = FALSE;
14153         DisplayBothClocks();    /* works because currentMove is 0 */
14154     } else if (gameMode == IcsExamining) {
14155         SendToICS(ics_prefix);
14156         SendToICS("tomove white\n");
14157     }
14158 }
14159
14160 void
14161 SetBlackToPlayEvent ()
14162 {
14163     if (gameMode == EditPosition) {
14164         blackPlaysFirst = TRUE;
14165         currentMove = 1;        /* kludge */
14166         DisplayBothClocks();
14167         currentMove = 0;
14168     } else if (gameMode == IcsExamining) {
14169         SendToICS(ics_prefix);
14170         SendToICS("tomove black\n");
14171     }
14172 }
14173
14174 void
14175 EditPositionMenuEvent (ChessSquare selection, int x, int y)
14176 {
14177     char buf[MSG_SIZ];
14178     ChessSquare piece = boards[0][y][x];
14179
14180     if (gameMode != EditPosition && gameMode != IcsExamining) return;
14181
14182     switch (selection) {
14183       case ClearBoard:
14184         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
14185             SendToICS(ics_prefix);
14186             SendToICS("bsetup clear\n");
14187         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
14188             SendToICS(ics_prefix);
14189             SendToICS("clearboard\n");
14190         } else {
14191             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
14192                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
14193                 for (y = 0; y < BOARD_HEIGHT; y++) {
14194                     if (gameMode == IcsExamining) {
14195                         if (boards[currentMove][y][x] != EmptySquare) {
14196                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
14197                                     AAA + x, ONE + y);
14198                             SendToICS(buf);
14199                         }
14200                     } else {
14201                         boards[0][y][x] = p;
14202                     }
14203                 }
14204             }
14205         }
14206         if (gameMode == EditPosition) {
14207             DrawPosition(FALSE, boards[0]);
14208         }
14209         break;
14210
14211       case WhitePlay:
14212         SetWhiteToPlayEvent();
14213         break;
14214
14215       case BlackPlay:
14216         SetBlackToPlayEvent();
14217         break;
14218
14219       case EmptySquare:
14220         if (gameMode == IcsExamining) {
14221             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14222             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
14223             SendToICS(buf);
14224         } else {
14225             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14226                 if(x == BOARD_LEFT-2) {
14227                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
14228                     boards[0][y][1] = 0;
14229                 } else
14230                 if(x == BOARD_RGHT+1) {
14231                     if(y >= gameInfo.holdingsSize) break;
14232                     boards[0][y][BOARD_WIDTH-2] = 0;
14233                 } else break;
14234             }
14235             boards[0][y][x] = EmptySquare;
14236             DrawPosition(FALSE, boards[0]);
14237         }
14238         break;
14239
14240       case PromotePiece:
14241         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
14242            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
14243             selection = (ChessSquare) (PROMOTED piece);
14244         } else if(piece == EmptySquare) selection = WhiteSilver;
14245         else selection = (ChessSquare)((int)piece - 1);
14246         goto defaultlabel;
14247
14248       case DemotePiece:
14249         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
14250            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
14251             selection = (ChessSquare) (DEMOTED piece);
14252         } else if(piece == EmptySquare) selection = BlackSilver;
14253         else selection = (ChessSquare)((int)piece + 1);
14254         goto defaultlabel;
14255
14256       case WhiteQueen:
14257       case BlackQueen:
14258         if(gameInfo.variant == VariantShatranj ||
14259            gameInfo.variant == VariantXiangqi  ||
14260            gameInfo.variant == VariantCourier  ||
14261            gameInfo.variant == VariantMakruk     )
14262             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
14263         goto defaultlabel;
14264
14265       case WhiteKing:
14266       case BlackKing:
14267         if(gameInfo.variant == VariantXiangqi)
14268             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
14269         if(gameInfo.variant == VariantKnightmate)
14270             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
14271       default:
14272         defaultlabel:
14273         if (gameMode == IcsExamining) {
14274             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14275             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
14276                      PieceToChar(selection), AAA + x, ONE + y);
14277             SendToICS(buf);
14278         } else {
14279             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14280                 int n;
14281                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
14282                     n = PieceToNumber(selection - BlackPawn);
14283                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
14284                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
14285                     boards[0][BOARD_HEIGHT-1-n][1]++;
14286                 } else
14287                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
14288                     n = PieceToNumber(selection);
14289                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
14290                     boards[0][n][BOARD_WIDTH-1] = selection;
14291                     boards[0][n][BOARD_WIDTH-2]++;
14292                 }
14293             } else
14294             boards[0][y][x] = selection;
14295             DrawPosition(TRUE, boards[0]);
14296             ClearHighlights();
14297             fromX = fromY = -1;
14298         }
14299         break;
14300     }
14301 }
14302
14303
14304 void
14305 DropMenuEvent (ChessSquare selection, int x, int y)
14306 {
14307     ChessMove moveType;
14308
14309     switch (gameMode) {
14310       case IcsPlayingWhite:
14311       case MachinePlaysBlack:
14312         if (!WhiteOnMove(currentMove)) {
14313             DisplayMoveError(_("It is Black's turn"));
14314             return;
14315         }
14316         moveType = WhiteDrop;
14317         break;
14318       case IcsPlayingBlack:
14319       case MachinePlaysWhite:
14320         if (WhiteOnMove(currentMove)) {
14321             DisplayMoveError(_("It is White's turn"));
14322             return;
14323         }
14324         moveType = BlackDrop;
14325         break;
14326       case EditGame:
14327         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
14328         break;
14329       default:
14330         return;
14331     }
14332
14333     if (moveType == BlackDrop && selection < BlackPawn) {
14334       selection = (ChessSquare) ((int) selection
14335                                  + (int) BlackPawn - (int) WhitePawn);
14336     }
14337     if (boards[currentMove][y][x] != EmptySquare) {
14338         DisplayMoveError(_("That square is occupied"));
14339         return;
14340     }
14341
14342     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
14343 }
14344
14345 void
14346 AcceptEvent ()
14347 {
14348     /* Accept a pending offer of any kind from opponent */
14349
14350     if (appData.icsActive) {
14351         SendToICS(ics_prefix);
14352         SendToICS("accept\n");
14353     } else if (cmailMsgLoaded) {
14354         if (currentMove == cmailOldMove &&
14355             commentList[cmailOldMove] != NULL &&
14356             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14357                    "Black offers a draw" : "White offers a draw")) {
14358             TruncateGame();
14359             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14360             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14361         } else {
14362             DisplayError(_("There is no pending offer on this move"), 0);
14363             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14364         }
14365     } else {
14366         /* Not used for offers from chess program */
14367     }
14368 }
14369
14370 void
14371 DeclineEvent ()
14372 {
14373     /* Decline a pending offer of any kind from opponent */
14374
14375     if (appData.icsActive) {
14376         SendToICS(ics_prefix);
14377         SendToICS("decline\n");
14378     } else if (cmailMsgLoaded) {
14379         if (currentMove == cmailOldMove &&
14380             commentList[cmailOldMove] != NULL &&
14381             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14382                    "Black offers a draw" : "White offers a draw")) {
14383 #ifdef NOTDEF
14384             AppendComment(cmailOldMove, "Draw declined", TRUE);
14385             DisplayComment(cmailOldMove - 1, "Draw declined");
14386 #endif /*NOTDEF*/
14387         } else {
14388             DisplayError(_("There is no pending offer on this move"), 0);
14389         }
14390     } else {
14391         /* Not used for offers from chess program */
14392     }
14393 }
14394
14395 void
14396 RematchEvent ()
14397 {
14398     /* Issue ICS rematch command */
14399     if (appData.icsActive) {
14400         SendToICS(ics_prefix);
14401         SendToICS("rematch\n");
14402     }
14403 }
14404
14405 void
14406 CallFlagEvent ()
14407 {
14408     /* Call your opponent's flag (claim a win on time) */
14409     if (appData.icsActive) {
14410         SendToICS(ics_prefix);
14411         SendToICS("flag\n");
14412     } else {
14413         switch (gameMode) {
14414           default:
14415             return;
14416           case MachinePlaysWhite:
14417             if (whiteFlag) {
14418                 if (blackFlag)
14419                   GameEnds(GameIsDrawn, "Both players ran out of time",
14420                            GE_PLAYER);
14421                 else
14422                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
14423             } else {
14424                 DisplayError(_("Your opponent is not out of time"), 0);
14425             }
14426             break;
14427           case MachinePlaysBlack:
14428             if (blackFlag) {
14429                 if (whiteFlag)
14430                   GameEnds(GameIsDrawn, "Both players ran out of time",
14431                            GE_PLAYER);
14432                 else
14433                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
14434             } else {
14435                 DisplayError(_("Your opponent is not out of time"), 0);
14436             }
14437             break;
14438         }
14439     }
14440 }
14441
14442 void
14443 ClockClick (int which)
14444 {       // [HGM] code moved to back-end from winboard.c
14445         if(which) { // black clock
14446           if (gameMode == EditPosition || gameMode == IcsExamining) {
14447             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14448             SetBlackToPlayEvent();
14449           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
14450           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
14451           } else if (shiftKey) {
14452             AdjustClock(which, -1);
14453           } else if (gameMode == IcsPlayingWhite ||
14454                      gameMode == MachinePlaysBlack) {
14455             CallFlagEvent();
14456           }
14457         } else { // white clock
14458           if (gameMode == EditPosition || gameMode == IcsExamining) {
14459             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14460             SetWhiteToPlayEvent();
14461           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
14462           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
14463           } else if (shiftKey) {
14464             AdjustClock(which, -1);
14465           } else if (gameMode == IcsPlayingBlack ||
14466                    gameMode == MachinePlaysWhite) {
14467             CallFlagEvent();
14468           }
14469         }
14470 }
14471
14472 void
14473 DrawEvent ()
14474 {
14475     /* Offer draw or accept pending draw offer from opponent */
14476
14477     if (appData.icsActive) {
14478         /* Note: tournament rules require draw offers to be
14479            made after you make your move but before you punch
14480            your clock.  Currently ICS doesn't let you do that;
14481            instead, you immediately punch your clock after making
14482            a move, but you can offer a draw at any time. */
14483
14484         SendToICS(ics_prefix);
14485         SendToICS("draw\n");
14486         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
14487     } else if (cmailMsgLoaded) {
14488         if (currentMove == cmailOldMove &&
14489             commentList[cmailOldMove] != NULL &&
14490             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14491                    "Black offers a draw" : "White offers a draw")) {
14492             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14493             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14494         } else if (currentMove == cmailOldMove + 1) {
14495             char *offer = WhiteOnMove(cmailOldMove) ?
14496               "White offers a draw" : "Black offers a draw";
14497             AppendComment(currentMove, offer, TRUE);
14498             DisplayComment(currentMove - 1, offer);
14499             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
14500         } else {
14501             DisplayError(_("You must make your move before offering a draw"), 0);
14502             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14503         }
14504     } else if (first.offeredDraw) {
14505         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
14506     } else {
14507         if (first.sendDrawOffers) {
14508             SendToProgram("draw\n", &first);
14509             userOfferedDraw = TRUE;
14510         }
14511     }
14512 }
14513
14514 void
14515 AdjournEvent ()
14516 {
14517     /* Offer Adjourn or accept pending Adjourn offer from opponent */
14518
14519     if (appData.icsActive) {
14520         SendToICS(ics_prefix);
14521         SendToICS("adjourn\n");
14522     } else {
14523         /* Currently GNU Chess doesn't offer or accept Adjourns */
14524     }
14525 }
14526
14527
14528 void
14529 AbortEvent ()
14530 {
14531     /* Offer Abort or accept pending Abort offer from opponent */
14532
14533     if (appData.icsActive) {
14534         SendToICS(ics_prefix);
14535         SendToICS("abort\n");
14536     } else {
14537         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
14538     }
14539 }
14540
14541 void
14542 ResignEvent ()
14543 {
14544     /* Resign.  You can do this even if it's not your turn. */
14545
14546     if (appData.icsActive) {
14547         SendToICS(ics_prefix);
14548         SendToICS("resign\n");
14549     } else {
14550         switch (gameMode) {
14551           case MachinePlaysWhite:
14552             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14553             break;
14554           case MachinePlaysBlack:
14555             GameEnds(BlackWins, "White resigns", GE_PLAYER);
14556             break;
14557           case EditGame:
14558             if (cmailMsgLoaded) {
14559                 TruncateGame();
14560                 if (WhiteOnMove(cmailOldMove)) {
14561                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
14562                 } else {
14563                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14564                 }
14565                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
14566             }
14567             break;
14568           default:
14569             break;
14570         }
14571     }
14572 }
14573
14574
14575 void
14576 StopObservingEvent ()
14577 {
14578     /* Stop observing current games */
14579     SendToICS(ics_prefix);
14580     SendToICS("unobserve\n");
14581 }
14582
14583 void
14584 StopExaminingEvent ()
14585 {
14586     /* Stop observing current game */
14587     SendToICS(ics_prefix);
14588     SendToICS("unexamine\n");
14589 }
14590
14591 void
14592 ForwardInner (int target)
14593 {
14594     int limit; int oldSeekGraphUp = seekGraphUp;
14595
14596     if (appData.debugMode)
14597         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
14598                 target, currentMove, forwardMostMove);
14599
14600     if (gameMode == EditPosition)
14601       return;
14602
14603     seekGraphUp = FALSE;
14604     MarkTargetSquares(1);
14605
14606     if (gameMode == PlayFromGameFile && !pausing)
14607       PauseEvent();
14608
14609     if (gameMode == IcsExamining && pausing)
14610       limit = pauseExamForwardMostMove;
14611     else
14612       limit = forwardMostMove;
14613
14614     if (target > limit) target = limit;
14615
14616     if (target > 0 && moveList[target - 1][0]) {
14617         int fromX, fromY, toX, toY;
14618         toX = moveList[target - 1][2] - AAA;
14619         toY = moveList[target - 1][3] - ONE;
14620         if (moveList[target - 1][1] == '@') {
14621             if (appData.highlightLastMove) {
14622                 SetHighlights(-1, -1, toX, toY);
14623             }
14624         } else {
14625             fromX = moveList[target - 1][0] - AAA;
14626             fromY = moveList[target - 1][1] - ONE;
14627             if (target == currentMove + 1) {
14628                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
14629             }
14630             if (appData.highlightLastMove) {
14631                 SetHighlights(fromX, fromY, toX, toY);
14632             }
14633         }
14634     }
14635     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14636         gameMode == Training || gameMode == PlayFromGameFile ||
14637         gameMode == AnalyzeFile) {
14638         while (currentMove < target) {
14639             if(second.analyzing) SendMoveToProgram(currentMove, &second);
14640             SendMoveToProgram(currentMove++, &first);
14641         }
14642     } else {
14643         currentMove = target;
14644     }
14645
14646     if (gameMode == EditGame || gameMode == EndOfGame) {
14647         whiteTimeRemaining = timeRemaining[0][currentMove];
14648         blackTimeRemaining = timeRemaining[1][currentMove];
14649     }
14650     DisplayBothClocks();
14651     DisplayMove(currentMove - 1);
14652     DrawPosition(oldSeekGraphUp, boards[currentMove]);
14653     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14654     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
14655         DisplayComment(currentMove - 1, commentList[currentMove]);
14656     }
14657     ClearMap(); // [HGM] exclude: invalidate map
14658 }
14659
14660
14661 void
14662 ForwardEvent ()
14663 {
14664     if (gameMode == IcsExamining && !pausing) {
14665         SendToICS(ics_prefix);
14666         SendToICS("forward\n");
14667     } else {
14668         ForwardInner(currentMove + 1);
14669     }
14670 }
14671
14672 void
14673 ToEndEvent ()
14674 {
14675     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14676         /* to optimze, we temporarily turn off analysis mode while we feed
14677          * the remaining moves to the engine. Otherwise we get analysis output
14678          * after each move.
14679          */
14680         if (first.analysisSupport) {
14681           SendToProgram("exit\nforce\n", &first);
14682           first.analyzing = FALSE;
14683         }
14684     }
14685
14686     if (gameMode == IcsExamining && !pausing) {
14687         SendToICS(ics_prefix);
14688         SendToICS("forward 999999\n");
14689     } else {
14690         ForwardInner(forwardMostMove);
14691     }
14692
14693     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14694         /* we have fed all the moves, so reactivate analysis mode */
14695         SendToProgram("analyze\n", &first);
14696         first.analyzing = TRUE;
14697         /*first.maybeThinking = TRUE;*/
14698         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14699     }
14700 }
14701
14702 void
14703 BackwardInner (int target)
14704 {
14705     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
14706
14707     if (appData.debugMode)
14708         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
14709                 target, currentMove, forwardMostMove);
14710
14711     if (gameMode == EditPosition) return;
14712     seekGraphUp = FALSE;
14713     MarkTargetSquares(1);
14714     if (currentMove <= backwardMostMove) {
14715         ClearHighlights();
14716         DrawPosition(full_redraw, boards[currentMove]);
14717         return;
14718     }
14719     if (gameMode == PlayFromGameFile && !pausing)
14720       PauseEvent();
14721
14722     if (moveList[target][0]) {
14723         int fromX, fromY, toX, toY;
14724         toX = moveList[target][2] - AAA;
14725         toY = moveList[target][3] - ONE;
14726         if (moveList[target][1] == '@') {
14727             if (appData.highlightLastMove) {
14728                 SetHighlights(-1, -1, toX, toY);
14729             }
14730         } else {
14731             fromX = moveList[target][0] - AAA;
14732             fromY = moveList[target][1] - ONE;
14733             if (target == currentMove - 1) {
14734                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
14735             }
14736             if (appData.highlightLastMove) {
14737                 SetHighlights(fromX, fromY, toX, toY);
14738             }
14739         }
14740     }
14741     if (gameMode == EditGame || gameMode==AnalyzeMode ||
14742         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
14743         while (currentMove > target) {
14744             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
14745                 // null move cannot be undone. Reload program with move history before it.
14746                 int i;
14747                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
14748                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
14749                 }
14750                 SendBoard(&first, i); 
14751               if(second.analyzing) SendBoard(&second, i);
14752                 for(currentMove=i; currentMove<target; currentMove++) {
14753                     SendMoveToProgram(currentMove, &first);
14754                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
14755                 }
14756                 break;
14757             }
14758             SendToBoth("undo\n");
14759             currentMove--;
14760         }
14761     } else {
14762         currentMove = target;
14763     }
14764
14765     if (gameMode == EditGame || gameMode == EndOfGame) {
14766         whiteTimeRemaining = timeRemaining[0][currentMove];
14767         blackTimeRemaining = timeRemaining[1][currentMove];
14768     }
14769     DisplayBothClocks();
14770     DisplayMove(currentMove - 1);
14771     DrawPosition(full_redraw, boards[currentMove]);
14772     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14773     // [HGM] PV info: routine tests if comment empty
14774     DisplayComment(currentMove - 1, commentList[currentMove]);
14775     ClearMap(); // [HGM] exclude: invalidate map
14776 }
14777
14778 void
14779 BackwardEvent ()
14780 {
14781     if (gameMode == IcsExamining && !pausing) {
14782         SendToICS(ics_prefix);
14783         SendToICS("backward\n");
14784     } else {
14785         BackwardInner(currentMove - 1);
14786     }
14787 }
14788
14789 void
14790 ToStartEvent ()
14791 {
14792     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14793         /* to optimize, we temporarily turn off analysis mode while we undo
14794          * all the moves. Otherwise we get analysis output after each undo.
14795          */
14796         if (first.analysisSupport) {
14797           SendToProgram("exit\nforce\n", &first);
14798           first.analyzing = FALSE;
14799         }
14800     }
14801
14802     if (gameMode == IcsExamining && !pausing) {
14803         SendToICS(ics_prefix);
14804         SendToICS("backward 999999\n");
14805     } else {
14806         BackwardInner(backwardMostMove);
14807     }
14808
14809     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14810         /* we have fed all the moves, so reactivate analysis mode */
14811         SendToProgram("analyze\n", &first);
14812         first.analyzing = TRUE;
14813         /*first.maybeThinking = TRUE;*/
14814         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14815     }
14816 }
14817
14818 void
14819 ToNrEvent (int to)
14820 {
14821   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
14822   if (to >= forwardMostMove) to = forwardMostMove;
14823   if (to <= backwardMostMove) to = backwardMostMove;
14824   if (to < currentMove) {
14825     BackwardInner(to);
14826   } else {
14827     ForwardInner(to);
14828   }
14829 }
14830
14831 void
14832 RevertEvent (Boolean annotate)
14833 {
14834     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
14835         return;
14836     }
14837     if (gameMode != IcsExamining) {
14838         DisplayError(_("You are not examining a game"), 0);
14839         return;
14840     }
14841     if (pausing) {
14842         DisplayError(_("You can't revert while pausing"), 0);
14843         return;
14844     }
14845     SendToICS(ics_prefix);
14846     SendToICS("revert\n");
14847 }
14848
14849 void
14850 RetractMoveEvent ()
14851 {
14852     switch (gameMode) {
14853       case MachinePlaysWhite:
14854       case MachinePlaysBlack:
14855         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14856             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
14857             return;
14858         }
14859         if (forwardMostMove < 2) return;
14860         currentMove = forwardMostMove = forwardMostMove - 2;
14861         whiteTimeRemaining = timeRemaining[0][currentMove];
14862         blackTimeRemaining = timeRemaining[1][currentMove];
14863         DisplayBothClocks();
14864         DisplayMove(currentMove - 1);
14865         ClearHighlights();/*!! could figure this out*/
14866         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
14867         SendToProgram("remove\n", &first);
14868         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
14869         break;
14870
14871       case BeginningOfGame:
14872       default:
14873         break;
14874
14875       case IcsPlayingWhite:
14876       case IcsPlayingBlack:
14877         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
14878             SendToICS(ics_prefix);
14879             SendToICS("takeback 2\n");
14880         } else {
14881             SendToICS(ics_prefix);
14882             SendToICS("takeback 1\n");
14883         }
14884         break;
14885     }
14886 }
14887
14888 void
14889 MoveNowEvent ()
14890 {
14891     ChessProgramState *cps;
14892
14893     switch (gameMode) {
14894       case MachinePlaysWhite:
14895         if (!WhiteOnMove(forwardMostMove)) {
14896             DisplayError(_("It is your turn"), 0);
14897             return;
14898         }
14899         cps = &first;
14900         break;
14901       case MachinePlaysBlack:
14902         if (WhiteOnMove(forwardMostMove)) {
14903             DisplayError(_("It is your turn"), 0);
14904             return;
14905         }
14906         cps = &first;
14907         break;
14908       case TwoMachinesPlay:
14909         if (WhiteOnMove(forwardMostMove) ==
14910             (first.twoMachinesColor[0] == 'w')) {
14911             cps = &first;
14912         } else {
14913             cps = &second;
14914         }
14915         break;
14916       case BeginningOfGame:
14917       default:
14918         return;
14919     }
14920     SendToProgram("?\n", cps);
14921 }
14922
14923 void
14924 TruncateGameEvent ()
14925 {
14926     EditGameEvent();
14927     if (gameMode != EditGame) return;
14928     TruncateGame();
14929 }
14930
14931 void
14932 TruncateGame ()
14933 {
14934     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
14935     if (forwardMostMove > currentMove) {
14936         if (gameInfo.resultDetails != NULL) {
14937             free(gameInfo.resultDetails);
14938             gameInfo.resultDetails = NULL;
14939             gameInfo.result = GameUnfinished;
14940         }
14941         forwardMostMove = currentMove;
14942         HistorySet(parseList, backwardMostMove, forwardMostMove,
14943                    currentMove-1);
14944     }
14945 }
14946
14947 void
14948 HintEvent ()
14949 {
14950     if (appData.noChessProgram) return;
14951     switch (gameMode) {
14952       case MachinePlaysWhite:
14953         if (WhiteOnMove(forwardMostMove)) {
14954             DisplayError(_("Wait until your turn"), 0);
14955             return;
14956         }
14957         break;
14958       case BeginningOfGame:
14959       case MachinePlaysBlack:
14960         if (!WhiteOnMove(forwardMostMove)) {
14961             DisplayError(_("Wait until your turn"), 0);
14962             return;
14963         }
14964         break;
14965       default:
14966         DisplayError(_("No hint available"), 0);
14967         return;
14968     }
14969     SendToProgram("hint\n", &first);
14970     hintRequested = TRUE;
14971 }
14972
14973 void
14974 BookEvent ()
14975 {
14976     if (appData.noChessProgram) return;
14977     switch (gameMode) {
14978       case MachinePlaysWhite:
14979         if (WhiteOnMove(forwardMostMove)) {
14980             DisplayError(_("Wait until your turn"), 0);
14981             return;
14982         }
14983         break;
14984       case BeginningOfGame:
14985       case MachinePlaysBlack:
14986         if (!WhiteOnMove(forwardMostMove)) {
14987             DisplayError(_("Wait until your turn"), 0);
14988             return;
14989         }
14990         break;
14991       case EditPosition:
14992         EditPositionDone(TRUE);
14993         break;
14994       case TwoMachinesPlay:
14995         return;
14996       default:
14997         break;
14998     }
14999     SendToProgram("bk\n", &first);
15000     bookOutput[0] = NULLCHAR;
15001     bookRequested = TRUE;
15002 }
15003
15004 void
15005 AboutGameEvent ()
15006 {
15007     char *tags = PGNTags(&gameInfo);
15008     TagsPopUp(tags, CmailMsg());
15009     free(tags);
15010 }
15011
15012 /* end button procedures */
15013
15014 void
15015 PrintPosition (FILE *fp, int move)
15016 {
15017     int i, j;
15018
15019     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15020         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15021             char c = PieceToChar(boards[move][i][j]);
15022             fputc(c == 'x' ? '.' : c, fp);
15023             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
15024         }
15025     }
15026     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
15027       fprintf(fp, "white to play\n");
15028     else
15029       fprintf(fp, "black to play\n");
15030 }
15031
15032 void
15033 PrintOpponents (FILE *fp)
15034 {
15035     if (gameInfo.white != NULL) {
15036         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
15037     } else {
15038         fprintf(fp, "\n");
15039     }
15040 }
15041
15042 /* Find last component of program's own name, using some heuristics */
15043 void
15044 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
15045 {
15046     char *p, *q, c;
15047     int local = (strcmp(host, "localhost") == 0);
15048     while (!local && (p = strchr(prog, ';')) != NULL) {
15049         p++;
15050         while (*p == ' ') p++;
15051         prog = p;
15052     }
15053     if (*prog == '"' || *prog == '\'') {
15054         q = strchr(prog + 1, *prog);
15055     } else {
15056         q = strchr(prog, ' ');
15057     }
15058     if (q == NULL) q = prog + strlen(prog);
15059     p = q;
15060     while (p >= prog && *p != '/' && *p != '\\') p--;
15061     p++;
15062     if(p == prog && *p == '"') p++;
15063     c = *q; *q = 0;
15064     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
15065     memcpy(buf, p, q - p);
15066     buf[q - p] = NULLCHAR;
15067     if (!local) {
15068         strcat(buf, "@");
15069         strcat(buf, host);
15070     }
15071 }
15072
15073 char *
15074 TimeControlTagValue ()
15075 {
15076     char buf[MSG_SIZ];
15077     if (!appData.clockMode) {
15078       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
15079     } else if (movesPerSession > 0) {
15080       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
15081     } else if (timeIncrement == 0) {
15082       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
15083     } else {
15084       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
15085     }
15086     return StrSave(buf);
15087 }
15088
15089 void
15090 SetGameInfo ()
15091 {
15092     /* This routine is used only for certain modes */
15093     VariantClass v = gameInfo.variant;
15094     ChessMove r = GameUnfinished;
15095     char *p = NULL;
15096
15097     if(keepInfo) return;
15098
15099     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
15100         r = gameInfo.result;
15101         p = gameInfo.resultDetails;
15102         gameInfo.resultDetails = NULL;
15103     }
15104     ClearGameInfo(&gameInfo);
15105     gameInfo.variant = v;
15106
15107     switch (gameMode) {
15108       case MachinePlaysWhite:
15109         gameInfo.event = StrSave( appData.pgnEventHeader );
15110         gameInfo.site = StrSave(HostName());
15111         gameInfo.date = PGNDate();
15112         gameInfo.round = StrSave("-");
15113         gameInfo.white = StrSave(first.tidy);
15114         gameInfo.black = StrSave(UserName());
15115         gameInfo.timeControl = TimeControlTagValue();
15116         break;
15117
15118       case MachinePlaysBlack:
15119         gameInfo.event = StrSave( appData.pgnEventHeader );
15120         gameInfo.site = StrSave(HostName());
15121         gameInfo.date = PGNDate();
15122         gameInfo.round = StrSave("-");
15123         gameInfo.white = StrSave(UserName());
15124         gameInfo.black = StrSave(first.tidy);
15125         gameInfo.timeControl = TimeControlTagValue();
15126         break;
15127
15128       case TwoMachinesPlay:
15129         gameInfo.event = StrSave( appData.pgnEventHeader );
15130         gameInfo.site = StrSave(HostName());
15131         gameInfo.date = PGNDate();
15132         if (roundNr > 0) {
15133             char buf[MSG_SIZ];
15134             snprintf(buf, MSG_SIZ, "%d", roundNr);
15135             gameInfo.round = StrSave(buf);
15136         } else {
15137             gameInfo.round = StrSave("-");
15138         }
15139         if (first.twoMachinesColor[0] == 'w') {
15140             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15141             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15142         } else {
15143             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15144             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15145         }
15146         gameInfo.timeControl = TimeControlTagValue();
15147         break;
15148
15149       case EditGame:
15150         gameInfo.event = StrSave("Edited game");
15151         gameInfo.site = StrSave(HostName());
15152         gameInfo.date = PGNDate();
15153         gameInfo.round = StrSave("-");
15154         gameInfo.white = StrSave("-");
15155         gameInfo.black = StrSave("-");
15156         gameInfo.result = r;
15157         gameInfo.resultDetails = p;
15158         break;
15159
15160       case EditPosition:
15161         gameInfo.event = StrSave("Edited position");
15162         gameInfo.site = StrSave(HostName());
15163         gameInfo.date = PGNDate();
15164         gameInfo.round = StrSave("-");
15165         gameInfo.white = StrSave("-");
15166         gameInfo.black = StrSave("-");
15167         break;
15168
15169       case IcsPlayingWhite:
15170       case IcsPlayingBlack:
15171       case IcsObserving:
15172       case IcsExamining:
15173         break;
15174
15175       case PlayFromGameFile:
15176         gameInfo.event = StrSave("Game from non-PGN file");
15177         gameInfo.site = StrSave(HostName());
15178         gameInfo.date = PGNDate();
15179         gameInfo.round = StrSave("-");
15180         gameInfo.white = StrSave("?");
15181         gameInfo.black = StrSave("?");
15182         break;
15183
15184       default:
15185         break;
15186     }
15187 }
15188
15189 void
15190 ReplaceComment (int index, char *text)
15191 {
15192     int len;
15193     char *p;
15194     float score;
15195
15196     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
15197        pvInfoList[index-1].depth == len &&
15198        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
15199        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
15200     while (*text == '\n') text++;
15201     len = strlen(text);
15202     while (len > 0 && text[len - 1] == '\n') len--;
15203
15204     if (commentList[index] != NULL)
15205       free(commentList[index]);
15206
15207     if (len == 0) {
15208         commentList[index] = NULL;
15209         return;
15210     }
15211   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
15212       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
15213       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
15214     commentList[index] = (char *) malloc(len + 2);
15215     strncpy(commentList[index], text, len);
15216     commentList[index][len] = '\n';
15217     commentList[index][len + 1] = NULLCHAR;
15218   } else {
15219     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
15220     char *p;
15221     commentList[index] = (char *) malloc(len + 7);
15222     safeStrCpy(commentList[index], "{\n", 3);
15223     safeStrCpy(commentList[index]+2, text, len+1);
15224     commentList[index][len+2] = NULLCHAR;
15225     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
15226     strcat(commentList[index], "\n}\n");
15227   }
15228 }
15229
15230 void
15231 CrushCRs (char *text)
15232 {
15233   char *p = text;
15234   char *q = text;
15235   char ch;
15236
15237   do {
15238     ch = *p++;
15239     if (ch == '\r') continue;
15240     *q++ = ch;
15241   } while (ch != '\0');
15242 }
15243
15244 void
15245 AppendComment (int index, char *text, Boolean addBraces)
15246 /* addBraces  tells if we should add {} */
15247 {
15248     int oldlen, len;
15249     char *old;
15250
15251 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
15252     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
15253
15254     CrushCRs(text);
15255     while (*text == '\n') text++;
15256     len = strlen(text);
15257     while (len > 0 && text[len - 1] == '\n') len--;
15258     text[len] = NULLCHAR;
15259
15260     if (len == 0) return;
15261
15262     if (commentList[index] != NULL) {
15263       Boolean addClosingBrace = addBraces;
15264         old = commentList[index];
15265         oldlen = strlen(old);
15266         while(commentList[index][oldlen-1] ==  '\n')
15267           commentList[index][--oldlen] = NULLCHAR;
15268         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
15269         safeStrCpy(commentList[index], old, oldlen + len + 6);
15270         free(old);
15271         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
15272         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
15273           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
15274           while (*text == '\n') { text++; len--; }
15275           commentList[index][--oldlen] = NULLCHAR;
15276       }
15277         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
15278         else          strcat(commentList[index], "\n");
15279         strcat(commentList[index], text);
15280         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
15281         else          strcat(commentList[index], "\n");
15282     } else {
15283         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
15284         if(addBraces)
15285           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
15286         else commentList[index][0] = NULLCHAR;
15287         strcat(commentList[index], text);
15288         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
15289         if(addBraces == TRUE) strcat(commentList[index], "}\n");
15290     }
15291 }
15292
15293 static char *
15294 FindStr (char * text, char * sub_text)
15295 {
15296     char * result = strstr( text, sub_text );
15297
15298     if( result != NULL ) {
15299         result += strlen( sub_text );
15300     }
15301
15302     return result;
15303 }
15304
15305 /* [AS] Try to extract PV info from PGN comment */
15306 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
15307 char *
15308 GetInfoFromComment (int index, char * text)
15309 {
15310     char * sep = text, *p;
15311
15312     if( text != NULL && index > 0 ) {
15313         int score = 0;
15314         int depth = 0;
15315         int time = -1, sec = 0, deci;
15316         char * s_eval = FindStr( text, "[%eval " );
15317         char * s_emt = FindStr( text, "[%emt " );
15318
15319         if( s_eval != NULL || s_emt != NULL ) {
15320             /* New style */
15321             char delim;
15322
15323             if( s_eval != NULL ) {
15324                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
15325                     return text;
15326                 }
15327
15328                 if( delim != ']' ) {
15329                     return text;
15330                 }
15331             }
15332
15333             if( s_emt != NULL ) {
15334             }
15335                 return text;
15336         }
15337         else {
15338             /* We expect something like: [+|-]nnn.nn/dd */
15339             int score_lo = 0;
15340
15341             if(*text != '{') return text; // [HGM] braces: must be normal comment
15342
15343             sep = strchr( text, '/' );
15344             if( sep == NULL || sep < (text+4) ) {
15345                 return text;
15346             }
15347
15348             p = text;
15349             if(p[1] == '(') { // comment starts with PV
15350                p = strchr(p, ')'); // locate end of PV
15351                if(p == NULL || sep < p+5) return text;
15352                // at this point we have something like "{(.*) +0.23/6 ..."
15353                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
15354                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
15355                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
15356             }
15357             time = -1; sec = -1; deci = -1;
15358             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
15359                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
15360                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
15361                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
15362                 return text;
15363             }
15364
15365             if( score_lo < 0 || score_lo >= 100 ) {
15366                 return text;
15367             }
15368
15369             if(sec >= 0) time = 600*time + 10*sec; else
15370             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
15371
15372             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
15373
15374             /* [HGM] PV time: now locate end of PV info */
15375             while( *++sep >= '0' && *sep <= '9'); // strip depth
15376             if(time >= 0)
15377             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
15378             if(sec >= 0)
15379             while( *++sep >= '0' && *sep <= '9'); // strip seconds
15380             if(deci >= 0)
15381             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
15382             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
15383         }
15384
15385         if( depth <= 0 ) {
15386             return text;
15387         }
15388
15389         if( time < 0 ) {
15390             time = -1;
15391         }
15392
15393         pvInfoList[index-1].depth = depth;
15394         pvInfoList[index-1].score = score;
15395         pvInfoList[index-1].time  = 10*time; // centi-sec
15396         if(*sep == '}') *sep = 0; else *--sep = '{';
15397         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
15398     }
15399     return sep;
15400 }
15401
15402 void
15403 SendToProgram (char *message, ChessProgramState *cps)
15404 {
15405     int count, outCount, error;
15406     char buf[MSG_SIZ];
15407
15408     if (cps->pr == NoProc) return;
15409     Attention(cps);
15410
15411     if (appData.debugMode) {
15412         TimeMark now;
15413         GetTimeMark(&now);
15414         fprintf(debugFP, "%ld >%-6s: %s",
15415                 SubtractTimeMarks(&now, &programStartTime),
15416                 cps->which, message);
15417         if(serverFP)
15418             fprintf(serverFP, "%ld >%-6s: %s",
15419                 SubtractTimeMarks(&now, &programStartTime),
15420                 cps->which, message), fflush(serverFP);
15421     }
15422
15423     count = strlen(message);
15424     outCount = OutputToProcess(cps->pr, message, count, &error);
15425     if (outCount < count && !exiting
15426                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
15427       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15428       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
15429         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15430             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15431                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15432                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15433                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15434             } else {
15435                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15436                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15437                 gameInfo.result = res;
15438             }
15439             gameInfo.resultDetails = StrSave(buf);
15440         }
15441         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15442         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15443     }
15444 }
15445
15446 void
15447 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
15448 {
15449     char *end_str;
15450     char buf[MSG_SIZ];
15451     ChessProgramState *cps = (ChessProgramState *)closure;
15452
15453     if (isr != cps->isr) return; /* Killed intentionally */
15454     if (count <= 0) {
15455         if (count == 0) {
15456             RemoveInputSource(cps->isr);
15457             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
15458                     _(cps->which), cps->program);
15459             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
15460             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15461                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15462                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15463                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15464                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15465                 } else {
15466                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15467                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15468                     gameInfo.result = res;
15469                 }
15470                 gameInfo.resultDetails = StrSave(buf);
15471             }
15472             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15473             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
15474         } else {
15475             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
15476                     _(cps->which), cps->program);
15477             RemoveInputSource(cps->isr);
15478
15479             /* [AS] Program is misbehaving badly... kill it */
15480             if( count == -2 ) {
15481                 DestroyChildProcess( cps->pr, 9 );
15482                 cps->pr = NoProc;
15483             }
15484
15485             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15486         }
15487         return;
15488     }
15489
15490     if ((end_str = strchr(message, '\r')) != NULL)
15491       *end_str = NULLCHAR;
15492     if ((end_str = strchr(message, '\n')) != NULL)
15493       *end_str = NULLCHAR;
15494
15495     if (appData.debugMode) {
15496         TimeMark now; int print = 1;
15497         char *quote = ""; char c; int i;
15498
15499         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
15500                 char start = message[0];
15501                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
15502                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
15503                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
15504                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
15505                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
15506                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
15507                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
15508                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
15509                    sscanf(message, "hint: %c", &c)!=1 && 
15510                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
15511                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
15512                     print = (appData.engineComments >= 2);
15513                 }
15514                 message[0] = start; // restore original message
15515         }
15516         if(print) {
15517                 GetTimeMark(&now);
15518                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
15519                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15520                         quote,
15521                         message);
15522                 if(serverFP)
15523                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
15524                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15525                         quote,
15526                         message), fflush(serverFP);
15527         }
15528     }
15529
15530     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
15531     if (appData.icsEngineAnalyze) {
15532         if (strstr(message, "whisper") != NULL ||
15533              strstr(message, "kibitz") != NULL ||
15534             strstr(message, "tellics") != NULL) return;
15535     }
15536
15537     HandleMachineMove(message, cps);
15538 }
15539
15540
15541 void
15542 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
15543 {
15544     char buf[MSG_SIZ];
15545     int seconds;
15546
15547     if( timeControl_2 > 0 ) {
15548         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
15549             tc = timeControl_2;
15550         }
15551     }
15552     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
15553     inc /= cps->timeOdds;
15554     st  /= cps->timeOdds;
15555
15556     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
15557
15558     if (st > 0) {
15559       /* Set exact time per move, normally using st command */
15560       if (cps->stKludge) {
15561         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
15562         seconds = st % 60;
15563         if (seconds == 0) {
15564           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
15565         } else {
15566           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
15567         }
15568       } else {
15569         snprintf(buf, MSG_SIZ, "st %d\n", st);
15570       }
15571     } else {
15572       /* Set conventional or incremental time control, using level command */
15573       if (seconds == 0) {
15574         /* Note old gnuchess bug -- minutes:seconds used to not work.
15575            Fixed in later versions, but still avoid :seconds
15576            when seconds is 0. */
15577         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
15578       } else {
15579         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
15580                  seconds, inc/1000.);
15581       }
15582     }
15583     SendToProgram(buf, cps);
15584
15585     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
15586     /* Orthogonally, limit search to given depth */
15587     if (sd > 0) {
15588       if (cps->sdKludge) {
15589         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
15590       } else {
15591         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
15592       }
15593       SendToProgram(buf, cps);
15594     }
15595
15596     if(cps->nps >= 0) { /* [HGM] nps */
15597         if(cps->supportsNPS == FALSE)
15598           cps->nps = -1; // don't use if engine explicitly says not supported!
15599         else {
15600           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
15601           SendToProgram(buf, cps);
15602         }
15603     }
15604 }
15605
15606 ChessProgramState *
15607 WhitePlayer ()
15608 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
15609 {
15610     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
15611        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
15612         return &second;
15613     return &first;
15614 }
15615
15616 void
15617 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
15618 {
15619     char message[MSG_SIZ];
15620     long time, otime;
15621
15622     /* Note: this routine must be called when the clocks are stopped
15623        or when they have *just* been set or switched; otherwise
15624        it will be off by the time since the current tick started.
15625     */
15626     if (machineWhite) {
15627         time = whiteTimeRemaining / 10;
15628         otime = blackTimeRemaining / 10;
15629     } else {
15630         time = blackTimeRemaining / 10;
15631         otime = whiteTimeRemaining / 10;
15632     }
15633     /* [HGM] translate opponent's time by time-odds factor */
15634     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
15635
15636     if (time <= 0) time = 1;
15637     if (otime <= 0) otime = 1;
15638
15639     snprintf(message, MSG_SIZ, "time %ld\n", time);
15640     SendToProgram(message, cps);
15641
15642     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
15643     SendToProgram(message, cps);
15644 }
15645
15646 int
15647 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15648 {
15649   char buf[MSG_SIZ];
15650   int len = strlen(name);
15651   int val;
15652
15653   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15654     (*p) += len + 1;
15655     sscanf(*p, "%d", &val);
15656     *loc = (val != 0);
15657     while (**p && **p != ' ')
15658       (*p)++;
15659     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15660     SendToProgram(buf, cps);
15661     return TRUE;
15662   }
15663   return FALSE;
15664 }
15665
15666 int
15667 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15668 {
15669   char buf[MSG_SIZ];
15670   int len = strlen(name);
15671   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15672     (*p) += len + 1;
15673     sscanf(*p, "%d", loc);
15674     while (**p && **p != ' ') (*p)++;
15675     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15676     SendToProgram(buf, cps);
15677     return TRUE;
15678   }
15679   return FALSE;
15680 }
15681
15682 int
15683 StringFeature (char **p, char *name, char loc[], ChessProgramState *cps)
15684 {
15685   char buf[MSG_SIZ];
15686   int len = strlen(name);
15687   if (strncmp((*p), name, len) == 0
15688       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
15689     (*p) += len + 2;
15690     sscanf(*p, "%[^\"]", loc);
15691     while (**p && **p != '\"') (*p)++;
15692     if (**p == '\"') (*p)++;
15693     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15694     SendToProgram(buf, cps);
15695     return TRUE;
15696   }
15697   return FALSE;
15698 }
15699
15700 int
15701 ParseOption (Option *opt, ChessProgramState *cps)
15702 // [HGM] options: process the string that defines an engine option, and determine
15703 // name, type, default value, and allowed value range
15704 {
15705         char *p, *q, buf[MSG_SIZ];
15706         int n, min = (-1)<<31, max = 1<<31, def;
15707
15708         if(p = strstr(opt->name, " -spin ")) {
15709             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15710             if(max < min) max = min; // enforce consistency
15711             if(def < min) def = min;
15712             if(def > max) def = max;
15713             opt->value = def;
15714             opt->min = min;
15715             opt->max = max;
15716             opt->type = Spin;
15717         } else if((p = strstr(opt->name, " -slider "))) {
15718             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
15719             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15720             if(max < min) max = min; // enforce consistency
15721             if(def < min) def = min;
15722             if(def > max) def = max;
15723             opt->value = def;
15724             opt->min = min;
15725             opt->max = max;
15726             opt->type = Spin; // Slider;
15727         } else if((p = strstr(opt->name, " -string "))) {
15728             opt->textValue = p+9;
15729             opt->type = TextBox;
15730         } else if((p = strstr(opt->name, " -file "))) {
15731             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15732             opt->textValue = p+7;
15733             opt->type = FileName; // FileName;
15734         } else if((p = strstr(opt->name, " -path "))) {
15735             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15736             opt->textValue = p+7;
15737             opt->type = PathName; // PathName;
15738         } else if(p = strstr(opt->name, " -check ")) {
15739             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
15740             opt->value = (def != 0);
15741             opt->type = CheckBox;
15742         } else if(p = strstr(opt->name, " -combo ")) {
15743             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
15744             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
15745             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
15746             opt->value = n = 0;
15747             while(q = StrStr(q, " /// ")) {
15748                 n++; *q = 0;    // count choices, and null-terminate each of them
15749                 q += 5;
15750                 if(*q == '*') { // remember default, which is marked with * prefix
15751                     q++;
15752                     opt->value = n;
15753                 }
15754                 cps->comboList[cps->comboCnt++] = q;
15755             }
15756             cps->comboList[cps->comboCnt++] = NULL;
15757             opt->max = n + 1;
15758             opt->type = ComboBox;
15759         } else if(p = strstr(opt->name, " -button")) {
15760             opt->type = Button;
15761         } else if(p = strstr(opt->name, " -save")) {
15762             opt->type = SaveButton;
15763         } else return FALSE;
15764         *p = 0; // terminate option name
15765         // now look if the command-line options define a setting for this engine option.
15766         if(cps->optionSettings && cps->optionSettings[0])
15767             p = strstr(cps->optionSettings, opt->name); else p = NULL;
15768         if(p && (p == cps->optionSettings || p[-1] == ',')) {
15769           snprintf(buf, MSG_SIZ, "option %s", p);
15770                 if(p = strstr(buf, ",")) *p = 0;
15771                 if(q = strchr(buf, '=')) switch(opt->type) {
15772                     case ComboBox:
15773                         for(n=0; n<opt->max; n++)
15774                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
15775                         break;
15776                     case TextBox:
15777                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
15778                         break;
15779                     case Spin:
15780                     case CheckBox:
15781                         opt->value = atoi(q+1);
15782                     default:
15783                         break;
15784                 }
15785                 strcat(buf, "\n");
15786                 SendToProgram(buf, cps);
15787         }
15788         return TRUE;
15789 }
15790
15791 void
15792 FeatureDone (ChessProgramState *cps, int val)
15793 {
15794   DelayedEventCallback cb = GetDelayedEvent();
15795   if ((cb == InitBackEnd3 && cps == &first) ||
15796       (cb == SettingsMenuIfReady && cps == &second) ||
15797       (cb == LoadEngine) ||
15798       (cb == TwoMachinesEventIfReady)) {
15799     CancelDelayedEvent();
15800     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
15801   }
15802   cps->initDone = val;
15803 }
15804
15805 /* Parse feature command from engine */
15806 void
15807 ParseFeatures (char *args, ChessProgramState *cps)
15808 {
15809   char *p = args;
15810   char *q;
15811   int val;
15812   char buf[MSG_SIZ];
15813
15814   for (;;) {
15815     while (*p == ' ') p++;
15816     if (*p == NULLCHAR) return;
15817
15818     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
15819     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
15820     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
15821     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
15822     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
15823     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
15824     if (BoolFeature(&p, "reuse", &val, cps)) {
15825       /* Engine can disable reuse, but can't enable it if user said no */
15826       if (!val) cps->reuse = FALSE;
15827       continue;
15828     }
15829     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
15830     if (StringFeature(&p, "myname", cps->tidy, cps)) {
15831       if (gameMode == TwoMachinesPlay) {
15832         DisplayTwoMachinesTitle();
15833       } else {
15834         DisplayTitle("");
15835       }
15836       continue;
15837     }
15838     if (StringFeature(&p, "variants", cps->variants, cps)) continue;
15839     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
15840     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
15841     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
15842     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
15843     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
15844     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
15845     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
15846     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
15847     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
15848     if (IntFeature(&p, "done", &val, cps)) {
15849       FeatureDone(cps, val);
15850       continue;
15851     }
15852     /* Added by Tord: */
15853     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
15854     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
15855     /* End of additions by Tord */
15856
15857     /* [HGM] added features: */
15858     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
15859     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
15860     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
15861     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
15862     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
15863     if (StringFeature(&p, "egt", cps->egtFormats, cps)) continue;
15864     if (StringFeature(&p, "option", buf, cps)) {
15865         FREE(cps->option[cps->nrOptions].name);
15866         cps->option[cps->nrOptions].name = malloc(MSG_SIZ);
15867         safeStrCpy(cps->option[cps->nrOptions].name, buf, MSG_SIZ);
15868         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
15869           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
15870             SendToProgram(buf, cps);
15871             continue;
15872         }
15873         if(cps->nrOptions >= MAX_OPTIONS) {
15874             cps->nrOptions--;
15875             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
15876             DisplayError(buf, 0);
15877         }
15878         continue;
15879     }
15880     /* End of additions by HGM */
15881
15882     /* unknown feature: complain and skip */
15883     q = p;
15884     while (*q && *q != '=') q++;
15885     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
15886     SendToProgram(buf, cps);
15887     p = q;
15888     if (*p == '=') {
15889       p++;
15890       if (*p == '\"') {
15891         p++;
15892         while (*p && *p != '\"') p++;
15893         if (*p == '\"') p++;
15894       } else {
15895         while (*p && *p != ' ') p++;
15896       }
15897     }
15898   }
15899
15900 }
15901
15902 void
15903 PeriodicUpdatesEvent (int newState)
15904 {
15905     if (newState == appData.periodicUpdates)
15906       return;
15907
15908     appData.periodicUpdates=newState;
15909
15910     /* Display type changes, so update it now */
15911 //    DisplayAnalysis();
15912
15913     /* Get the ball rolling again... */
15914     if (newState) {
15915         AnalysisPeriodicEvent(1);
15916         StartAnalysisClock();
15917     }
15918 }
15919
15920 void
15921 PonderNextMoveEvent (int newState)
15922 {
15923     if (newState == appData.ponderNextMove) return;
15924     if (gameMode == EditPosition) EditPositionDone(TRUE);
15925     if (newState) {
15926         SendToProgram("hard\n", &first);
15927         if (gameMode == TwoMachinesPlay) {
15928             SendToProgram("hard\n", &second);
15929         }
15930     } else {
15931         SendToProgram("easy\n", &first);
15932         thinkOutput[0] = NULLCHAR;
15933         if (gameMode == TwoMachinesPlay) {
15934             SendToProgram("easy\n", &second);
15935         }
15936     }
15937     appData.ponderNextMove = newState;
15938 }
15939
15940 void
15941 NewSettingEvent (int option, int *feature, char *command, int value)
15942 {
15943     char buf[MSG_SIZ];
15944
15945     if (gameMode == EditPosition) EditPositionDone(TRUE);
15946     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
15947     if(feature == NULL || *feature) SendToProgram(buf, &first);
15948     if (gameMode == TwoMachinesPlay) {
15949         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
15950     }
15951 }
15952
15953 void
15954 ShowThinkingEvent ()
15955 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
15956 {
15957     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
15958     int newState = appData.showThinking
15959         // [HGM] thinking: other features now need thinking output as well
15960         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
15961
15962     if (oldState == newState) return;
15963     oldState = newState;
15964     if (gameMode == EditPosition) EditPositionDone(TRUE);
15965     if (oldState) {
15966         SendToProgram("post\n", &first);
15967         if (gameMode == TwoMachinesPlay) {
15968             SendToProgram("post\n", &second);
15969         }
15970     } else {
15971         SendToProgram("nopost\n", &first);
15972         thinkOutput[0] = NULLCHAR;
15973         if (gameMode == TwoMachinesPlay) {
15974             SendToProgram("nopost\n", &second);
15975         }
15976     }
15977 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
15978 }
15979
15980 void
15981 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
15982 {
15983   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
15984   if (pr == NoProc) return;
15985   AskQuestion(title, question, replyPrefix, pr);
15986 }
15987
15988 void
15989 TypeInEvent (char firstChar)
15990 {
15991     if ((gameMode == BeginningOfGame && !appData.icsActive) || 
15992         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
15993         gameMode == AnalyzeMode || gameMode == EditGame || 
15994         gameMode == EditPosition || gameMode == IcsExamining ||
15995         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
15996         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
15997                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
15998                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
15999         gameMode == Training) PopUpMoveDialog(firstChar);
16000 }
16001
16002 void
16003 TypeInDoneEvent (char *move)
16004 {
16005         Board board;
16006         int n, fromX, fromY, toX, toY;
16007         char promoChar;
16008         ChessMove moveType;
16009
16010         // [HGM] FENedit
16011         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
16012                 EditPositionPasteFEN(move);
16013                 return;
16014         }
16015         // [HGM] movenum: allow move number to be typed in any mode
16016         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
16017           ToNrEvent(2*n-1);
16018           return;
16019         }
16020         // undocumented kludge: allow command-line option to be typed in!
16021         // (potentially fatal, and does not implement the effect of the option.)
16022         // should only be used for options that are values on which future decisions will be made,
16023         // and definitely not on options that would be used during initialization.
16024         if(strstr(move, "!!! -") == move) {
16025             ParseArgsFromString(move+4);
16026             return;
16027         }
16028
16029       if (gameMode != EditGame && currentMove != forwardMostMove && 
16030         gameMode != Training) {
16031         DisplayMoveError(_("Displayed move is not current"));
16032       } else {
16033         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
16034           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
16035         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
16036         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
16037           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
16038           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     
16039         } else {
16040           DisplayMoveError(_("Could not parse move"));
16041         }
16042       }
16043 }
16044
16045 void
16046 DisplayMove (int moveNumber)
16047 {
16048     char message[MSG_SIZ];
16049     char res[MSG_SIZ];
16050     char cpThinkOutput[MSG_SIZ];
16051
16052     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
16053
16054     if (moveNumber == forwardMostMove - 1 ||
16055         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16056
16057         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
16058
16059         if (strchr(cpThinkOutput, '\n')) {
16060             *strchr(cpThinkOutput, '\n') = NULLCHAR;
16061         }
16062     } else {
16063         *cpThinkOutput = NULLCHAR;
16064     }
16065
16066     /* [AS] Hide thinking from human user */
16067     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
16068         *cpThinkOutput = NULLCHAR;
16069         if( thinkOutput[0] != NULLCHAR ) {
16070             int i;
16071
16072             for( i=0; i<=hiddenThinkOutputState; i++ ) {
16073                 cpThinkOutput[i] = '.';
16074             }
16075             cpThinkOutput[i] = NULLCHAR;
16076             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
16077         }
16078     }
16079
16080     if (moveNumber == forwardMostMove - 1 &&
16081         gameInfo.resultDetails != NULL) {
16082         if (gameInfo.resultDetails[0] == NULLCHAR) {
16083           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
16084         } else {
16085           snprintf(res, MSG_SIZ, " {%s} %s",
16086                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
16087         }
16088     } else {
16089         res[0] = NULLCHAR;
16090     }
16091
16092     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16093         DisplayMessage(res, cpThinkOutput);
16094     } else {
16095       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
16096                 WhiteOnMove(moveNumber) ? " " : ".. ",
16097                 parseList[moveNumber], res);
16098         DisplayMessage(message, cpThinkOutput);
16099     }
16100 }
16101
16102 void
16103 DisplayComment (int moveNumber, char *text)
16104 {
16105     char title[MSG_SIZ];
16106
16107     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16108       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
16109     } else {
16110       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
16111               WhiteOnMove(moveNumber) ? " " : ".. ",
16112               parseList[moveNumber]);
16113     }
16114     if (text != NULL && (appData.autoDisplayComment || commentUp))
16115         CommentPopUp(title, text);
16116 }
16117
16118 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
16119  * might be busy thinking or pondering.  It can be omitted if your
16120  * gnuchess is configured to stop thinking immediately on any user
16121  * input.  However, that gnuchess feature depends on the FIONREAD
16122  * ioctl, which does not work properly on some flavors of Unix.
16123  */
16124 void
16125 Attention (ChessProgramState *cps)
16126 {
16127 #if ATTENTION
16128     if (!cps->useSigint) return;
16129     if (appData.noChessProgram || (cps->pr == NoProc)) return;
16130     switch (gameMode) {
16131       case MachinePlaysWhite:
16132       case MachinePlaysBlack:
16133       case TwoMachinesPlay:
16134       case IcsPlayingWhite:
16135       case IcsPlayingBlack:
16136       case AnalyzeMode:
16137       case AnalyzeFile:
16138         /* Skip if we know it isn't thinking */
16139         if (!cps->maybeThinking) return;
16140         if (appData.debugMode)
16141           fprintf(debugFP, "Interrupting %s\n", cps->which);
16142         InterruptChildProcess(cps->pr);
16143         cps->maybeThinking = FALSE;
16144         break;
16145       default:
16146         break;
16147     }
16148 #endif /*ATTENTION*/
16149 }
16150
16151 int
16152 CheckFlags ()
16153 {
16154     if (whiteTimeRemaining <= 0) {
16155         if (!whiteFlag) {
16156             whiteFlag = TRUE;
16157             if (appData.icsActive) {
16158                 if (appData.autoCallFlag &&
16159                     gameMode == IcsPlayingBlack && !blackFlag) {
16160                   SendToICS(ics_prefix);
16161                   SendToICS("flag\n");
16162                 }
16163             } else {
16164                 if (blackFlag) {
16165                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16166                 } else {
16167                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
16168                     if (appData.autoCallFlag) {
16169                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
16170                         return TRUE;
16171                     }
16172                 }
16173             }
16174         }
16175     }
16176     if (blackTimeRemaining <= 0) {
16177         if (!blackFlag) {
16178             blackFlag = TRUE;
16179             if (appData.icsActive) {
16180                 if (appData.autoCallFlag &&
16181                     gameMode == IcsPlayingWhite && !whiteFlag) {
16182                   SendToICS(ics_prefix);
16183                   SendToICS("flag\n");
16184                 }
16185             } else {
16186                 if (whiteFlag) {
16187                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16188                 } else {
16189                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
16190                     if (appData.autoCallFlag) {
16191                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
16192                         return TRUE;
16193                     }
16194                 }
16195             }
16196         }
16197     }
16198     return FALSE;
16199 }
16200
16201 void
16202 CheckTimeControl ()
16203 {
16204     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
16205         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
16206
16207     /*
16208      * add time to clocks when time control is achieved ([HGM] now also used for increment)
16209      */
16210     if ( !WhiteOnMove(forwardMostMove) ) {
16211         /* White made time control */
16212         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
16213         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
16214         /* [HGM] time odds: correct new time quota for time odds! */
16215                                             / WhitePlayer()->timeOdds;
16216         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
16217     } else {
16218         lastBlack -= blackTimeRemaining;
16219         /* Black made time control */
16220         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
16221                                             / WhitePlayer()->other->timeOdds;
16222         lastWhite = whiteTimeRemaining;
16223     }
16224 }
16225
16226 void
16227 DisplayBothClocks ()
16228 {
16229     int wom = gameMode == EditPosition ?
16230       !blackPlaysFirst : WhiteOnMove(currentMove);
16231     DisplayWhiteClock(whiteTimeRemaining, wom);
16232     DisplayBlackClock(blackTimeRemaining, !wom);
16233 }
16234
16235
16236 /* Timekeeping seems to be a portability nightmare.  I think everyone
16237    has ftime(), but I'm really not sure, so I'm including some ifdefs
16238    to use other calls if you don't.  Clocks will be less accurate if
16239    you have neither ftime nor gettimeofday.
16240 */
16241
16242 /* VS 2008 requires the #include outside of the function */
16243 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
16244 #include <sys/timeb.h>
16245 #endif
16246
16247 /* Get the current time as a TimeMark */
16248 void
16249 GetTimeMark (TimeMark *tm)
16250 {
16251 #if HAVE_GETTIMEOFDAY
16252
16253     struct timeval timeVal;
16254     struct timezone timeZone;
16255
16256     gettimeofday(&timeVal, &timeZone);
16257     tm->sec = (long) timeVal.tv_sec;
16258     tm->ms = (int) (timeVal.tv_usec / 1000L);
16259
16260 #else /*!HAVE_GETTIMEOFDAY*/
16261 #if HAVE_FTIME
16262
16263 // include <sys/timeb.h> / moved to just above start of function
16264     struct timeb timeB;
16265
16266     ftime(&timeB);
16267     tm->sec = (long) timeB.time;
16268     tm->ms = (int) timeB.millitm;
16269
16270 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
16271     tm->sec = (long) time(NULL);
16272     tm->ms = 0;
16273 #endif
16274 #endif
16275 }
16276
16277 /* Return the difference in milliseconds between two
16278    time marks.  We assume the difference will fit in a long!
16279 */
16280 long
16281 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
16282 {
16283     return 1000L*(tm2->sec - tm1->sec) +
16284            (long) (tm2->ms - tm1->ms);
16285 }
16286
16287
16288 /*
16289  * Code to manage the game clocks.
16290  *
16291  * In tournament play, black starts the clock and then white makes a move.
16292  * We give the human user a slight advantage if he is playing white---the
16293  * clocks don't run until he makes his first move, so it takes zero time.
16294  * Also, we don't account for network lag, so we could get out of sync
16295  * with GNU Chess's clock -- but then, referees are always right.
16296  */
16297
16298 static TimeMark tickStartTM;
16299 static long intendedTickLength;
16300
16301 long
16302 NextTickLength (long timeRemaining)
16303 {
16304     long nominalTickLength, nextTickLength;
16305
16306     if (timeRemaining > 0L && timeRemaining <= 10000L)
16307       nominalTickLength = 100L;
16308     else
16309       nominalTickLength = 1000L;
16310     nextTickLength = timeRemaining % nominalTickLength;
16311     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
16312
16313     return nextTickLength;
16314 }
16315
16316 /* Adjust clock one minute up or down */
16317 void
16318 AdjustClock (Boolean which, int dir)
16319 {
16320     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
16321     if(which) blackTimeRemaining += 60000*dir;
16322     else      whiteTimeRemaining += 60000*dir;
16323     DisplayBothClocks();
16324     adjustedClock = TRUE;
16325 }
16326
16327 /* Stop clocks and reset to a fresh time control */
16328 void
16329 ResetClocks ()
16330 {
16331     (void) StopClockTimer();
16332     if (appData.icsActive) {
16333         whiteTimeRemaining = blackTimeRemaining = 0;
16334     } else if (searchTime) {
16335         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16336         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16337     } else { /* [HGM] correct new time quote for time odds */
16338         whiteTC = blackTC = fullTimeControlString;
16339         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
16340         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
16341     }
16342     if (whiteFlag || blackFlag) {
16343         DisplayTitle("");
16344         whiteFlag = blackFlag = FALSE;
16345     }
16346     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
16347     DisplayBothClocks();
16348     adjustedClock = FALSE;
16349 }
16350
16351 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
16352
16353 /* Decrement running clock by amount of time that has passed */
16354 void
16355 DecrementClocks ()
16356 {
16357     long timeRemaining;
16358     long lastTickLength, fudge;
16359     TimeMark now;
16360
16361     if (!appData.clockMode) return;
16362     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
16363
16364     GetTimeMark(&now);
16365
16366     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16367
16368     /* Fudge if we woke up a little too soon */
16369     fudge = intendedTickLength - lastTickLength;
16370     if (fudge < 0 || fudge > FUDGE) fudge = 0;
16371
16372     if (WhiteOnMove(forwardMostMove)) {
16373         if(whiteNPS >= 0) lastTickLength = 0;
16374         timeRemaining = whiteTimeRemaining -= lastTickLength;
16375         if(timeRemaining < 0 && !appData.icsActive) {
16376             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
16377             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
16378                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
16379                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
16380             }
16381         }
16382         DisplayWhiteClock(whiteTimeRemaining - fudge,
16383                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16384     } else {
16385         if(blackNPS >= 0) lastTickLength = 0;
16386         timeRemaining = blackTimeRemaining -= lastTickLength;
16387         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
16388             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
16389             if(suddenDeath) {
16390                 blackStartMove = forwardMostMove;
16391                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
16392             }
16393         }
16394         DisplayBlackClock(blackTimeRemaining - fudge,
16395                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16396     }
16397     if (CheckFlags()) return;
16398
16399     if(twoBoards) { // count down secondary board's clocks as well
16400         activePartnerTime -= lastTickLength;
16401         partnerUp = 1;
16402         if(activePartner == 'W')
16403             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
16404         else
16405             DisplayBlackClock(activePartnerTime, TRUE);
16406         partnerUp = 0;
16407     }
16408
16409     tickStartTM = now;
16410     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
16411     StartClockTimer(intendedTickLength);
16412
16413     /* if the time remaining has fallen below the alarm threshold, sound the
16414      * alarm. if the alarm has sounded and (due to a takeback or time control
16415      * with increment) the time remaining has increased to a level above the
16416      * threshold, reset the alarm so it can sound again.
16417      */
16418
16419     if (appData.icsActive && appData.icsAlarm) {
16420
16421         /* make sure we are dealing with the user's clock */
16422         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
16423                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
16424            )) return;
16425
16426         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
16427             alarmSounded = FALSE;
16428         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
16429             PlayAlarmSound();
16430             alarmSounded = TRUE;
16431         }
16432     }
16433 }
16434
16435
16436 /* A player has just moved, so stop the previously running
16437    clock and (if in clock mode) start the other one.
16438    We redisplay both clocks in case we're in ICS mode, because
16439    ICS gives us an update to both clocks after every move.
16440    Note that this routine is called *after* forwardMostMove
16441    is updated, so the last fractional tick must be subtracted
16442    from the color that is *not* on move now.
16443 */
16444 void
16445 SwitchClocks (int newMoveNr)
16446 {
16447     long lastTickLength;
16448     TimeMark now;
16449     int flagged = FALSE;
16450
16451     GetTimeMark(&now);
16452
16453     if (StopClockTimer() && appData.clockMode) {
16454         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16455         if (!WhiteOnMove(forwardMostMove)) {
16456             if(blackNPS >= 0) lastTickLength = 0;
16457             blackTimeRemaining -= lastTickLength;
16458            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16459 //         if(pvInfoList[forwardMostMove].time == -1)
16460                  pvInfoList[forwardMostMove].time =               // use GUI time
16461                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
16462         } else {
16463            if(whiteNPS >= 0) lastTickLength = 0;
16464            whiteTimeRemaining -= lastTickLength;
16465            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16466 //         if(pvInfoList[forwardMostMove].time == -1)
16467                  pvInfoList[forwardMostMove].time =
16468                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
16469         }
16470         flagged = CheckFlags();
16471     }
16472     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
16473     CheckTimeControl();
16474
16475     if (flagged || !appData.clockMode) return;
16476
16477     switch (gameMode) {
16478       case MachinePlaysBlack:
16479       case MachinePlaysWhite:
16480       case BeginningOfGame:
16481         if (pausing) return;
16482         break;
16483
16484       case EditGame:
16485       case PlayFromGameFile:
16486       case IcsExamining:
16487         return;
16488
16489       default:
16490         break;
16491     }
16492
16493     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
16494         if(WhiteOnMove(forwardMostMove))
16495              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16496         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16497     }
16498
16499     tickStartTM = now;
16500     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16501       whiteTimeRemaining : blackTimeRemaining);
16502     StartClockTimer(intendedTickLength);
16503 }
16504
16505
16506 /* Stop both clocks */
16507 void
16508 StopClocks ()
16509 {
16510     long lastTickLength;
16511     TimeMark now;
16512
16513     if (!StopClockTimer()) return;
16514     if (!appData.clockMode) return;
16515
16516     GetTimeMark(&now);
16517
16518     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16519     if (WhiteOnMove(forwardMostMove)) {
16520         if(whiteNPS >= 0) lastTickLength = 0;
16521         whiteTimeRemaining -= lastTickLength;
16522         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
16523     } else {
16524         if(blackNPS >= 0) lastTickLength = 0;
16525         blackTimeRemaining -= lastTickLength;
16526         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
16527     }
16528     CheckFlags();
16529 }
16530
16531 /* Start clock of player on move.  Time may have been reset, so
16532    if clock is already running, stop and restart it. */
16533 void
16534 StartClocks ()
16535 {
16536     (void) StopClockTimer(); /* in case it was running already */
16537     DisplayBothClocks();
16538     if (CheckFlags()) return;
16539
16540     if (!appData.clockMode) return;
16541     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
16542
16543     GetTimeMark(&tickStartTM);
16544     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16545       whiteTimeRemaining : blackTimeRemaining);
16546
16547    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
16548     whiteNPS = blackNPS = -1;
16549     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
16550        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
16551         whiteNPS = first.nps;
16552     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
16553        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
16554         blackNPS = first.nps;
16555     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
16556         whiteNPS = second.nps;
16557     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
16558         blackNPS = second.nps;
16559     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
16560
16561     StartClockTimer(intendedTickLength);
16562 }
16563
16564 char *
16565 TimeString (long ms)
16566 {
16567     long second, minute, hour, day;
16568     char *sign = "";
16569     static char buf[32];
16570
16571     if (ms > 0 && ms <= 9900) {
16572       /* convert milliseconds to tenths, rounding up */
16573       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
16574
16575       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
16576       return buf;
16577     }
16578
16579     /* convert milliseconds to seconds, rounding up */
16580     /* use floating point to avoid strangeness of integer division
16581        with negative dividends on many machines */
16582     second = (long) floor(((double) (ms + 999L)) / 1000.0);
16583
16584     if (second < 0) {
16585         sign = "-";
16586         second = -second;
16587     }
16588
16589     day = second / (60 * 60 * 24);
16590     second = second % (60 * 60 * 24);
16591     hour = second / (60 * 60);
16592     second = second % (60 * 60);
16593     minute = second / 60;
16594     second = second % 60;
16595
16596     if (day > 0)
16597       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
16598               sign, day, hour, minute, second);
16599     else if (hour > 0)
16600       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
16601     else
16602       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
16603
16604     return buf;
16605 }
16606
16607
16608 /*
16609  * This is necessary because some C libraries aren't ANSI C compliant yet.
16610  */
16611 char *
16612 StrStr (char *string, char *match)
16613 {
16614     int i, length;
16615
16616     length = strlen(match);
16617
16618     for (i = strlen(string) - length; i >= 0; i--, string++)
16619       if (!strncmp(match, string, length))
16620         return string;
16621
16622     return NULL;
16623 }
16624
16625 char *
16626 StrCaseStr (char *string, char *match)
16627 {
16628     int i, j, length;
16629
16630     length = strlen(match);
16631
16632     for (i = strlen(string) - length; i >= 0; i--, string++) {
16633         for (j = 0; j < length; j++) {
16634             if (ToLower(match[j]) != ToLower(string[j]))
16635               break;
16636         }
16637         if (j == length) return string;
16638     }
16639
16640     return NULL;
16641 }
16642
16643 #ifndef _amigados
16644 int
16645 StrCaseCmp (char *s1, char *s2)
16646 {
16647     char c1, c2;
16648
16649     for (;;) {
16650         c1 = ToLower(*s1++);
16651         c2 = ToLower(*s2++);
16652         if (c1 > c2) return 1;
16653         if (c1 < c2) return -1;
16654         if (c1 == NULLCHAR) return 0;
16655     }
16656 }
16657
16658
16659 int
16660 ToLower (int c)
16661 {
16662     return isupper(c) ? tolower(c) : c;
16663 }
16664
16665
16666 int
16667 ToUpper (int c)
16668 {
16669     return islower(c) ? toupper(c) : c;
16670 }
16671 #endif /* !_amigados    */
16672
16673 char *
16674 StrSave (char *s)
16675 {
16676   char *ret;
16677
16678   if ((ret = (char *) malloc(strlen(s) + 1)))
16679     {
16680       safeStrCpy(ret, s, strlen(s)+1);
16681     }
16682   return ret;
16683 }
16684
16685 char *
16686 StrSavePtr (char *s, char **savePtr)
16687 {
16688     if (*savePtr) {
16689         free(*savePtr);
16690     }
16691     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
16692       safeStrCpy(*savePtr, s, strlen(s)+1);
16693     }
16694     return(*savePtr);
16695 }
16696
16697 char *
16698 PGNDate ()
16699 {
16700     time_t clock;
16701     struct tm *tm;
16702     char buf[MSG_SIZ];
16703
16704     clock = time((time_t *)NULL);
16705     tm = localtime(&clock);
16706     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
16707             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
16708     return StrSave(buf);
16709 }
16710
16711
16712 char *
16713 PositionToFEN (int move, char *overrideCastling)
16714 {
16715     int i, j, fromX, fromY, toX, toY;
16716     int whiteToPlay;
16717     char buf[MSG_SIZ];
16718     char *p, *q;
16719     int emptycount;
16720     ChessSquare piece;
16721
16722     whiteToPlay = (gameMode == EditPosition) ?
16723       !blackPlaysFirst : (move % 2 == 0);
16724     p = buf;
16725
16726     /* Piece placement data */
16727     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16728         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
16729         emptycount = 0;
16730         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16731             if (boards[move][i][j] == EmptySquare) {
16732                 emptycount++;
16733             } else { ChessSquare piece = boards[move][i][j];
16734                 if (emptycount > 0) {
16735                     if(emptycount<10) /* [HGM] can be >= 10 */
16736                         *p++ = '0' + emptycount;
16737                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16738                     emptycount = 0;
16739                 }
16740                 if(PieceToChar(piece) == '+') {
16741                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
16742                     *p++ = '+';
16743                     piece = (ChessSquare)(DEMOTED piece);
16744                 }
16745                 *p++ = PieceToChar(piece);
16746                 if(p[-1] == '~') {
16747                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
16748                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
16749                     *p++ = '~';
16750                 }
16751             }
16752         }
16753         if (emptycount > 0) {
16754             if(emptycount<10) /* [HGM] can be >= 10 */
16755                 *p++ = '0' + emptycount;
16756             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16757             emptycount = 0;
16758         }
16759         *p++ = '/';
16760     }
16761     *(p - 1) = ' ';
16762
16763     /* [HGM] print Crazyhouse or Shogi holdings */
16764     if( gameInfo.holdingsWidth ) {
16765         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
16766         q = p;
16767         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
16768             piece = boards[move][i][BOARD_WIDTH-1];
16769             if( piece != EmptySquare )
16770               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
16771                   *p++ = PieceToChar(piece);
16772         }
16773         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
16774             piece = boards[move][BOARD_HEIGHT-i-1][0];
16775             if( piece != EmptySquare )
16776               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
16777                   *p++ = PieceToChar(piece);
16778         }
16779
16780         if( q == p ) *p++ = '-';
16781         *p++ = ']';
16782         *p++ = ' ';
16783     }
16784
16785     /* Active color */
16786     *p++ = whiteToPlay ? 'w' : 'b';
16787     *p++ = ' ';
16788
16789   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
16790     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
16791   } else {
16792   if(nrCastlingRights) {
16793      q = p;
16794      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
16795        /* [HGM] write directly from rights */
16796            if(boards[move][CASTLING][2] != NoRights &&
16797               boards[move][CASTLING][0] != NoRights   )
16798                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
16799            if(boards[move][CASTLING][2] != NoRights &&
16800               boards[move][CASTLING][1] != NoRights   )
16801                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
16802            if(boards[move][CASTLING][5] != NoRights &&
16803               boards[move][CASTLING][3] != NoRights   )
16804                 *p++ = boards[move][CASTLING][3] + AAA;
16805            if(boards[move][CASTLING][5] != NoRights &&
16806               boards[move][CASTLING][4] != NoRights   )
16807                 *p++ = boards[move][CASTLING][4] + AAA;
16808      } else {
16809
16810         /* [HGM] write true castling rights */
16811         if( nrCastlingRights == 6 ) {
16812             int q, k=0;
16813             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
16814                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
16815             q = (boards[move][CASTLING][1] == BOARD_LEFT &&
16816                  boards[move][CASTLING][2] != NoRights  );
16817             if(gameInfo.variant == VariantSChess) { // for S-Chess, indicate all vrgin backrank pieces
16818                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_RGHT]; // count white held pieces
16819                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
16820                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
16821                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
16822             }
16823             if(q) *p++ = 'Q';
16824             k = 0;
16825             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
16826                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
16827             q = (boards[move][CASTLING][4] == BOARD_LEFT &&
16828                  boards[move][CASTLING][5] != NoRights  );
16829             if(gameInfo.variant == VariantSChess) {
16830                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_LEFT-1]; // count black held pieces
16831                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
16832                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
16833                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
16834             }
16835             if(q) *p++ = 'q';
16836         }
16837      }
16838      if (q == p) *p++ = '-'; /* No castling rights */
16839      *p++ = ' ';
16840   }
16841
16842   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16843      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16844     /* En passant target square */
16845     if (move > backwardMostMove) {
16846         fromX = moveList[move - 1][0] - AAA;
16847         fromY = moveList[move - 1][1] - ONE;
16848         toX = moveList[move - 1][2] - AAA;
16849         toY = moveList[move - 1][3] - ONE;
16850         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
16851             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
16852             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
16853             fromX == toX) {
16854             /* 2-square pawn move just happened */
16855             *p++ = toX + AAA;
16856             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16857         } else {
16858             *p++ = '-';
16859         }
16860     } else if(move == backwardMostMove) {
16861         // [HGM] perhaps we should always do it like this, and forget the above?
16862         if((signed char)boards[move][EP_STATUS] >= 0) {
16863             *p++ = boards[move][EP_STATUS] + AAA;
16864             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16865         } else {
16866             *p++ = '-';
16867         }
16868     } else {
16869         *p++ = '-';
16870     }
16871     *p++ = ' ';
16872   }
16873   }
16874
16875     /* [HGM] find reversible plies */
16876     {   int i = 0, j=move;
16877
16878         if (appData.debugMode) { int k;
16879             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
16880             for(k=backwardMostMove; k<=forwardMostMove; k++)
16881                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
16882
16883         }
16884
16885         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
16886         if( j == backwardMostMove ) i += initialRulePlies;
16887         sprintf(p, "%d ", i);
16888         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
16889     }
16890     /* Fullmove number */
16891     sprintf(p, "%d", (move / 2) + 1);
16892
16893     return StrSave(buf);
16894 }
16895
16896 Boolean
16897 ParseFEN (Board board, int *blackPlaysFirst, char *fen)
16898 {
16899     int i, j;
16900     char *p, c;
16901     int emptycount, virgin[BOARD_FILES];
16902     ChessSquare piece;
16903
16904     p = fen;
16905
16906     /* [HGM] by default clear Crazyhouse holdings, if present */
16907     if(gameInfo.holdingsWidth) {
16908        for(i=0; i<BOARD_HEIGHT; i++) {
16909            board[i][0]             = EmptySquare; /* black holdings */
16910            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
16911            board[i][1]             = (ChessSquare) 0; /* black counts */
16912            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
16913        }
16914     }
16915
16916     /* Piece placement data */
16917     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16918         j = 0;
16919         for (;;) {
16920             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
16921                 if (*p == '/') p++;
16922                 emptycount = gameInfo.boardWidth - j;
16923                 while (emptycount--)
16924                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16925                 break;
16926 #if(BOARD_FILES >= 10)
16927             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
16928                 p++; emptycount=10;
16929                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16930                 while (emptycount--)
16931                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16932 #endif
16933             } else if (isdigit(*p)) {
16934                 emptycount = *p++ - '0';
16935                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
16936                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16937                 while (emptycount--)
16938                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16939             } else if (*p == '+' || isalpha(*p)) {
16940                 if (j >= gameInfo.boardWidth) return FALSE;
16941                 if(*p=='+') {
16942                     piece = CharToPiece(*++p);
16943                     if(piece == EmptySquare) return FALSE; /* unknown piece */
16944                     piece = (ChessSquare) (PROMOTED piece ); p++;
16945                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
16946                 } else piece = CharToPiece(*p++);
16947
16948                 if(piece==EmptySquare) return FALSE; /* unknown piece */
16949                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
16950                     piece = (ChessSquare) (PROMOTED piece);
16951                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
16952                     p++;
16953                 }
16954                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
16955             } else {
16956                 return FALSE;
16957             }
16958         }
16959     }
16960     while (*p == '/' || *p == ' ') p++;
16961
16962     /* [HGM] look for Crazyhouse holdings here */
16963     while(*p==' ') p++;
16964     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
16965         if(*p == '[') p++;
16966         if(*p == '-' ) p++; /* empty holdings */ else {
16967             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
16968             /* if we would allow FEN reading to set board size, we would   */
16969             /* have to add holdings and shift the board read so far here   */
16970             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
16971                 p++;
16972                 if((int) piece >= (int) BlackPawn ) {
16973                     i = (int)piece - (int)BlackPawn;
16974                     i = PieceToNumber((ChessSquare)i);
16975                     if( i >= gameInfo.holdingsSize ) return FALSE;
16976                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
16977                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
16978                 } else {
16979                     i = (int)piece - (int)WhitePawn;
16980                     i = PieceToNumber((ChessSquare)i);
16981                     if( i >= gameInfo.holdingsSize ) return FALSE;
16982                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
16983                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
16984                 }
16985             }
16986         }
16987         if(*p == ']') p++;
16988     }
16989
16990     while(*p == ' ') p++;
16991
16992     /* Active color */
16993     c = *p++;
16994     if(appData.colorNickNames) {
16995       if( c == appData.colorNickNames[0] ) c = 'w'; else
16996       if( c == appData.colorNickNames[1] ) c = 'b';
16997     }
16998     switch (c) {
16999       case 'w':
17000         *blackPlaysFirst = FALSE;
17001         break;
17002       case 'b':
17003         *blackPlaysFirst = TRUE;
17004         break;
17005       default:
17006         return FALSE;
17007     }
17008
17009     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
17010     /* return the extra info in global variiables             */
17011
17012     /* set defaults in case FEN is incomplete */
17013     board[EP_STATUS] = EP_UNKNOWN;
17014     for(i=0; i<nrCastlingRights; i++ ) {
17015         board[CASTLING][i] =
17016             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
17017     }   /* assume possible unless obviously impossible */
17018     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
17019     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
17020     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
17021                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
17022     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
17023     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
17024     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
17025                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
17026     FENrulePlies = 0;
17027
17028     while(*p==' ') p++;
17029     if(nrCastlingRights) {
17030       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
17031       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
17032           /* castling indicator present, so default becomes no castlings */
17033           for(i=0; i<nrCastlingRights; i++ ) {
17034                  board[CASTLING][i] = NoRights;
17035           }
17036       }
17037       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
17038              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess) &&
17039              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
17040              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
17041         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
17042
17043         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
17044             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
17045             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
17046         }
17047         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
17048             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
17049         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
17050                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
17051         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
17052                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
17053         switch(c) {
17054           case'K':
17055               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
17056               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
17057               board[CASTLING][2] = whiteKingFile;
17058               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
17059               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17060               break;
17061           case'Q':
17062               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
17063               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
17064               board[CASTLING][2] = whiteKingFile;
17065               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
17066               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17067               break;
17068           case'k':
17069               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
17070               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
17071               board[CASTLING][5] = blackKingFile;
17072               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
17073               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17074               break;
17075           case'q':
17076               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
17077               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
17078               board[CASTLING][5] = blackKingFile;
17079               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
17080               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17081           case '-':
17082               break;
17083           default: /* FRC castlings */
17084               if(c >= 'a') { /* black rights */
17085                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
17086                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17087                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
17088                   if(i == BOARD_RGHT) break;
17089                   board[CASTLING][5] = i;
17090                   c -= AAA;
17091                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
17092                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
17093                   if(c > i)
17094                       board[CASTLING][3] = c;
17095                   else
17096                       board[CASTLING][4] = c;
17097               } else { /* white rights */
17098                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
17099                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17100                     if(board[0][i] == WhiteKing) break;
17101                   if(i == BOARD_RGHT) break;
17102                   board[CASTLING][2] = i;
17103                   c -= AAA - 'a' + 'A';
17104                   if(board[0][c] >= WhiteKing) break;
17105                   if(c > i)
17106                       board[CASTLING][0] = c;
17107                   else
17108                       board[CASTLING][1] = c;
17109               }
17110         }
17111       }
17112       for(i=0; i<nrCastlingRights; i++)
17113         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
17114       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = virgin[i];
17115     if (appData.debugMode) {
17116         fprintf(debugFP, "FEN castling rights:");
17117         for(i=0; i<nrCastlingRights; i++)
17118         fprintf(debugFP, " %d", board[CASTLING][i]);
17119         fprintf(debugFP, "\n");
17120     }
17121
17122       while(*p==' ') p++;
17123     }
17124
17125     /* read e.p. field in games that know e.p. capture */
17126     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17127        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
17128       if(*p=='-') {
17129         p++; board[EP_STATUS] = EP_NONE;
17130       } else {
17131          char c = *p++ - AAA;
17132
17133          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
17134          if(*p >= '0' && *p <='9') p++;
17135          board[EP_STATUS] = c;
17136       }
17137     }
17138
17139
17140     if(sscanf(p, "%d", &i) == 1) {
17141         FENrulePlies = i; /* 50-move ply counter */
17142         /* (The move number is still ignored)    */
17143     }
17144
17145     return TRUE;
17146 }
17147
17148 void
17149 EditPositionPasteFEN (char *fen)
17150 {
17151   if (fen != NULL) {
17152     Board initial_position;
17153
17154     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
17155       DisplayError(_("Bad FEN position in clipboard"), 0);
17156       return ;
17157     } else {
17158       int savedBlackPlaysFirst = blackPlaysFirst;
17159       EditPositionEvent();
17160       blackPlaysFirst = savedBlackPlaysFirst;
17161       CopyBoard(boards[0], initial_position);
17162       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
17163       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
17164       DisplayBothClocks();
17165       DrawPosition(FALSE, boards[currentMove]);
17166     }
17167   }
17168 }
17169
17170 static char cseq[12] = "\\   ";
17171
17172 Boolean
17173 set_cont_sequence (char *new_seq)
17174 {
17175     int len;
17176     Boolean ret;
17177
17178     // handle bad attempts to set the sequence
17179         if (!new_seq)
17180                 return 0; // acceptable error - no debug
17181
17182     len = strlen(new_seq);
17183     ret = (len > 0) && (len < sizeof(cseq));
17184     if (ret)
17185       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
17186     else if (appData.debugMode)
17187       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
17188     return ret;
17189 }
17190
17191 /*
17192     reformat a source message so words don't cross the width boundary.  internal
17193     newlines are not removed.  returns the wrapped size (no null character unless
17194     included in source message).  If dest is NULL, only calculate the size required
17195     for the dest buffer.  lp argument indicats line position upon entry, and it's
17196     passed back upon exit.
17197 */
17198 int
17199 wrap (char *dest, char *src, int count, int width, int *lp)
17200 {
17201     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
17202
17203     cseq_len = strlen(cseq);
17204     old_line = line = *lp;
17205     ansi = len = clen = 0;
17206
17207     for (i=0; i < count; i++)
17208     {
17209         if (src[i] == '\033')
17210             ansi = 1;
17211
17212         // if we hit the width, back up
17213         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
17214         {
17215             // store i & len in case the word is too long
17216             old_i = i, old_len = len;
17217
17218             // find the end of the last word
17219             while (i && src[i] != ' ' && src[i] != '\n')
17220             {
17221                 i--;
17222                 len--;
17223             }
17224
17225             // word too long?  restore i & len before splitting it
17226             if ((old_i-i+clen) >= width)
17227             {
17228                 i = old_i;
17229                 len = old_len;
17230             }
17231
17232             // extra space?
17233             if (i && src[i-1] == ' ')
17234                 len--;
17235
17236             if (src[i] != ' ' && src[i] != '\n')
17237             {
17238                 i--;
17239                 if (len)
17240                     len--;
17241             }
17242
17243             // now append the newline and continuation sequence
17244             if (dest)
17245                 dest[len] = '\n';
17246             len++;
17247             if (dest)
17248                 strncpy(dest+len, cseq, cseq_len);
17249             len += cseq_len;
17250             line = cseq_len;
17251             clen = cseq_len;
17252             continue;
17253         }
17254
17255         if (dest)
17256             dest[len] = src[i];
17257         len++;
17258         if (!ansi)
17259             line++;
17260         if (src[i] == '\n')
17261             line = 0;
17262         if (src[i] == 'm')
17263             ansi = 0;
17264     }
17265     if (dest && appData.debugMode)
17266     {
17267         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
17268             count, width, line, len, *lp);
17269         show_bytes(debugFP, src, count);
17270         fprintf(debugFP, "\ndest: ");
17271         show_bytes(debugFP, dest, len);
17272         fprintf(debugFP, "\n");
17273     }
17274     *lp = dest ? line : old_line;
17275
17276     return len;
17277 }
17278
17279 // [HGM] vari: routines for shelving variations
17280 Boolean modeRestore = FALSE;
17281
17282 void
17283 PushInner (int firstMove, int lastMove)
17284 {
17285         int i, j, nrMoves = lastMove - firstMove;
17286
17287         // push current tail of game on stack
17288         savedResult[storedGames] = gameInfo.result;
17289         savedDetails[storedGames] = gameInfo.resultDetails;
17290         gameInfo.resultDetails = NULL;
17291         savedFirst[storedGames] = firstMove;
17292         savedLast [storedGames] = lastMove;
17293         savedFramePtr[storedGames] = framePtr;
17294         framePtr -= nrMoves; // reserve space for the boards
17295         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
17296             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
17297             for(j=0; j<MOVE_LEN; j++)
17298                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
17299             for(j=0; j<2*MOVE_LEN; j++)
17300                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
17301             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
17302             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
17303             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
17304             pvInfoList[firstMove+i-1].depth = 0;
17305             commentList[framePtr+i] = commentList[firstMove+i];
17306             commentList[firstMove+i] = NULL;
17307         }
17308
17309         storedGames++;
17310         forwardMostMove = firstMove; // truncate game so we can start variation
17311 }
17312
17313 void
17314 PushTail (int firstMove, int lastMove)
17315 {
17316         if(appData.icsActive) { // only in local mode
17317                 forwardMostMove = currentMove; // mimic old ICS behavior
17318                 return;
17319         }
17320         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
17321
17322         PushInner(firstMove, lastMove);
17323         if(storedGames == 1) GreyRevert(FALSE);
17324         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
17325 }
17326
17327 void
17328 PopInner (Boolean annotate)
17329 {
17330         int i, j, nrMoves;
17331         char buf[8000], moveBuf[20];
17332
17333         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
17334         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
17335         nrMoves = savedLast[storedGames] - currentMove;
17336         if(annotate) {
17337                 int cnt = 10;
17338                 if(!WhiteOnMove(currentMove))
17339                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
17340                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
17341                 for(i=currentMove; i<forwardMostMove; i++) {
17342                         if(WhiteOnMove(i))
17343                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
17344                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
17345                         strcat(buf, moveBuf);
17346                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
17347                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
17348                 }
17349                 strcat(buf, ")");
17350         }
17351         for(i=1; i<=nrMoves; i++) { // copy last variation back
17352             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
17353             for(j=0; j<MOVE_LEN; j++)
17354                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
17355             for(j=0; j<2*MOVE_LEN; j++)
17356                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
17357             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
17358             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
17359             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
17360             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
17361             commentList[currentMove+i] = commentList[framePtr+i];
17362             commentList[framePtr+i] = NULL;
17363         }
17364         if(annotate) AppendComment(currentMove+1, buf, FALSE);
17365         framePtr = savedFramePtr[storedGames];
17366         gameInfo.result = savedResult[storedGames];
17367         if(gameInfo.resultDetails != NULL) {
17368             free(gameInfo.resultDetails);
17369       }
17370         gameInfo.resultDetails = savedDetails[storedGames];
17371         forwardMostMove = currentMove + nrMoves;
17372 }
17373
17374 Boolean
17375 PopTail (Boolean annotate)
17376 {
17377         if(appData.icsActive) return FALSE; // only in local mode
17378         if(!storedGames) return FALSE; // sanity
17379         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
17380
17381         PopInner(annotate);
17382         if(currentMove < forwardMostMove) ForwardEvent(); else
17383         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
17384
17385         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
17386         return TRUE;
17387 }
17388
17389 void
17390 CleanupTail ()
17391 {       // remove all shelved variations
17392         int i;
17393         for(i=0; i<storedGames; i++) {
17394             if(savedDetails[i])
17395                 free(savedDetails[i]);
17396             savedDetails[i] = NULL;
17397         }
17398         for(i=framePtr; i<MAX_MOVES; i++) {
17399                 if(commentList[i]) free(commentList[i]);
17400                 commentList[i] = NULL;
17401         }
17402         framePtr = MAX_MOVES-1;
17403         storedGames = 0;
17404 }
17405
17406 void
17407 LoadVariation (int index, char *text)
17408 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
17409         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
17410         int level = 0, move;
17411
17412         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
17413         // first find outermost bracketing variation
17414         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
17415             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
17416                 if(*p == '{') wait = '}'; else
17417                 if(*p == '[') wait = ']'; else
17418                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
17419                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
17420             }
17421             if(*p == wait) wait = NULLCHAR; // closing ]} found
17422             p++;
17423         }
17424         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
17425         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
17426         end[1] = NULLCHAR; // clip off comment beyond variation
17427         ToNrEvent(currentMove-1);
17428         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
17429         // kludge: use ParsePV() to append variation to game
17430         move = currentMove;
17431         ParsePV(start, TRUE, TRUE);
17432         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
17433         ClearPremoveHighlights();
17434         CommentPopDown();
17435         ToNrEvent(currentMove+1);
17436 }
17437