Allow ICS nickname as positional argument
[xboard.git] / backend.c
1 /*
2  * backend.c -- Common back end for X and Windows NT versions of
3  *
4  * Copyright 1991 by Digital Equipment Corporation, Maynard,
5  * Massachusetts.
6  *
7  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8  * 2007, 2008, 2009, 2010, 2011, 2012 Free Software Foundation, Inc.
9  *
10  * Enhancements Copyright 2005 Alessandro Scotti
11  *
12  * The following terms apply to Digital Equipment Corporation's copyright
13  * interest in XBoard:
14  * ------------------------------------------------------------------------
15  * All Rights Reserved
16  *
17  * Permission to use, copy, modify, and distribute this software and its
18  * documentation for any purpose and without fee is hereby granted,
19  * provided that the above copyright notice appear in all copies and that
20  * both that copyright notice and this permission notice appear in
21  * supporting documentation, and that the name of Digital not be
22  * used in advertising or publicity pertaining to distribution of the
23  * software without specific, written prior permission.
24  *
25  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
31  * SOFTWARE.
32  * ------------------------------------------------------------------------
33  *
34  * The following terms apply to the enhanced version of XBoard
35  * distributed by the Free Software Foundation:
36  * ------------------------------------------------------------------------
37  *
38  * GNU XBoard is free software: you can redistribute it and/or modify
39  * it under the terms of the GNU General Public License as published by
40  * the Free Software Foundation, either version 3 of the License, or (at
41  * your option) any later version.
42  *
43  * GNU XBoard is distributed in the hope that it will be useful, but
44  * WITHOUT ANY WARRANTY; without even the implied warranty of
45  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46  * General Public License for more details.
47  *
48  * You should have received a copy of the GNU General Public License
49  * along with this program. If not, see http://www.gnu.org/licenses/.  *
50  *
51  *------------------------------------------------------------------------
52  ** See the file ChangeLog for a revision history.  */
53
54 /* [AS] Also useful here for debugging */
55 #ifdef WIN32
56 #include <windows.h>
57
58 int flock(int f, int code);
59 #define LOCK_EX 2
60 #define SLASH '\\'
61
62 #else
63
64 #include <sys/file.h>
65 #define SLASH '/'
66
67 #endif
68
69 #include "config.h"
70
71 #include <assert.h>
72 #include <stdio.h>
73 #include <ctype.h>
74 #include <errno.h>
75 #include <sys/types.h>
76 #include <sys/stat.h>
77 #include <math.h>
78 #include <ctype.h>
79
80 #if STDC_HEADERS
81 # include <stdlib.h>
82 # include <string.h>
83 # include <stdarg.h>
84 #else /* not STDC_HEADERS */
85 # if HAVE_STRING_H
86 #  include <string.h>
87 # else /* not HAVE_STRING_H */
88 #  include <strings.h>
89 # endif /* not HAVE_STRING_H */
90 #endif /* not STDC_HEADERS */
91
92 #if HAVE_SYS_FCNTL_H
93 # include <sys/fcntl.h>
94 #else /* not HAVE_SYS_FCNTL_H */
95 # if HAVE_FCNTL_H
96 #  include <fcntl.h>
97 # endif /* HAVE_FCNTL_H */
98 #endif /* not HAVE_SYS_FCNTL_H */
99
100 #if TIME_WITH_SYS_TIME
101 # include <sys/time.h>
102 # include <time.h>
103 #else
104 # if HAVE_SYS_TIME_H
105 #  include <sys/time.h>
106 # else
107 #  include <time.h>
108 # endif
109 #endif
110
111 #if defined(_amigados) && !defined(__GNUC__)
112 struct timezone {
113     int tz_minuteswest;
114     int tz_dsttime;
115 };
116 extern int gettimeofday(struct timeval *, struct timezone *);
117 #endif
118
119 #if HAVE_UNISTD_H
120 # include <unistd.h>
121 #endif
122
123 #include "common.h"
124 #include "frontend.h"
125 #include "backend.h"
126 #include "parser.h"
127 #include "moves.h"
128 #if ZIPPY
129 # include "zippy.h"
130 #endif
131 #include "backendz.h"
132 #include "gettext.h"
133
134 #ifdef ENABLE_NLS
135 # define _(s) gettext (s)
136 # define N_(s) gettext_noop (s)
137 # define T_(s) gettext(s)
138 #else
139 # ifdef WIN32
140 #   define _(s) T_(s)
141 #   define N_(s) s
142 # else
143 #   define _(s) (s)
144 #   define N_(s) s
145 #   define T_(s) s
146 # endif
147 #endif
148
149
150 int establish P((void));
151 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
152                          char *buf, int count, int error));
153 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
154                       char *buf, int count, int error));
155 void SendToICS P((char *s));
156 void SendToICSDelayed P((char *s, long msdelay));
157 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
158 void HandleMachineMove P((char *message, ChessProgramState *cps));
159 int AutoPlayOneMove P((void));
160 int LoadGameOneMove P((ChessMove readAhead));
161 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
162 int LoadPositionFromFile P((char *filename, int n, char *title));
163 int SavePositionToFile P((char *filename));
164 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
165 void ShowMove P((int fromX, int fromY, int toX, int toY));
166 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
167                    /*char*/int promoChar));
168 void BackwardInner P((int target));
169 void ForwardInner P((int target));
170 int Adjudicate P((ChessProgramState *cps));
171 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
172 void EditPositionDone P((Boolean fakeRights));
173 void PrintOpponents P((FILE *fp));
174 void PrintPosition P((FILE *fp, int move));
175 void StartChessProgram P((ChessProgramState *cps));
176 void SendToProgram P((char *message, ChessProgramState *cps));
177 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
178 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
179                            char *buf, int count, int error));
180 void SendTimeControl P((ChessProgramState *cps,
181                         int mps, long tc, int inc, int sd, int st));
182 char *TimeControlTagValue P((void));
183 void Attention P((ChessProgramState *cps));
184 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
185 int ResurrectChessProgram P((void));
186 void DisplayComment P((int moveNumber, char *text));
187 void DisplayMove P((int moveNumber));
188
189 void ParseGameHistory P((char *game));
190 void ParseBoard12 P((char *string));
191 void KeepAlive P((void));
192 void StartClocks P((void));
193 void SwitchClocks P((int nr));
194 void StopClocks P((void));
195 void ResetClocks P((void));
196 char *PGNDate P((void));
197 void SetGameInfo P((void));
198 int RegisterMove P((void));
199 void MakeRegisteredMove P((void));
200 void TruncateGame P((void));
201 int looking_at P((char *, int *, char *));
202 void CopyPlayerNameIntoFileName P((char **, char *));
203 char *SavePart P((char *));
204 int SaveGameOldStyle P((FILE *));
205 int SaveGamePGN P((FILE *));
206 int CheckFlags P((void));
207 long NextTickLength P((long));
208 void CheckTimeControl P((void));
209 void show_bytes P((FILE *, char *, int));
210 int string_to_rating P((char *str));
211 void ParseFeatures P((char* args, ChessProgramState *cps));
212 void InitBackEnd3 P((void));
213 void FeatureDone P((ChessProgramState* cps, int val));
214 void InitChessProgram P((ChessProgramState *cps, int setup));
215 void OutputKibitz(int window, char *text);
216 int PerpetualChase(int first, int last);
217 int EngineOutputIsUp();
218 void InitDrawingSizes(int x, int y);
219 void NextMatchGame P((void));
220 int NextTourneyGame P((int nr, int *swap));
221 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
222 FILE *WriteTourneyFile P((char *results, FILE *f));
223 void DisplayTwoMachinesTitle P(());
224 static void ExcludeClick P((int index));
225 void ToggleSecond P((void));
226
227 #ifdef WIN32
228        extern void ConsoleCreate();
229 #endif
230
231 ChessProgramState *WhitePlayer();
232 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
233 int VerifyDisplayMode P(());
234
235 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
236 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
237 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
238 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
239 void ics_update_width P((int new_width));
240 extern char installDir[MSG_SIZ];
241 VariantClass startVariant; /* [HGM] nicks: initial variant */
242 Boolean abortMatch;
243
244 extern int tinyLayout, smallLayout;
245 ChessProgramStats programStats;
246 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
247 int endPV = -1;
248 static int exiting = 0; /* [HGM] moved to top */
249 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
250 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
251 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
252 int partnerHighlight[2];
253 Boolean partnerBoardValid = 0;
254 char partnerStatus[MSG_SIZ];
255 Boolean partnerUp;
256 Boolean originalFlip;
257 Boolean twoBoards = 0;
258 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
259 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
260 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
261 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
262 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
263 int opponentKibitzes;
264 int lastSavedGame; /* [HGM] save: ID of game */
265 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
266 extern int chatCount;
267 int chattingPartner;
268 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
269 char lastMsg[MSG_SIZ];
270 ChessSquare pieceSweep = EmptySquare;
271 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
272 int promoDefaultAltered;
273 int keepInfo = 0; /* [HGM] to protect PGN tags in auto-step game analysis */
274
275 /* States for ics_getting_history */
276 #define H_FALSE 0
277 #define H_REQUESTED 1
278 #define H_GOT_REQ_HEADER 2
279 #define H_GOT_UNREQ_HEADER 3
280 #define H_GETTING_MOVES 4
281 #define H_GOT_UNWANTED_HEADER 5
282
283 /* whosays values for GameEnds */
284 #define GE_ICS 0
285 #define GE_ENGINE 1
286 #define GE_PLAYER 2
287 #define GE_FILE 3
288 #define GE_XBOARD 4
289 #define GE_ENGINE1 5
290 #define GE_ENGINE2 6
291
292 /* Maximum number of games in a cmail message */
293 #define CMAIL_MAX_GAMES 20
294
295 /* Different types of move when calling RegisterMove */
296 #define CMAIL_MOVE   0
297 #define CMAIL_RESIGN 1
298 #define CMAIL_DRAW   2
299 #define CMAIL_ACCEPT 3
300
301 /* Different types of result to remember for each game */
302 #define CMAIL_NOT_RESULT 0
303 #define CMAIL_OLD_RESULT 1
304 #define CMAIL_NEW_RESULT 2
305
306 /* Telnet protocol constants */
307 #define TN_WILL 0373
308 #define TN_WONT 0374
309 #define TN_DO   0375
310 #define TN_DONT 0376
311 #define TN_IAC  0377
312 #define TN_ECHO 0001
313 #define TN_SGA  0003
314 #define TN_PORT 23
315
316 char*
317 safeStrCpy (char *dst, const char *src, size_t count)
318 { // [HGM] made safe
319   int i;
320   assert( dst != NULL );
321   assert( src != NULL );
322   assert( count > 0 );
323
324   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
325   if(  i == count && dst[count-1] != NULLCHAR)
326     {
327       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
328       if(appData.debugMode)
329       fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
330     }
331
332   return dst;
333 }
334
335 /* Some compiler can't cast u64 to double
336  * This function do the job for us:
337
338  * We use the highest bit for cast, this only
339  * works if the highest bit is not
340  * in use (This should not happen)
341  *
342  * We used this for all compiler
343  */
344 double
345 u64ToDouble (u64 value)
346 {
347   double r;
348   u64 tmp = value & u64Const(0x7fffffffffffffff);
349   r = (double)(s64)tmp;
350   if (value & u64Const(0x8000000000000000))
351        r +=  9.2233720368547758080e18; /* 2^63 */
352  return r;
353 }
354
355 /* Fake up flags for now, as we aren't keeping track of castling
356    availability yet. [HGM] Change of logic: the flag now only
357    indicates the type of castlings allowed by the rule of the game.
358    The actual rights themselves are maintained in the array
359    castlingRights, as part of the game history, and are not probed
360    by this function.
361  */
362 int
363 PosFlags (index)
364 {
365   int flags = F_ALL_CASTLE_OK;
366   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
367   switch (gameInfo.variant) {
368   case VariantSuicide:
369     flags &= ~F_ALL_CASTLE_OK;
370   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
371     flags |= F_IGNORE_CHECK;
372   case VariantLosers:
373     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
374     break;
375   case VariantAtomic:
376     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
377     break;
378   case VariantKriegspiel:
379     flags |= F_KRIEGSPIEL_CAPTURE;
380     break;
381   case VariantCapaRandom:
382   case VariantFischeRandom:
383     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
384   case VariantNoCastle:
385   case VariantShatranj:
386   case VariantCourier:
387   case VariantMakruk:
388   case VariantGrand:
389     flags &= ~F_ALL_CASTLE_OK;
390     break;
391   default:
392     break;
393   }
394   return flags;
395 }
396
397 FILE *gameFileFP, *debugFP, *serverFP;
398 char *currentDebugFile; // [HGM] debug split: to remember name
399
400 /*
401     [AS] Note: sometimes, the sscanf() function is used to parse the input
402     into a fixed-size buffer. Because of this, we must be prepared to
403     receive strings as long as the size of the input buffer, which is currently
404     set to 4K for Windows and 8K for the rest.
405     So, we must either allocate sufficiently large buffers here, or
406     reduce the size of the input buffer in the input reading part.
407 */
408
409 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
410 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
411 char thinkOutput1[MSG_SIZ*10];
412
413 ChessProgramState first, second, pairing;
414
415 /* premove variables */
416 int premoveToX = 0;
417 int premoveToY = 0;
418 int premoveFromX = 0;
419 int premoveFromY = 0;
420 int premovePromoChar = 0;
421 int gotPremove = 0;
422 Boolean alarmSounded;
423 /* end premove variables */
424
425 char *ics_prefix = "$";
426 enum ICS_TYPE ics_type = ICS_GENERIC;
427
428 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
429 int pauseExamForwardMostMove = 0;
430 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
431 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
432 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
433 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
434 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
435 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
436 int whiteFlag = FALSE, blackFlag = FALSE;
437 int userOfferedDraw = FALSE;
438 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
439 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
440 int cmailMoveType[CMAIL_MAX_GAMES];
441 long ics_clock_paused = 0;
442 ProcRef icsPR = NoProc, cmailPR = NoProc;
443 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
444 GameMode gameMode = BeginningOfGame;
445 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
446 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
447 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
448 int hiddenThinkOutputState = 0; /* [AS] */
449 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
450 int adjudicateLossPlies = 6;
451 char white_holding[64], black_holding[64];
452 TimeMark lastNodeCountTime;
453 long lastNodeCount=0;
454 int shiftKey, controlKey; // [HGM] set by mouse handler
455
456 int have_sent_ICS_logon = 0;
457 int movesPerSession;
458 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
459 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack, activePartnerTime;
460 Boolean adjustedClock;
461 long timeControl_2; /* [AS] Allow separate time controls */
462 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC, activePartner; /* [HGM] secondary TC: merge of MPS, TC and inc */
463 long timeRemaining[2][MAX_MOVES];
464 int matchGame = 0, nextGame = 0, roundNr = 0;
465 Boolean waitingForGame = FALSE;
466 TimeMark programStartTime, pauseStart;
467 char ics_handle[MSG_SIZ];
468 int have_set_title = 0;
469
470 /* animateTraining preserves the state of appData.animate
471  * when Training mode is activated. This allows the
472  * response to be animated when appData.animate == TRUE and
473  * appData.animateDragging == TRUE.
474  */
475 Boolean animateTraining;
476
477 GameInfo gameInfo;
478
479 AppData appData;
480
481 Board boards[MAX_MOVES];
482 /* [HGM] Following 7 needed for accurate legality tests: */
483 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
484 signed char  initialRights[BOARD_FILES];
485 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
486 int   initialRulePlies, FENrulePlies;
487 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
488 int loadFlag = 0;
489 Boolean shuffleOpenings;
490 int mute; // mute all sounds
491
492 // [HGM] vari: next 12 to save and restore variations
493 #define MAX_VARIATIONS 10
494 int framePtr = MAX_MOVES-1; // points to free stack entry
495 int storedGames = 0;
496 int savedFirst[MAX_VARIATIONS];
497 int savedLast[MAX_VARIATIONS];
498 int savedFramePtr[MAX_VARIATIONS];
499 char *savedDetails[MAX_VARIATIONS];
500 ChessMove savedResult[MAX_VARIATIONS];
501
502 void PushTail P((int firstMove, int lastMove));
503 Boolean PopTail P((Boolean annotate));
504 void PushInner P((int firstMove, int lastMove));
505 void PopInner P((Boolean annotate));
506 void CleanupTail P((void));
507
508 ChessSquare  FIDEArray[2][BOARD_FILES] = {
509     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
510         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
511     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
512         BlackKing, BlackBishop, BlackKnight, BlackRook }
513 };
514
515 ChessSquare twoKingsArray[2][BOARD_FILES] = {
516     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
517         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
518     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
519         BlackKing, BlackKing, BlackKnight, BlackRook }
520 };
521
522 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
523     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
524         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
525     { BlackRook, BlackMan, BlackBishop, BlackQueen,
526         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
527 };
528
529 ChessSquare SpartanArray[2][BOARD_FILES] = {
530     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
531         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
532     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
533         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
534 };
535
536 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
537     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
538         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
539     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
540         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
541 };
542
543 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
544     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
545         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
546     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
547         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
548 };
549
550 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
551     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
552         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
553     { BlackRook, BlackKnight, BlackMan, BlackFerz,
554         BlackKing, BlackMan, BlackKnight, BlackRook }
555 };
556
557
558 #if (BOARD_FILES>=10)
559 ChessSquare ShogiArray[2][BOARD_FILES] = {
560     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
561         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
562     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
563         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
564 };
565
566 ChessSquare XiangqiArray[2][BOARD_FILES] = {
567     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
568         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
569     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
570         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
571 };
572
573 ChessSquare CapablancaArray[2][BOARD_FILES] = {
574     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
575         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
576     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
577         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
578 };
579
580 ChessSquare GreatArray[2][BOARD_FILES] = {
581     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
582         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
583     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
584         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
585 };
586
587 ChessSquare JanusArray[2][BOARD_FILES] = {
588     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
589         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
590     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
591         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
592 };
593
594 ChessSquare GrandArray[2][BOARD_FILES] = {
595     { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
596         WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
597     { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
598         BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
599 };
600
601 #ifdef GOTHIC
602 ChessSquare GothicArray[2][BOARD_FILES] = {
603     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
604         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
605     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
606         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
607 };
608 #else // !GOTHIC
609 #define GothicArray CapablancaArray
610 #endif // !GOTHIC
611
612 #ifdef FALCON
613 ChessSquare FalconArray[2][BOARD_FILES] = {
614     { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
615         WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
616     { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
617         BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
618 };
619 #else // !FALCON
620 #define FalconArray CapablancaArray
621 #endif // !FALCON
622
623 #else // !(BOARD_FILES>=10)
624 #define XiangqiPosition FIDEArray
625 #define CapablancaArray FIDEArray
626 #define GothicArray FIDEArray
627 #define GreatArray FIDEArray
628 #endif // !(BOARD_FILES>=10)
629
630 #if (BOARD_FILES>=12)
631 ChessSquare CourierArray[2][BOARD_FILES] = {
632     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
633         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
634     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
635         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
636 };
637 #else // !(BOARD_FILES>=12)
638 #define CourierArray CapablancaArray
639 #endif // !(BOARD_FILES>=12)
640
641
642 Board initialPosition;
643
644
645 /* Convert str to a rating. Checks for special cases of "----",
646
647    "++++", etc. Also strips ()'s */
648 int
649 string_to_rating (char *str)
650 {
651   while(*str && !isdigit(*str)) ++str;
652   if (!*str)
653     return 0;   /* One of the special "no rating" cases */
654   else
655     return atoi(str);
656 }
657
658 void
659 ClearProgramStats ()
660 {
661     /* Init programStats */
662     programStats.movelist[0] = 0;
663     programStats.depth = 0;
664     programStats.nr_moves = 0;
665     programStats.moves_left = 0;
666     programStats.nodes = 0;
667     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
668     programStats.score = 0;
669     programStats.got_only_move = 0;
670     programStats.got_fail = 0;
671     programStats.line_is_book = 0;
672 }
673
674 void
675 CommonEngineInit ()
676 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
677     if (appData.firstPlaysBlack) {
678         first.twoMachinesColor = "black\n";
679         second.twoMachinesColor = "white\n";
680     } else {
681         first.twoMachinesColor = "white\n";
682         second.twoMachinesColor = "black\n";
683     }
684
685     first.other = &second;
686     second.other = &first;
687
688     { float norm = 1;
689         if(appData.timeOddsMode) {
690             norm = appData.timeOdds[0];
691             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
692         }
693         first.timeOdds  = appData.timeOdds[0]/norm;
694         second.timeOdds = appData.timeOdds[1]/norm;
695     }
696
697     if(programVersion) free(programVersion);
698     if (appData.noChessProgram) {
699         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
700         sprintf(programVersion, "%s", PACKAGE_STRING);
701     } else {
702       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
703       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
704       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
705     }
706 }
707
708 void
709 UnloadEngine (ChessProgramState *cps)
710 {
711         /* Kill off first chess program */
712         if (cps->isr != NULL)
713           RemoveInputSource(cps->isr);
714         cps->isr = NULL;
715
716         if (cps->pr != NoProc) {
717             ExitAnalyzeMode();
718             DoSleep( appData.delayBeforeQuit );
719             SendToProgram("quit\n", cps);
720             DoSleep( appData.delayAfterQuit );
721             DestroyChildProcess(cps->pr, cps->useSigterm);
722         }
723         cps->pr = NoProc;
724         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
725 }
726
727 void
728 ClearOptions (ChessProgramState *cps)
729 {
730     int i;
731     cps->nrOptions = cps->comboCnt = 0;
732     for(i=0; i<MAX_OPTIONS; i++) {
733         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
734         cps->option[i].textValue = 0;
735     }
736 }
737
738 char *engineNames[] = {
739   /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
740      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
741 N_("first"),
742   /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
743      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
744 N_("second")
745 };
746
747 void
748 InitEngine (ChessProgramState *cps, int n)
749 {   // [HGM] all engine initialiation put in a function that does one engine
750
751     ClearOptions(cps);
752
753     cps->which = engineNames[n];
754     cps->maybeThinking = FALSE;
755     cps->pr = NoProc;
756     cps->isr = NULL;
757     cps->sendTime = 2;
758     cps->sendDrawOffers = 1;
759
760     cps->program = appData.chessProgram[n];
761     cps->host = appData.host[n];
762     cps->dir = appData.directory[n];
763     cps->initString = appData.engInitString[n];
764     cps->computerString = appData.computerString[n];
765     cps->useSigint  = TRUE;
766     cps->useSigterm = TRUE;
767     cps->reuse = appData.reuse[n];
768     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
769     cps->useSetboard = FALSE;
770     cps->useSAN = FALSE;
771     cps->usePing = FALSE;
772     cps->lastPing = 0;
773     cps->lastPong = 0;
774     cps->usePlayother = FALSE;
775     cps->useColors = TRUE;
776     cps->useUsermove = FALSE;
777     cps->sendICS = FALSE;
778     cps->sendName = appData.icsActive;
779     cps->sdKludge = FALSE;
780     cps->stKludge = FALSE;
781     TidyProgramName(cps->program, cps->host, cps->tidy);
782     cps->matchWins = 0;
783     safeStrCpy(cps->variants, appData.variant, MSG_SIZ);
784     cps->analysisSupport = 2; /* detect */
785     cps->analyzing = FALSE;
786     cps->initDone = FALSE;
787
788     /* New features added by Tord: */
789     cps->useFEN960 = FALSE;
790     cps->useOOCastle = TRUE;
791     /* End of new features added by Tord. */
792     cps->fenOverride  = appData.fenOverride[n];
793
794     /* [HGM] time odds: set factor for each machine */
795     cps->timeOdds  = appData.timeOdds[n];
796
797     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
798     cps->accumulateTC = appData.accumulateTC[n];
799     cps->maxNrOfSessions = 1;
800
801     /* [HGM] debug */
802     cps->debug = FALSE;
803
804     cps->supportsNPS = UNKNOWN;
805     cps->memSize = FALSE;
806     cps->maxCores = FALSE;
807     cps->egtFormats[0] = NULLCHAR;
808
809     /* [HGM] options */
810     cps->optionSettings  = appData.engOptions[n];
811
812     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
813     cps->isUCI = appData.isUCI[n]; /* [AS] */
814     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
815
816     if (appData.protocolVersion[n] > PROTOVER
817         || appData.protocolVersion[n] < 1)
818       {
819         char buf[MSG_SIZ];
820         int len;
821
822         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
823                        appData.protocolVersion[n]);
824         if( (len >= MSG_SIZ) && appData.debugMode )
825           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
826
827         DisplayFatalError(buf, 0, 2);
828       }
829     else
830       {
831         cps->protocolVersion = appData.protocolVersion[n];
832       }
833
834     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
835     ParseFeatures(appData.featureDefaults, cps);
836 }
837
838 ChessProgramState *savCps;
839
840 void
841 LoadEngine ()
842 {
843     int i;
844     if(WaitForEngine(savCps, LoadEngine)) return;
845     CommonEngineInit(); // recalculate time odds
846     if(gameInfo.variant != StringToVariant(appData.variant)) {
847         // we changed variant when loading the engine; this forces us to reset
848         Reset(TRUE, savCps != &first);
849         EditGameEvent(); // for consistency with other path, as Reset changes mode
850     }
851     InitChessProgram(savCps, FALSE);
852     SendToProgram("force\n", savCps);
853     DisplayMessage("", "");
854     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
855     for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
856     ThawUI();
857     SetGNUMode();
858 }
859
860 void
861 ReplaceEngine (ChessProgramState *cps, int n)
862 {
863     EditGameEvent();
864     UnloadEngine(cps);
865     appData.noChessProgram = FALSE;
866     appData.clockMode = TRUE;
867     InitEngine(cps, n);
868     UpdateLogos(TRUE);
869     if(n) return; // only startup first engine immediately; second can wait
870     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
871     LoadEngine();
872 }
873
874 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
875 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
876
877 static char resetOptions[] = 
878         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
879         "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
880         "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 "
881         "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
882
883 void
884 FloatToFront(char **list, char *engineLine)
885 {
886     char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
887     int i=0;
888     if(appData.recentEngines <= 0) return;
889     TidyProgramName(engineLine, "localhost", tidy+1);
890     tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
891     strncpy(buf+1, *list, MSG_SIZ-50);
892     if(p = strstr(buf, tidy)) { // tidy name appears in list
893         q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
894         while(*p++ = *++q); // squeeze out
895     }
896     strcat(tidy, buf+1); // put list behind tidy name
897     p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
898     if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
899     ASSIGN(*list, tidy+1);
900 }
901
902 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
903
904 void
905 Load (ChessProgramState *cps, int i)
906 {
907     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ];
908     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
909         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
910         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
911         ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
912         FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
913         appData.firstProtocolVersion = PROTOVER;
914         ParseArgsFromString(buf);
915         SwapEngines(i);
916         ReplaceEngine(cps, i);
917         FloatToFront(&appData.recentEngineList, engineLine);
918         return;
919     }
920     p = engineName;
921     while(q = strchr(p, SLASH)) p = q+1;
922     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
923     if(engineDir[0] != NULLCHAR) {
924         ASSIGN(appData.directory[i], engineDir); p = engineName;
925     } else if(p != engineName) { // derive directory from engine path, when not given
926         p[-1] = 0;
927         ASSIGN(appData.directory[i], engineName);
928         p[-1] = SLASH;
929         if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
930     } else { ASSIGN(appData.directory[i], "."); }
931     if(params[0]) {
932         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
933         snprintf(command, MSG_SIZ, "%s %s", p, params);
934         p = command;
935     }
936     ASSIGN(appData.chessProgram[i], p);
937     appData.isUCI[i] = isUCI;
938     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
939     appData.hasOwnBookUCI[i] = hasBook;
940     if(!nickName[0]) useNick = FALSE;
941     if(useNick) ASSIGN(appData.pgnName[i], nickName);
942     if(addToList) {
943         int len;
944         char quote;
945         q = firstChessProgramNames;
946         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
947         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
948         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
949                         quote, p, quote, appData.directory[i], 
950                         useNick ? " -fn \"" : "",
951                         useNick ? nickName : "",
952                         useNick ? "\"" : "",
953                         v1 ? " -firstProtocolVersion 1" : "",
954                         hasBook ? "" : " -fNoOwnBookUCI",
955                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
956                         storeVariant ? " -variant " : "",
957                         storeVariant ? VariantName(gameInfo.variant) : "");
958         if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
959         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
960         if(insert != q) insert[-1] = NULLCHAR;
961         snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
962         if(q)   free(q);
963         FloatToFront(&appData.recentEngineList, buf);
964     }
965     ReplaceEngine(cps, i);
966 }
967
968 void
969 InitTimeControls ()
970 {
971     int matched, min, sec;
972     /*
973      * Parse timeControl resource
974      */
975     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
976                           appData.movesPerSession)) {
977         char buf[MSG_SIZ];
978         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
979         DisplayFatalError(buf, 0, 2);
980     }
981
982     /*
983      * Parse searchTime resource
984      */
985     if (*appData.searchTime != NULLCHAR) {
986         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
987         if (matched == 1) {
988             searchTime = min * 60;
989         } else if (matched == 2) {
990             searchTime = min * 60 + sec;
991         } else {
992             char buf[MSG_SIZ];
993             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
994             DisplayFatalError(buf, 0, 2);
995         }
996     }
997 }
998
999 void
1000 InitBackEnd1 ()
1001 {
1002
1003     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1004     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1005
1006     GetTimeMark(&programStartTime);
1007     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1008     appData.seedBase = random() + (random()<<15);
1009     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1010
1011     ClearProgramStats();
1012     programStats.ok_to_send = 1;
1013     programStats.seen_stat = 0;
1014
1015     /*
1016      * Initialize game list
1017      */
1018     ListNew(&gameList);
1019
1020
1021     /*
1022      * Internet chess server status
1023      */
1024     if (appData.icsActive) {
1025         appData.matchMode = FALSE;
1026         appData.matchGames = 0;
1027 #if ZIPPY
1028         appData.noChessProgram = !appData.zippyPlay;
1029 #else
1030         appData.zippyPlay = FALSE;
1031         appData.zippyTalk = FALSE;
1032         appData.noChessProgram = TRUE;
1033 #endif
1034         if (*appData.icsHelper != NULLCHAR) {
1035             appData.useTelnet = TRUE;
1036             appData.telnetProgram = appData.icsHelper;
1037         }
1038     } else {
1039         appData.zippyTalk = appData.zippyPlay = FALSE;
1040     }
1041
1042     /* [AS] Initialize pv info list [HGM] and game state */
1043     {
1044         int i, j;
1045
1046         for( i=0; i<=framePtr; i++ ) {
1047             pvInfoList[i].depth = -1;
1048             boards[i][EP_STATUS] = EP_NONE;
1049             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1050         }
1051     }
1052
1053     InitTimeControls();
1054
1055     /* [AS] Adjudication threshold */
1056     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1057
1058     InitEngine(&first, 0);
1059     InitEngine(&second, 1);
1060     CommonEngineInit();
1061
1062     pairing.which = "pairing"; // pairing engine
1063     pairing.pr = NoProc;
1064     pairing.isr = NULL;
1065     pairing.program = appData.pairingEngine;
1066     pairing.host = "localhost";
1067     pairing.dir = ".";
1068
1069     if (appData.icsActive) {
1070         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1071     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1072         appData.clockMode = FALSE;
1073         first.sendTime = second.sendTime = 0;
1074     }
1075
1076 #if ZIPPY
1077     /* Override some settings from environment variables, for backward
1078        compatibility.  Unfortunately it's not feasible to have the env
1079        vars just set defaults, at least in xboard.  Ugh.
1080     */
1081     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1082       ZippyInit();
1083     }
1084 #endif
1085
1086     if (!appData.icsActive) {
1087       char buf[MSG_SIZ];
1088       int len;
1089
1090       /* Check for variants that are supported only in ICS mode,
1091          or not at all.  Some that are accepted here nevertheless
1092          have bugs; see comments below.
1093       */
1094       VariantClass variant = StringToVariant(appData.variant);
1095       switch (variant) {
1096       case VariantBughouse:     /* need four players and two boards */
1097       case VariantKriegspiel:   /* need to hide pieces and move details */
1098         /* case VariantFischeRandom: (Fabien: moved below) */
1099         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1100         if( (len >= MSG_SIZ) && appData.debugMode )
1101           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1102
1103         DisplayFatalError(buf, 0, 2);
1104         return;
1105
1106       case VariantUnknown:
1107       case VariantLoadable:
1108       case Variant29:
1109       case Variant30:
1110       case Variant31:
1111       case Variant32:
1112       case Variant33:
1113       case Variant34:
1114       case Variant35:
1115       case Variant36:
1116       default:
1117         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1118         if( (len >= MSG_SIZ) && appData.debugMode )
1119           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1120
1121         DisplayFatalError(buf, 0, 2);
1122         return;
1123
1124       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1125       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1126       case VariantGothic:     /* [HGM] should work */
1127       case VariantCapablanca: /* [HGM] should work */
1128       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1129       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1130       case VariantKnightmate: /* [HGM] should work */
1131       case VariantCylinder:   /* [HGM] untested */
1132       case VariantFalcon:     /* [HGM] untested */
1133       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1134                                  offboard interposition not understood */
1135       case VariantNormal:     /* definitely works! */
1136       case VariantWildCastle: /* pieces not automatically shuffled */
1137       case VariantNoCastle:   /* pieces not automatically shuffled */
1138       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1139       case VariantLosers:     /* should work except for win condition,
1140                                  and doesn't know captures are mandatory */
1141       case VariantSuicide:    /* should work except for win condition,
1142                                  and doesn't know captures are mandatory */
1143       case VariantGiveaway:   /* should work except for win condition,
1144                                  and doesn't know captures are mandatory */
1145       case VariantTwoKings:   /* should work */
1146       case VariantAtomic:     /* should work except for win condition */
1147       case Variant3Check:     /* should work except for win condition */
1148       case VariantShatranj:   /* should work except for all win conditions */
1149       case VariantMakruk:     /* should work except for draw countdown */
1150       case VariantBerolina:   /* might work if TestLegality is off */
1151       case VariantCapaRandom: /* should work */
1152       case VariantJanus:      /* should work */
1153       case VariantSuper:      /* experimental */
1154       case VariantGreat:      /* experimental, requires legality testing to be off */
1155       case VariantSChess:     /* S-Chess, should work */
1156       case VariantGrand:      /* should work */
1157       case VariantSpartan:    /* should work */
1158         break;
1159       }
1160     }
1161
1162 }
1163
1164 int
1165 NextIntegerFromString (char ** str, long * value)
1166 {
1167     int result = -1;
1168     char * s = *str;
1169
1170     while( *s == ' ' || *s == '\t' ) {
1171         s++;
1172     }
1173
1174     *value = 0;
1175
1176     if( *s >= '0' && *s <= '9' ) {
1177         while( *s >= '0' && *s <= '9' ) {
1178             *value = *value * 10 + (*s - '0');
1179             s++;
1180         }
1181
1182         result = 0;
1183     }
1184
1185     *str = s;
1186
1187     return result;
1188 }
1189
1190 int
1191 NextTimeControlFromString (char ** str, long * value)
1192 {
1193     long temp;
1194     int result = NextIntegerFromString( str, &temp );
1195
1196     if( result == 0 ) {
1197         *value = temp * 60; /* Minutes */
1198         if( **str == ':' ) {
1199             (*str)++;
1200             result = NextIntegerFromString( str, &temp );
1201             *value += temp; /* Seconds */
1202         }
1203     }
1204
1205     return result;
1206 }
1207
1208 int
1209 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1210 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1211     int result = -1, type = 0; long temp, temp2;
1212
1213     if(**str != ':') return -1; // old params remain in force!
1214     (*str)++;
1215     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1216     if( NextIntegerFromString( str, &temp ) ) return -1;
1217     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1218
1219     if(**str != '/') {
1220         /* time only: incremental or sudden-death time control */
1221         if(**str == '+') { /* increment follows; read it */
1222             (*str)++;
1223             if(**str == '!') type = *(*str)++; // Bronstein TC
1224             if(result = NextIntegerFromString( str, &temp2)) return -1;
1225             *inc = temp2 * 1000;
1226             if(**str == '.') { // read fraction of increment
1227                 char *start = ++(*str);
1228                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1229                 temp2 *= 1000;
1230                 while(start++ < *str) temp2 /= 10;
1231                 *inc += temp2;
1232             }
1233         } else *inc = 0;
1234         *moves = 0; *tc = temp * 1000; *incType = type;
1235         return 0;
1236     }
1237
1238     (*str)++; /* classical time control */
1239     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1240
1241     if(result == 0) {
1242         *moves = temp;
1243         *tc    = temp2 * 1000;
1244         *inc   = 0;
1245         *incType = type;
1246     }
1247     return result;
1248 }
1249
1250 int
1251 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1252 {   /* [HGM] get time to add from the multi-session time-control string */
1253     int incType, moves=1; /* kludge to force reading of first session */
1254     long time, increment;
1255     char *s = tcString;
1256
1257     if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1258     do {
1259         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1260         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1261         if(movenr == -1) return time;    /* last move before new session     */
1262         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1263         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1264         if(!moves) return increment;     /* current session is incremental   */
1265         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1266     } while(movenr >= -1);               /* try again for next session       */
1267
1268     return 0; // no new time quota on this move
1269 }
1270
1271 int
1272 ParseTimeControl (char *tc, float ti, int mps)
1273 {
1274   long tc1;
1275   long tc2;
1276   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1277   int min, sec=0;
1278
1279   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1280   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1281       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1282   if(ti > 0) {
1283
1284     if(mps)
1285       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1286     else 
1287       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1288   } else {
1289     if(mps)
1290       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1291     else 
1292       snprintf(buf, MSG_SIZ, ":%s", mytc);
1293   }
1294   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1295   
1296   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1297     return FALSE;
1298   }
1299
1300   if( *tc == '/' ) {
1301     /* Parse second time control */
1302     tc++;
1303
1304     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1305       return FALSE;
1306     }
1307
1308     if( tc2 == 0 ) {
1309       return FALSE;
1310     }
1311
1312     timeControl_2 = tc2 * 1000;
1313   }
1314   else {
1315     timeControl_2 = 0;
1316   }
1317
1318   if( tc1 == 0 ) {
1319     return FALSE;
1320   }
1321
1322   timeControl = tc1 * 1000;
1323
1324   if (ti >= 0) {
1325     timeIncrement = ti * 1000;  /* convert to ms */
1326     movesPerSession = 0;
1327   } else {
1328     timeIncrement = 0;
1329     movesPerSession = mps;
1330   }
1331   return TRUE;
1332 }
1333
1334 void
1335 InitBackEnd2 ()
1336 {
1337     if (appData.debugMode) {
1338         fprintf(debugFP, "%s\n", programVersion);
1339     }
1340     ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1341
1342     set_cont_sequence(appData.wrapContSeq);
1343     if (appData.matchGames > 0) {
1344         appData.matchMode = TRUE;
1345     } else if (appData.matchMode) {
1346         appData.matchGames = 1;
1347     }
1348     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1349         appData.matchGames = appData.sameColorGames;
1350     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1351         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1352         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1353     }
1354     Reset(TRUE, FALSE);
1355     if (appData.noChessProgram || first.protocolVersion == 1) {
1356       InitBackEnd3();
1357     } else {
1358       /* kludge: allow timeout for initial "feature" commands */
1359       FreezeUI();
1360       DisplayMessage("", _("Starting chess program"));
1361       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1362     }
1363 }
1364
1365 int
1366 CalculateIndex (int index, int gameNr)
1367 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1368     int res;
1369     if(index > 0) return index; // fixed nmber
1370     if(index == 0) return 1;
1371     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1372     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1373     return res;
1374 }
1375
1376 int
1377 LoadGameOrPosition (int gameNr)
1378 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1379     if (*appData.loadGameFile != NULLCHAR) {
1380         if (!LoadGameFromFile(appData.loadGameFile,
1381                 CalculateIndex(appData.loadGameIndex, gameNr),
1382                               appData.loadGameFile, FALSE)) {
1383             DisplayFatalError(_("Bad game file"), 0, 1);
1384             return 0;
1385         }
1386     } else if (*appData.loadPositionFile != NULLCHAR) {
1387         if (!LoadPositionFromFile(appData.loadPositionFile,
1388                 CalculateIndex(appData.loadPositionIndex, gameNr),
1389                                   appData.loadPositionFile)) {
1390             DisplayFatalError(_("Bad position file"), 0, 1);
1391             return 0;
1392         }
1393     }
1394     return 1;
1395 }
1396
1397 void
1398 ReserveGame (int gameNr, char resChar)
1399 {
1400     FILE *tf = fopen(appData.tourneyFile, "r+");
1401     char *p, *q, c, buf[MSG_SIZ];
1402     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1403     safeStrCpy(buf, lastMsg, MSG_SIZ);
1404     DisplayMessage(_("Pick new game"), "");
1405     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1406     ParseArgsFromFile(tf);
1407     p = q = appData.results;
1408     if(appData.debugMode) {
1409       char *r = appData.participants;
1410       fprintf(debugFP, "results = '%s'\n", p);
1411       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1412       fprintf(debugFP, "\n");
1413     }
1414     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1415     nextGame = q - p;
1416     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1417     safeStrCpy(q, p, strlen(p) + 2);
1418     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1419     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1420     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1421         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1422         q[nextGame] = '*';
1423     }
1424     fseek(tf, -(strlen(p)+4), SEEK_END);
1425     c = fgetc(tf);
1426     if(c != '"') // depending on DOS or Unix line endings we can be one off
1427          fseek(tf, -(strlen(p)+2), SEEK_END);
1428     else fseek(tf, -(strlen(p)+3), SEEK_END);
1429     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1430     DisplayMessage(buf, "");
1431     free(p); appData.results = q;
1432     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1433        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1434       int round = appData.defaultMatchGames * appData.tourneyType;
1435       if(gameNr < 0 || appData.tourneyType < 1 ||  // gauntlet engine can always stay loaded as first engine
1436          appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1437         UnloadEngine(&first);  // next game belongs to other pairing;
1438         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1439     }
1440     if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1441 }
1442
1443 void
1444 MatchEvent (int mode)
1445 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1446         int dummy;
1447         if(matchMode) { // already in match mode: switch it off
1448             abortMatch = TRUE;
1449             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1450             return;
1451         }
1452 //      if(gameMode != BeginningOfGame) {
1453 //          DisplayError(_("You can only start a match from the initial position."), 0);
1454 //          return;
1455 //      }
1456         abortMatch = FALSE;
1457         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1458         /* Set up machine vs. machine match */
1459         nextGame = 0;
1460         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1461         if(appData.tourneyFile[0]) {
1462             ReserveGame(-1, 0);
1463             if(nextGame > appData.matchGames) {
1464                 char buf[MSG_SIZ];
1465                 if(strchr(appData.results, '*') == NULL) {
1466                     FILE *f;
1467                     appData.tourneyCycles++;
1468                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1469                         fclose(f);
1470                         NextTourneyGame(-1, &dummy);
1471                         ReserveGame(-1, 0);
1472                         if(nextGame <= appData.matchGames) {
1473                             DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1474                             matchMode = mode;
1475                             ScheduleDelayedEvent(NextMatchGame, 10000);
1476                             return;
1477                         }
1478                     }
1479                 }
1480                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1481                 DisplayError(buf, 0);
1482                 appData.tourneyFile[0] = 0;
1483                 return;
1484             }
1485         } else
1486         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1487             DisplayFatalError(_("Can't have a match with no chess programs"),
1488                               0, 2);
1489             return;
1490         }
1491         matchMode = mode;
1492         matchGame = roundNr = 1;
1493         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1494         NextMatchGame();
1495 }
1496
1497 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1498
1499 void
1500 InitBackEnd3 P((void))
1501 {
1502     GameMode initialMode;
1503     char buf[MSG_SIZ];
1504     int err, len;
1505
1506     InitChessProgram(&first, startedFromSetupPosition);
1507
1508     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1509         free(programVersion);
1510         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1511         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1512         FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1513     }
1514
1515     if (appData.icsActive) {
1516 #ifdef WIN32
1517         /* [DM] Make a console window if needed [HGM] merged ifs */
1518         ConsoleCreate();
1519 #endif
1520         err = establish();
1521         if (err != 0)
1522           {
1523             if (*appData.icsCommPort != NULLCHAR)
1524               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1525                              appData.icsCommPort);
1526             else
1527               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1528                         appData.icsHost, appData.icsPort);
1529
1530             if( (len >= MSG_SIZ) && appData.debugMode )
1531               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1532
1533             DisplayFatalError(buf, err, 1);
1534             return;
1535         }
1536         SetICSMode();
1537         telnetISR =
1538           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1539         fromUserISR =
1540           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1541         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1542             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1543     } else if (appData.noChessProgram) {
1544         SetNCPMode();
1545     } else {
1546         SetGNUMode();
1547     }
1548
1549     if (*appData.cmailGameName != NULLCHAR) {
1550         SetCmailMode();
1551         OpenLoopback(&cmailPR);
1552         cmailISR =
1553           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1554     }
1555
1556     ThawUI();
1557     DisplayMessage("", "");
1558     if (StrCaseCmp(appData.initialMode, "") == 0) {
1559       initialMode = BeginningOfGame;
1560       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1561         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1562         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1563         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1564         ModeHighlight();
1565       }
1566     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1567       initialMode = TwoMachinesPlay;
1568     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1569       initialMode = AnalyzeFile;
1570     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1571       initialMode = AnalyzeMode;
1572     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1573       initialMode = MachinePlaysWhite;
1574     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1575       initialMode = MachinePlaysBlack;
1576     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1577       initialMode = EditGame;
1578     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1579       initialMode = EditPosition;
1580     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1581       initialMode = Training;
1582     } else {
1583       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1584       if( (len >= MSG_SIZ) && appData.debugMode )
1585         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1586
1587       DisplayFatalError(buf, 0, 2);
1588       return;
1589     }
1590
1591     if (appData.matchMode) {
1592         if(appData.tourneyFile[0]) { // start tourney from command line
1593             FILE *f;
1594             if(f = fopen(appData.tourneyFile, "r")) {
1595                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1596                 fclose(f);
1597                 appData.clockMode = TRUE;
1598                 SetGNUMode();
1599             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1600         }
1601         MatchEvent(TRUE);
1602     } else if (*appData.cmailGameName != NULLCHAR) {
1603         /* Set up cmail mode */
1604         ReloadCmailMsgEvent(TRUE);
1605     } else {
1606         /* Set up other modes */
1607         if (initialMode == AnalyzeFile) {
1608           if (*appData.loadGameFile == NULLCHAR) {
1609             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1610             return;
1611           }
1612         }
1613         if (*appData.loadGameFile != NULLCHAR) {
1614             (void) LoadGameFromFile(appData.loadGameFile,
1615                                     appData.loadGameIndex,
1616                                     appData.loadGameFile, TRUE);
1617         } else if (*appData.loadPositionFile != NULLCHAR) {
1618             (void) LoadPositionFromFile(appData.loadPositionFile,
1619                                         appData.loadPositionIndex,
1620                                         appData.loadPositionFile);
1621             /* [HGM] try to make self-starting even after FEN load */
1622             /* to allow automatic setup of fairy variants with wtm */
1623             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1624                 gameMode = BeginningOfGame;
1625                 setboardSpoiledMachineBlack = 1;
1626             }
1627             /* [HGM] loadPos: make that every new game uses the setup */
1628             /* from file as long as we do not switch variant          */
1629             if(!blackPlaysFirst) {
1630                 startedFromPositionFile = TRUE;
1631                 CopyBoard(filePosition, boards[0]);
1632             }
1633         }
1634         if (initialMode == AnalyzeMode) {
1635           if (appData.noChessProgram) {
1636             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1637             return;
1638           }
1639           if (appData.icsActive) {
1640             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1641             return;
1642           }
1643           AnalyzeModeEvent();
1644         } else if (initialMode == AnalyzeFile) {
1645           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1646           ShowThinkingEvent();
1647           AnalyzeFileEvent();
1648           AnalysisPeriodicEvent(1);
1649         } else if (initialMode == MachinePlaysWhite) {
1650           if (appData.noChessProgram) {
1651             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1652                               0, 2);
1653             return;
1654           }
1655           if (appData.icsActive) {
1656             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1657                               0, 2);
1658             return;
1659           }
1660           MachineWhiteEvent();
1661         } else if (initialMode == MachinePlaysBlack) {
1662           if (appData.noChessProgram) {
1663             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1664                               0, 2);
1665             return;
1666           }
1667           if (appData.icsActive) {
1668             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1669                               0, 2);
1670             return;
1671           }
1672           MachineBlackEvent();
1673         } else if (initialMode == TwoMachinesPlay) {
1674           if (appData.noChessProgram) {
1675             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1676                               0, 2);
1677             return;
1678           }
1679           if (appData.icsActive) {
1680             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1681                               0, 2);
1682             return;
1683           }
1684           TwoMachinesEvent();
1685         } else if (initialMode == EditGame) {
1686           EditGameEvent();
1687         } else if (initialMode == EditPosition) {
1688           EditPositionEvent();
1689         } else if (initialMode == Training) {
1690           if (*appData.loadGameFile == NULLCHAR) {
1691             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1692             return;
1693           }
1694           TrainingEvent();
1695         }
1696     }
1697 }
1698
1699 void
1700 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1701 {
1702     DisplayBook(current+1);
1703
1704     MoveHistorySet( movelist, first, last, current, pvInfoList );
1705
1706     EvalGraphSet( first, last, current, pvInfoList );
1707
1708     MakeEngineOutputTitle();
1709 }
1710
1711 /*
1712  * Establish will establish a contact to a remote host.port.
1713  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1714  *  used to talk to the host.
1715  * Returns 0 if okay, error code if not.
1716  */
1717 int
1718 establish ()
1719 {
1720     char buf[MSG_SIZ];
1721
1722     if (*appData.icsCommPort != NULLCHAR) {
1723         /* Talk to the host through a serial comm port */
1724         return OpenCommPort(appData.icsCommPort, &icsPR);
1725
1726     } else if (*appData.gateway != NULLCHAR) {
1727         if (*appData.remoteShell == NULLCHAR) {
1728             /* Use the rcmd protocol to run telnet program on a gateway host */
1729             snprintf(buf, sizeof(buf), "%s %s %s",
1730                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1731             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1732
1733         } else {
1734             /* Use the rsh program to run telnet program on a gateway host */
1735             if (*appData.remoteUser == NULLCHAR) {
1736                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1737                         appData.gateway, appData.telnetProgram,
1738                         appData.icsHost, appData.icsPort);
1739             } else {
1740                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1741                         appData.remoteShell, appData.gateway,
1742                         appData.remoteUser, appData.telnetProgram,
1743                         appData.icsHost, appData.icsPort);
1744             }
1745             return StartChildProcess(buf, "", &icsPR);
1746
1747         }
1748     } else if (appData.useTelnet) {
1749         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1750
1751     } else {
1752         /* TCP socket interface differs somewhat between
1753            Unix and NT; handle details in the front end.
1754            */
1755         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1756     }
1757 }
1758
1759 void
1760 EscapeExpand (char *p, char *q)
1761 {       // [HGM] initstring: routine to shape up string arguments
1762         while(*p++ = *q++) if(p[-1] == '\\')
1763             switch(*q++) {
1764                 case 'n': p[-1] = '\n'; break;
1765                 case 'r': p[-1] = '\r'; break;
1766                 case 't': p[-1] = '\t'; break;
1767                 case '\\': p[-1] = '\\'; break;
1768                 case 0: *p = 0; return;
1769                 default: p[-1] = q[-1]; break;
1770             }
1771 }
1772
1773 void
1774 show_bytes (FILE *fp, char *buf, int count)
1775 {
1776     while (count--) {
1777         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1778             fprintf(fp, "\\%03o", *buf & 0xff);
1779         } else {
1780             putc(*buf, fp);
1781         }
1782         buf++;
1783     }
1784     fflush(fp);
1785 }
1786
1787 /* Returns an errno value */
1788 int
1789 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1790 {
1791     char buf[8192], *p, *q, *buflim;
1792     int left, newcount, outcount;
1793
1794     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1795         *appData.gateway != NULLCHAR) {
1796         if (appData.debugMode) {
1797             fprintf(debugFP, ">ICS: ");
1798             show_bytes(debugFP, message, count);
1799             fprintf(debugFP, "\n");
1800         }
1801         return OutputToProcess(pr, message, count, outError);
1802     }
1803
1804     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1805     p = message;
1806     q = buf;
1807     left = count;
1808     newcount = 0;
1809     while (left) {
1810         if (q >= buflim) {
1811             if (appData.debugMode) {
1812                 fprintf(debugFP, ">ICS: ");
1813                 show_bytes(debugFP, buf, newcount);
1814                 fprintf(debugFP, "\n");
1815             }
1816             outcount = OutputToProcess(pr, buf, newcount, outError);
1817             if (outcount < newcount) return -1; /* to be sure */
1818             q = buf;
1819             newcount = 0;
1820         }
1821         if (*p == '\n') {
1822             *q++ = '\r';
1823             newcount++;
1824         } else if (((unsigned char) *p) == TN_IAC) {
1825             *q++ = (char) TN_IAC;
1826             newcount ++;
1827         }
1828         *q++ = *p++;
1829         newcount++;
1830         left--;
1831     }
1832     if (appData.debugMode) {
1833         fprintf(debugFP, ">ICS: ");
1834         show_bytes(debugFP, buf, newcount);
1835         fprintf(debugFP, "\n");
1836     }
1837     outcount = OutputToProcess(pr, buf, newcount, outError);
1838     if (outcount < newcount) return -1; /* to be sure */
1839     return count;
1840 }
1841
1842 void
1843 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1844 {
1845     int outError, outCount;
1846     static int gotEof = 0;
1847
1848     /* Pass data read from player on to ICS */
1849     if (count > 0) {
1850         gotEof = 0;
1851         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1852         if (outCount < count) {
1853             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1854         }
1855     } else if (count < 0) {
1856         RemoveInputSource(isr);
1857         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1858     } else if (gotEof++ > 0) {
1859         RemoveInputSource(isr);
1860         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1861     }
1862 }
1863
1864 void
1865 KeepAlive ()
1866 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1867     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1868     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1869     SendToICS("date\n");
1870     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1871 }
1872
1873 /* added routine for printf style output to ics */
1874 void
1875 ics_printf (char *format, ...)
1876 {
1877     char buffer[MSG_SIZ];
1878     va_list args;
1879
1880     va_start(args, format);
1881     vsnprintf(buffer, sizeof(buffer), format, args);
1882     buffer[sizeof(buffer)-1] = '\0';
1883     SendToICS(buffer);
1884     va_end(args);
1885 }
1886
1887 void
1888 SendToICS (char *s)
1889 {
1890     int count, outCount, outError;
1891
1892     if (icsPR == NoProc) return;
1893
1894     count = strlen(s);
1895     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1896     if (outCount < count) {
1897         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1898     }
1899 }
1900
1901 /* This is used for sending logon scripts to the ICS. Sending
1902    without a delay causes problems when using timestamp on ICC
1903    (at least on my machine). */
1904 void
1905 SendToICSDelayed (char *s, long msdelay)
1906 {
1907     int count, outCount, outError;
1908
1909     if (icsPR == NoProc) return;
1910
1911     count = strlen(s);
1912     if (appData.debugMode) {
1913         fprintf(debugFP, ">ICS: ");
1914         show_bytes(debugFP, s, count);
1915         fprintf(debugFP, "\n");
1916     }
1917     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1918                                       msdelay);
1919     if (outCount < count) {
1920         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1921     }
1922 }
1923
1924
1925 /* Remove all highlighting escape sequences in s
1926    Also deletes any suffix starting with '('
1927    */
1928 char *
1929 StripHighlightAndTitle (char *s)
1930 {
1931     static char retbuf[MSG_SIZ];
1932     char *p = retbuf;
1933
1934     while (*s != NULLCHAR) {
1935         while (*s == '\033') {
1936             while (*s != NULLCHAR && !isalpha(*s)) s++;
1937             if (*s != NULLCHAR) s++;
1938         }
1939         while (*s != NULLCHAR && *s != '\033') {
1940             if (*s == '(' || *s == '[') {
1941                 *p = NULLCHAR;
1942                 return retbuf;
1943             }
1944             *p++ = *s++;
1945         }
1946     }
1947     *p = NULLCHAR;
1948     return retbuf;
1949 }
1950
1951 /* Remove all highlighting escape sequences in s */
1952 char *
1953 StripHighlight (char *s)
1954 {
1955     static char retbuf[MSG_SIZ];
1956     char *p = retbuf;
1957
1958     while (*s != NULLCHAR) {
1959         while (*s == '\033') {
1960             while (*s != NULLCHAR && !isalpha(*s)) s++;
1961             if (*s != NULLCHAR) s++;
1962         }
1963         while (*s != NULLCHAR && *s != '\033') {
1964             *p++ = *s++;
1965         }
1966     }
1967     *p = NULLCHAR;
1968     return retbuf;
1969 }
1970
1971 char *variantNames[] = VARIANT_NAMES;
1972 char *
1973 VariantName (VariantClass v)
1974 {
1975     return variantNames[v];
1976 }
1977
1978
1979 /* Identify a variant from the strings the chess servers use or the
1980    PGN Variant tag names we use. */
1981 VariantClass
1982 StringToVariant (char *e)
1983 {
1984     char *p;
1985     int wnum = -1;
1986     VariantClass v = VariantNormal;
1987     int i, found = FALSE;
1988     char buf[MSG_SIZ];
1989     int len;
1990
1991     if (!e) return v;
1992
1993     /* [HGM] skip over optional board-size prefixes */
1994     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1995         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1996         while( *e++ != '_');
1997     }
1998
1999     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2000         v = VariantNormal;
2001         found = TRUE;
2002     } else
2003     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2004       if (StrCaseStr(e, variantNames[i])) {
2005         v = (VariantClass) i;
2006         found = TRUE;
2007         break;
2008       }
2009     }
2010
2011     if (!found) {
2012       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2013           || StrCaseStr(e, "wild/fr")
2014           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2015         v = VariantFischeRandom;
2016       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2017                  (i = 1, p = StrCaseStr(e, "w"))) {
2018         p += i;
2019         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2020         if (isdigit(*p)) {
2021           wnum = atoi(p);
2022         } else {
2023           wnum = -1;
2024         }
2025         switch (wnum) {
2026         case 0: /* FICS only, actually */
2027         case 1:
2028           /* Castling legal even if K starts on d-file */
2029           v = VariantWildCastle;
2030           break;
2031         case 2:
2032         case 3:
2033         case 4:
2034           /* Castling illegal even if K & R happen to start in
2035              normal positions. */
2036           v = VariantNoCastle;
2037           break;
2038         case 5:
2039         case 7:
2040         case 8:
2041         case 10:
2042         case 11:
2043         case 12:
2044         case 13:
2045         case 14:
2046         case 15:
2047         case 18:
2048         case 19:
2049           /* Castling legal iff K & R start in normal positions */
2050           v = VariantNormal;
2051           break;
2052         case 6:
2053         case 20:
2054         case 21:
2055           /* Special wilds for position setup; unclear what to do here */
2056           v = VariantLoadable;
2057           break;
2058         case 9:
2059           /* Bizarre ICC game */
2060           v = VariantTwoKings;
2061           break;
2062         case 16:
2063           v = VariantKriegspiel;
2064           break;
2065         case 17:
2066           v = VariantLosers;
2067           break;
2068         case 22:
2069           v = VariantFischeRandom;
2070           break;
2071         case 23:
2072           v = VariantCrazyhouse;
2073           break;
2074         case 24:
2075           v = VariantBughouse;
2076           break;
2077         case 25:
2078           v = Variant3Check;
2079           break;
2080         case 26:
2081           /* Not quite the same as FICS suicide! */
2082           v = VariantGiveaway;
2083           break;
2084         case 27:
2085           v = VariantAtomic;
2086           break;
2087         case 28:
2088           v = VariantShatranj;
2089           break;
2090
2091         /* Temporary names for future ICC types.  The name *will* change in
2092            the next xboard/WinBoard release after ICC defines it. */
2093         case 29:
2094           v = Variant29;
2095           break;
2096         case 30:
2097           v = Variant30;
2098           break;
2099         case 31:
2100           v = Variant31;
2101           break;
2102         case 32:
2103           v = Variant32;
2104           break;
2105         case 33:
2106           v = Variant33;
2107           break;
2108         case 34:
2109           v = Variant34;
2110           break;
2111         case 35:
2112           v = Variant35;
2113           break;
2114         case 36:
2115           v = Variant36;
2116           break;
2117         case 37:
2118           v = VariantShogi;
2119           break;
2120         case 38:
2121           v = VariantXiangqi;
2122           break;
2123         case 39:
2124           v = VariantCourier;
2125           break;
2126         case 40:
2127           v = VariantGothic;
2128           break;
2129         case 41:
2130           v = VariantCapablanca;
2131           break;
2132         case 42:
2133           v = VariantKnightmate;
2134           break;
2135         case 43:
2136           v = VariantFairy;
2137           break;
2138         case 44:
2139           v = VariantCylinder;
2140           break;
2141         case 45:
2142           v = VariantFalcon;
2143           break;
2144         case 46:
2145           v = VariantCapaRandom;
2146           break;
2147         case 47:
2148           v = VariantBerolina;
2149           break;
2150         case 48:
2151           v = VariantJanus;
2152           break;
2153         case 49:
2154           v = VariantSuper;
2155           break;
2156         case 50:
2157           v = VariantGreat;
2158           break;
2159         case -1:
2160           /* Found "wild" or "w" in the string but no number;
2161              must assume it's normal chess. */
2162           v = VariantNormal;
2163           break;
2164         default:
2165           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2166           if( (len >= MSG_SIZ) && appData.debugMode )
2167             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2168
2169           DisplayError(buf, 0);
2170           v = VariantUnknown;
2171           break;
2172         }
2173       }
2174     }
2175     if (appData.debugMode) {
2176       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2177               e, wnum, VariantName(v));
2178     }
2179     return v;
2180 }
2181
2182 static int leftover_start = 0, leftover_len = 0;
2183 char star_match[STAR_MATCH_N][MSG_SIZ];
2184
2185 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2186    advance *index beyond it, and set leftover_start to the new value of
2187    *index; else return FALSE.  If pattern contains the character '*', it
2188    matches any sequence of characters not containing '\r', '\n', or the
2189    character following the '*' (if any), and the matched sequence(s) are
2190    copied into star_match.
2191    */
2192 int
2193 looking_at ( char *buf, int *index, char *pattern)
2194 {
2195     char *bufp = &buf[*index], *patternp = pattern;
2196     int star_count = 0;
2197     char *matchp = star_match[0];
2198
2199     for (;;) {
2200         if (*patternp == NULLCHAR) {
2201             *index = leftover_start = bufp - buf;
2202             *matchp = NULLCHAR;
2203             return TRUE;
2204         }
2205         if (*bufp == NULLCHAR) return FALSE;
2206         if (*patternp == '*') {
2207             if (*bufp == *(patternp + 1)) {
2208                 *matchp = NULLCHAR;
2209                 matchp = star_match[++star_count];
2210                 patternp += 2;
2211                 bufp++;
2212                 continue;
2213             } else if (*bufp == '\n' || *bufp == '\r') {
2214                 patternp++;
2215                 if (*patternp == NULLCHAR)
2216                   continue;
2217                 else
2218                   return FALSE;
2219             } else {
2220                 *matchp++ = *bufp++;
2221                 continue;
2222             }
2223         }
2224         if (*patternp != *bufp) return FALSE;
2225         patternp++;
2226         bufp++;
2227     }
2228 }
2229
2230 void
2231 SendToPlayer (char *data, int length)
2232 {
2233     int error, outCount;
2234     outCount = OutputToProcess(NoProc, data, length, &error);
2235     if (outCount < length) {
2236         DisplayFatalError(_("Error writing to display"), error, 1);
2237     }
2238 }
2239
2240 void
2241 PackHolding (char packed[], char *holding)
2242 {
2243     char *p = holding;
2244     char *q = packed;
2245     int runlength = 0;
2246     int curr = 9999;
2247     do {
2248         if (*p == curr) {
2249             runlength++;
2250         } else {
2251             switch (runlength) {
2252               case 0:
2253                 break;
2254               case 1:
2255                 *q++ = curr;
2256                 break;
2257               case 2:
2258                 *q++ = curr;
2259                 *q++ = curr;
2260                 break;
2261               default:
2262                 sprintf(q, "%d", runlength);
2263                 while (*q) q++;
2264                 *q++ = curr;
2265                 break;
2266             }
2267             runlength = 1;
2268             curr = *p;
2269         }
2270     } while (*p++);
2271     *q = NULLCHAR;
2272 }
2273
2274 /* Telnet protocol requests from the front end */
2275 void
2276 TelnetRequest (unsigned char ddww, unsigned char option)
2277 {
2278     unsigned char msg[3];
2279     int outCount, outError;
2280
2281     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2282
2283     if (appData.debugMode) {
2284         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2285         switch (ddww) {
2286           case TN_DO:
2287             ddwwStr = "DO";
2288             break;
2289           case TN_DONT:
2290             ddwwStr = "DONT";
2291             break;
2292           case TN_WILL:
2293             ddwwStr = "WILL";
2294             break;
2295           case TN_WONT:
2296             ddwwStr = "WONT";
2297             break;
2298           default:
2299             ddwwStr = buf1;
2300             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2301             break;
2302         }
2303         switch (option) {
2304           case TN_ECHO:
2305             optionStr = "ECHO";
2306             break;
2307           default:
2308             optionStr = buf2;
2309             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2310             break;
2311         }
2312         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2313     }
2314     msg[0] = TN_IAC;
2315     msg[1] = ddww;
2316     msg[2] = option;
2317     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2318     if (outCount < 3) {
2319         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2320     }
2321 }
2322
2323 void
2324 DoEcho ()
2325 {
2326     if (!appData.icsActive) return;
2327     TelnetRequest(TN_DO, TN_ECHO);
2328 }
2329
2330 void
2331 DontEcho ()
2332 {
2333     if (!appData.icsActive) return;
2334     TelnetRequest(TN_DONT, TN_ECHO);
2335 }
2336
2337 void
2338 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2339 {
2340     /* put the holdings sent to us by the server on the board holdings area */
2341     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2342     char p;
2343     ChessSquare piece;
2344
2345     if(gameInfo.holdingsWidth < 2)  return;
2346     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2347         return; // prevent overwriting by pre-board holdings
2348
2349     if( (int)lowestPiece >= BlackPawn ) {
2350         holdingsColumn = 0;
2351         countsColumn = 1;
2352         holdingsStartRow = BOARD_HEIGHT-1;
2353         direction = -1;
2354     } else {
2355         holdingsColumn = BOARD_WIDTH-1;
2356         countsColumn = BOARD_WIDTH-2;
2357         holdingsStartRow = 0;
2358         direction = 1;
2359     }
2360
2361     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2362         board[i][holdingsColumn] = EmptySquare;
2363         board[i][countsColumn]   = (ChessSquare) 0;
2364     }
2365     while( (p=*holdings++) != NULLCHAR ) {
2366         piece = CharToPiece( ToUpper(p) );
2367         if(piece == EmptySquare) continue;
2368         /*j = (int) piece - (int) WhitePawn;*/
2369         j = PieceToNumber(piece);
2370         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2371         if(j < 0) continue;               /* should not happen */
2372         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2373         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2374         board[holdingsStartRow+j*direction][countsColumn]++;
2375     }
2376 }
2377
2378
2379 void
2380 VariantSwitch (Board board, VariantClass newVariant)
2381 {
2382    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2383    static Board oldBoard;
2384
2385    startedFromPositionFile = FALSE;
2386    if(gameInfo.variant == newVariant) return;
2387
2388    /* [HGM] This routine is called each time an assignment is made to
2389     * gameInfo.variant during a game, to make sure the board sizes
2390     * are set to match the new variant. If that means adding or deleting
2391     * holdings, we shift the playing board accordingly
2392     * This kludge is needed because in ICS observe mode, we get boards
2393     * of an ongoing game without knowing the variant, and learn about the
2394     * latter only later. This can be because of the move list we requested,
2395     * in which case the game history is refilled from the beginning anyway,
2396     * but also when receiving holdings of a crazyhouse game. In the latter
2397     * case we want to add those holdings to the already received position.
2398     */
2399
2400
2401    if (appData.debugMode) {
2402      fprintf(debugFP, "Switch board from %s to %s\n",
2403              VariantName(gameInfo.variant), VariantName(newVariant));
2404      setbuf(debugFP, NULL);
2405    }
2406    shuffleOpenings = 0;       /* [HGM] shuffle */
2407    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2408    switch(newVariant)
2409      {
2410      case VariantShogi:
2411        newWidth = 9;  newHeight = 9;
2412        gameInfo.holdingsSize = 7;
2413      case VariantBughouse:
2414      case VariantCrazyhouse:
2415        newHoldingsWidth = 2; break;
2416      case VariantGreat:
2417        newWidth = 10;
2418      case VariantSuper:
2419        newHoldingsWidth = 2;
2420        gameInfo.holdingsSize = 8;
2421        break;
2422      case VariantGothic:
2423      case VariantCapablanca:
2424      case VariantCapaRandom:
2425        newWidth = 10;
2426      default:
2427        newHoldingsWidth = gameInfo.holdingsSize = 0;
2428      };
2429
2430    if(newWidth  != gameInfo.boardWidth  ||
2431       newHeight != gameInfo.boardHeight ||
2432       newHoldingsWidth != gameInfo.holdingsWidth ) {
2433
2434      /* shift position to new playing area, if needed */
2435      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2436        for(i=0; i<BOARD_HEIGHT; i++)
2437          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2438            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2439              board[i][j];
2440        for(i=0; i<newHeight; i++) {
2441          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2442          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2443        }
2444      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2445        for(i=0; i<BOARD_HEIGHT; i++)
2446          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2447            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2448              board[i][j];
2449      }
2450      board[HOLDINGS_SET] = 0;
2451      gameInfo.boardWidth  = newWidth;
2452      gameInfo.boardHeight = newHeight;
2453      gameInfo.holdingsWidth = newHoldingsWidth;
2454      gameInfo.variant = newVariant;
2455      InitDrawingSizes(-2, 0);
2456    } else gameInfo.variant = newVariant;
2457    CopyBoard(oldBoard, board);   // remember correctly formatted board
2458      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2459    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2460 }
2461
2462 static int loggedOn = FALSE;
2463
2464 /*-- Game start info cache: --*/
2465 int gs_gamenum;
2466 char gs_kind[MSG_SIZ];
2467 static char player1Name[128] = "";
2468 static char player2Name[128] = "";
2469 static char cont_seq[] = "\n\\   ";
2470 static int player1Rating = -1;
2471 static int player2Rating = -1;
2472 /*----------------------------*/
2473
2474 ColorClass curColor = ColorNormal;
2475 int suppressKibitz = 0;
2476
2477 // [HGM] seekgraph
2478 Boolean soughtPending = FALSE;
2479 Boolean seekGraphUp;
2480 #define MAX_SEEK_ADS 200
2481 #define SQUARE 0x80
2482 char *seekAdList[MAX_SEEK_ADS];
2483 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2484 float tcList[MAX_SEEK_ADS];
2485 char colorList[MAX_SEEK_ADS];
2486 int nrOfSeekAds = 0;
2487 int minRating = 1010, maxRating = 2800;
2488 int hMargin = 10, vMargin = 20, h, w;
2489 extern int squareSize, lineGap;
2490
2491 void
2492 PlotSeekAd (int i)
2493 {
2494         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2495         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2496         if(r < minRating+100 && r >=0 ) r = minRating+100;
2497         if(r > maxRating) r = maxRating;
2498         if(tc < 1.f) tc = 1.f;
2499         if(tc > 95.f) tc = 95.f;
2500         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2501         y = ((double)r - minRating)/(maxRating - minRating)
2502             * (h-vMargin-squareSize/8-1) + vMargin;
2503         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2504         if(strstr(seekAdList[i], " u ")) color = 1;
2505         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2506            !strstr(seekAdList[i], "bullet") &&
2507            !strstr(seekAdList[i], "blitz") &&
2508            !strstr(seekAdList[i], "standard") ) color = 2;
2509         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2510         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2511 }
2512
2513 void
2514 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2515 {
2516         char buf[MSG_SIZ], *ext = "";
2517         VariantClass v = StringToVariant(type);
2518         if(strstr(type, "wild")) {
2519             ext = type + 4; // append wild number
2520             if(v == VariantFischeRandom) type = "chess960"; else
2521             if(v == VariantLoadable) type = "setup"; else
2522             type = VariantName(v);
2523         }
2524         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2525         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2526             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2527             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2528             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2529             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2530             seekNrList[nrOfSeekAds] = nr;
2531             zList[nrOfSeekAds] = 0;
2532             seekAdList[nrOfSeekAds++] = StrSave(buf);
2533             if(plot) PlotSeekAd(nrOfSeekAds-1);
2534         }
2535 }
2536
2537 void
2538 EraseSeekDot (int i)
2539 {
2540     int x = xList[i], y = yList[i], d=squareSize/4, k;
2541     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2542     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2543     // now replot every dot that overlapped
2544     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2545         int xx = xList[k], yy = yList[k];
2546         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2547             DrawSeekDot(xx, yy, colorList[k]);
2548     }
2549 }
2550
2551 void
2552 RemoveSeekAd (int nr)
2553 {
2554         int i;
2555         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2556             EraseSeekDot(i);
2557             if(seekAdList[i]) free(seekAdList[i]);
2558             seekAdList[i] = seekAdList[--nrOfSeekAds];
2559             seekNrList[i] = seekNrList[nrOfSeekAds];
2560             ratingList[i] = ratingList[nrOfSeekAds];
2561             colorList[i]  = colorList[nrOfSeekAds];
2562             tcList[i] = tcList[nrOfSeekAds];
2563             xList[i]  = xList[nrOfSeekAds];
2564             yList[i]  = yList[nrOfSeekAds];
2565             zList[i]  = zList[nrOfSeekAds];
2566             seekAdList[nrOfSeekAds] = NULL;
2567             break;
2568         }
2569 }
2570
2571 Boolean
2572 MatchSoughtLine (char *line)
2573 {
2574     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2575     int nr, base, inc, u=0; char dummy;
2576
2577     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2578        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2579        (u=1) &&
2580        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2581         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2582         // match: compact and save the line
2583         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2584         return TRUE;
2585     }
2586     return FALSE;
2587 }
2588
2589 int
2590 DrawSeekGraph ()
2591 {
2592     int i;
2593     if(!seekGraphUp) return FALSE;
2594     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2595     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2596
2597     DrawSeekBackground(0, 0, w, h);
2598     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2599     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2600     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2601         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2602         yy = h-1-yy;
2603         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2604         if(i%500 == 0) {
2605             char buf[MSG_SIZ];
2606             snprintf(buf, MSG_SIZ, "%d", i);
2607             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2608         }
2609     }
2610     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2611     for(i=1; i<100; i+=(i<10?1:5)) {
2612         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2613         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2614         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2615             char buf[MSG_SIZ];
2616             snprintf(buf, MSG_SIZ, "%d", i);
2617             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2618         }
2619     }
2620     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2621     return TRUE;
2622 }
2623
2624 int
2625 SeekGraphClick (ClickType click, int x, int y, int moving)
2626 {
2627     static int lastDown = 0, displayed = 0, lastSecond;
2628     if(y < 0) return FALSE;
2629     if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2630         (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2631         if(!seekGraphUp) return FALSE;
2632         seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2633         DrawPosition(TRUE, NULL);
2634         return TRUE;
2635     }
2636     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2637         if(click == Release || moving) return FALSE;
2638         nrOfSeekAds = 0;
2639         soughtPending = TRUE;
2640         SendToICS(ics_prefix);
2641         SendToICS("sought\n"); // should this be "sought all"?
2642     } else { // issue challenge based on clicked ad
2643         int dist = 10000; int i, closest = 0, second = 0;
2644         for(i=0; i<nrOfSeekAds; i++) {
2645             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2646             if(d < dist) { dist = d; closest = i; }
2647             second += (d - zList[i] < 120); // count in-range ads
2648             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2649         }
2650         if(dist < 120) {
2651             char buf[MSG_SIZ];
2652             second = (second > 1);
2653             if(displayed != closest || second != lastSecond) {
2654                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2655                 lastSecond = second; displayed = closest;
2656             }
2657             if(click == Press) {
2658                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2659                 lastDown = closest;
2660                 return TRUE;
2661             } // on press 'hit', only show info
2662             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2663             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2664             SendToICS(ics_prefix);
2665             SendToICS(buf);
2666             return TRUE; // let incoming board of started game pop down the graph
2667         } else if(click == Release) { // release 'miss' is ignored
2668             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2669             if(moving == 2) { // right up-click
2670                 nrOfSeekAds = 0; // refresh graph
2671                 soughtPending = TRUE;
2672                 SendToICS(ics_prefix);
2673                 SendToICS("sought\n"); // should this be "sought all"?
2674             }
2675             return TRUE;
2676         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2677         // press miss or release hit 'pop down' seek graph
2678         seekGraphUp = FALSE;
2679         DrawPosition(TRUE, NULL);
2680     }
2681     return TRUE;
2682 }
2683
2684 void
2685 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2686 {
2687 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2688 #define STARTED_NONE 0
2689 #define STARTED_MOVES 1
2690 #define STARTED_BOARD 2
2691 #define STARTED_OBSERVE 3
2692 #define STARTED_HOLDINGS 4
2693 #define STARTED_CHATTER 5
2694 #define STARTED_COMMENT 6
2695 #define STARTED_MOVES_NOHIDE 7
2696
2697     static int started = STARTED_NONE;
2698     static char parse[20000];
2699     static int parse_pos = 0;
2700     static char buf[BUF_SIZE + 1];
2701     static int firstTime = TRUE, intfSet = FALSE;
2702     static ColorClass prevColor = ColorNormal;
2703     static int savingComment = FALSE;
2704     static int cmatch = 0; // continuation sequence match
2705     char *bp;
2706     char str[MSG_SIZ];
2707     int i, oldi;
2708     int buf_len;
2709     int next_out;
2710     int tkind;
2711     int backup;    /* [DM] For zippy color lines */
2712     char *p;
2713     char talker[MSG_SIZ]; // [HGM] chat
2714     int channel;
2715
2716     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2717
2718     if (appData.debugMode) {
2719       if (!error) {
2720         fprintf(debugFP, "<ICS: ");
2721         show_bytes(debugFP, data, count);
2722         fprintf(debugFP, "\n");
2723       }
2724     }
2725
2726     if (appData.debugMode) { int f = forwardMostMove;
2727         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2728                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2729                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2730     }
2731     if (count > 0) {
2732         /* If last read ended with a partial line that we couldn't parse,
2733            prepend it to the new read and try again. */
2734         if (leftover_len > 0) {
2735             for (i=0; i<leftover_len; i++)
2736               buf[i] = buf[leftover_start + i];
2737         }
2738
2739     /* copy new characters into the buffer */
2740     bp = buf + leftover_len;
2741     buf_len=leftover_len;
2742     for (i=0; i<count; i++)
2743     {
2744         // ignore these
2745         if (data[i] == '\r')
2746             continue;
2747
2748         // join lines split by ICS?
2749         if (!appData.noJoin)
2750         {
2751             /*
2752                 Joining just consists of finding matches against the
2753                 continuation sequence, and discarding that sequence
2754                 if found instead of copying it.  So, until a match
2755                 fails, there's nothing to do since it might be the
2756                 complete sequence, and thus, something we don't want
2757                 copied.
2758             */
2759             if (data[i] == cont_seq[cmatch])
2760             {
2761                 cmatch++;
2762                 if (cmatch == strlen(cont_seq))
2763                 {
2764                     cmatch = 0; // complete match.  just reset the counter
2765
2766                     /*
2767                         it's possible for the ICS to not include the space
2768                         at the end of the last word, making our [correct]
2769                         join operation fuse two separate words.  the server
2770                         does this when the space occurs at the width setting.
2771                     */
2772                     if (!buf_len || buf[buf_len-1] != ' ')
2773                     {
2774                         *bp++ = ' ';
2775                         buf_len++;
2776                     }
2777                 }
2778                 continue;
2779             }
2780             else if (cmatch)
2781             {
2782                 /*
2783                     match failed, so we have to copy what matched before
2784                     falling through and copying this character.  In reality,
2785                     this will only ever be just the newline character, but
2786                     it doesn't hurt to be precise.
2787                 */
2788                 strncpy(bp, cont_seq, cmatch);
2789                 bp += cmatch;
2790                 buf_len += cmatch;
2791                 cmatch = 0;
2792             }
2793         }
2794
2795         // copy this char
2796         *bp++ = data[i];
2797         buf_len++;
2798     }
2799
2800         buf[buf_len] = NULLCHAR;
2801 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2802         next_out = 0;
2803         leftover_start = 0;
2804
2805         i = 0;
2806         while (i < buf_len) {
2807             /* Deal with part of the TELNET option negotiation
2808                protocol.  We refuse to do anything beyond the
2809                defaults, except that we allow the WILL ECHO option,
2810                which ICS uses to turn off password echoing when we are
2811                directly connected to it.  We reject this option
2812                if localLineEditing mode is on (always on in xboard)
2813                and we are talking to port 23, which might be a real
2814                telnet server that will try to keep WILL ECHO on permanently.
2815              */
2816             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2817                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2818                 unsigned char option;
2819                 oldi = i;
2820                 switch ((unsigned char) buf[++i]) {
2821                   case TN_WILL:
2822                     if (appData.debugMode)
2823                       fprintf(debugFP, "\n<WILL ");
2824                     switch (option = (unsigned char) buf[++i]) {
2825                       case TN_ECHO:
2826                         if (appData.debugMode)
2827                           fprintf(debugFP, "ECHO ");
2828                         /* Reply only if this is a change, according
2829                            to the protocol rules. */
2830                         if (remoteEchoOption) break;
2831                         if (appData.localLineEditing &&
2832                             atoi(appData.icsPort) == TN_PORT) {
2833                             TelnetRequest(TN_DONT, TN_ECHO);
2834                         } else {
2835                             EchoOff();
2836                             TelnetRequest(TN_DO, TN_ECHO);
2837                             remoteEchoOption = TRUE;
2838                         }
2839                         break;
2840                       default:
2841                         if (appData.debugMode)
2842                           fprintf(debugFP, "%d ", option);
2843                         /* Whatever this is, we don't want it. */
2844                         TelnetRequest(TN_DONT, option);
2845                         break;
2846                     }
2847                     break;
2848                   case TN_WONT:
2849                     if (appData.debugMode)
2850                       fprintf(debugFP, "\n<WONT ");
2851                     switch (option = (unsigned char) buf[++i]) {
2852                       case TN_ECHO:
2853                         if (appData.debugMode)
2854                           fprintf(debugFP, "ECHO ");
2855                         /* Reply only if this is a change, according
2856                            to the protocol rules. */
2857                         if (!remoteEchoOption) break;
2858                         EchoOn();
2859                         TelnetRequest(TN_DONT, TN_ECHO);
2860                         remoteEchoOption = FALSE;
2861                         break;
2862                       default:
2863                         if (appData.debugMode)
2864                           fprintf(debugFP, "%d ", (unsigned char) option);
2865                         /* Whatever this is, it must already be turned
2866                            off, because we never agree to turn on
2867                            anything non-default, so according to the
2868                            protocol rules, we don't reply. */
2869                         break;
2870                     }
2871                     break;
2872                   case TN_DO:
2873                     if (appData.debugMode)
2874                       fprintf(debugFP, "\n<DO ");
2875                     switch (option = (unsigned char) buf[++i]) {
2876                       default:
2877                         /* Whatever this is, we refuse to do it. */
2878                         if (appData.debugMode)
2879                           fprintf(debugFP, "%d ", option);
2880                         TelnetRequest(TN_WONT, option);
2881                         break;
2882                     }
2883                     break;
2884                   case TN_DONT:
2885                     if (appData.debugMode)
2886                       fprintf(debugFP, "\n<DONT ");
2887                     switch (option = (unsigned char) buf[++i]) {
2888                       default:
2889                         if (appData.debugMode)
2890                           fprintf(debugFP, "%d ", option);
2891                         /* Whatever this is, we are already not doing
2892                            it, because we never agree to do anything
2893                            non-default, so according to the protocol
2894                            rules, we don't reply. */
2895                         break;
2896                     }
2897                     break;
2898                   case TN_IAC:
2899                     if (appData.debugMode)
2900                       fprintf(debugFP, "\n<IAC ");
2901                     /* Doubled IAC; pass it through */
2902                     i--;
2903                     break;
2904                   default:
2905                     if (appData.debugMode)
2906                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2907                     /* Drop all other telnet commands on the floor */
2908                     break;
2909                 }
2910                 if (oldi > next_out)
2911                   SendToPlayer(&buf[next_out], oldi - next_out);
2912                 if (++i > next_out)
2913                   next_out = i;
2914                 continue;
2915             }
2916
2917             /* OK, this at least will *usually* work */
2918             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2919                 loggedOn = TRUE;
2920             }
2921
2922             if (loggedOn && !intfSet) {
2923                 if (ics_type == ICS_ICC) {
2924                   snprintf(str, MSG_SIZ,
2925                           "/set-quietly interface %s\n/set-quietly style 12\n",
2926                           programVersion);
2927                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2928                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2929                 } else if (ics_type == ICS_CHESSNET) {
2930                   snprintf(str, MSG_SIZ, "/style 12\n");
2931                 } else {
2932                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2933                   strcat(str, programVersion);
2934                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2935                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2936                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2937 #ifdef WIN32
2938                   strcat(str, "$iset nohighlight 1\n");
2939 #endif
2940                   strcat(str, "$iset lock 1\n$style 12\n");
2941                 }
2942                 SendToICS(str);
2943                 NotifyFrontendLogin();
2944                 intfSet = TRUE;
2945             }
2946
2947             if (started == STARTED_COMMENT) {
2948                 /* Accumulate characters in comment */
2949                 parse[parse_pos++] = buf[i];
2950                 if (buf[i] == '\n') {
2951                     parse[parse_pos] = NULLCHAR;
2952                     if(chattingPartner>=0) {
2953                         char mess[MSG_SIZ];
2954                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2955                         OutputChatMessage(chattingPartner, mess);
2956                         chattingPartner = -1;
2957                         next_out = i+1; // [HGM] suppress printing in ICS window
2958                     } else
2959                     if(!suppressKibitz) // [HGM] kibitz
2960                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2961                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2962                         int nrDigit = 0, nrAlph = 0, j;
2963                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2964                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2965                         parse[parse_pos] = NULLCHAR;
2966                         // try to be smart: if it does not look like search info, it should go to
2967                         // ICS interaction window after all, not to engine-output window.
2968                         for(j=0; j<parse_pos; j++) { // count letters and digits
2969                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2970                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2971                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2972                         }
2973                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2974                             int depth=0; float score;
2975                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2976                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2977                                 pvInfoList[forwardMostMove-1].depth = depth;
2978                                 pvInfoList[forwardMostMove-1].score = 100*score;
2979                             }
2980                             OutputKibitz(suppressKibitz, parse);
2981                         } else {
2982                             char tmp[MSG_SIZ];
2983                             if(gameMode == IcsObserving) // restore original ICS messages
2984                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
2985                             else
2986                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2987                             SendToPlayer(tmp, strlen(tmp));
2988                         }
2989                         next_out = i+1; // [HGM] suppress printing in ICS window
2990                     }
2991                     started = STARTED_NONE;
2992                 } else {
2993                     /* Don't match patterns against characters in comment */
2994                     i++;
2995                     continue;
2996                 }
2997             }
2998             if (started == STARTED_CHATTER) {
2999                 if (buf[i] != '\n') {
3000                     /* Don't match patterns against characters in chatter */
3001                     i++;
3002                     continue;
3003                 }
3004                 started = STARTED_NONE;
3005                 if(suppressKibitz) next_out = i+1;
3006             }
3007
3008             /* Kludge to deal with rcmd protocol */
3009             if (firstTime && looking_at(buf, &i, "\001*")) {
3010                 DisplayFatalError(&buf[1], 0, 1);
3011                 continue;
3012             } else {
3013                 firstTime = FALSE;
3014             }
3015
3016             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3017                 ics_type = ICS_ICC;
3018                 ics_prefix = "/";
3019                 if (appData.debugMode)
3020                   fprintf(debugFP, "ics_type %d\n", ics_type);
3021                 continue;
3022             }
3023             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3024                 ics_type = ICS_FICS;
3025                 ics_prefix = "$";
3026                 if (appData.debugMode)
3027                   fprintf(debugFP, "ics_type %d\n", ics_type);
3028                 continue;
3029             }
3030             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3031                 ics_type = ICS_CHESSNET;
3032                 ics_prefix = "/";
3033                 if (appData.debugMode)
3034                   fprintf(debugFP, "ics_type %d\n", ics_type);
3035                 continue;
3036             }
3037
3038             if (!loggedOn &&
3039                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3040                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3041                  looking_at(buf, &i, "will be \"*\""))) {
3042               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3043               continue;
3044             }
3045
3046             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3047               char buf[MSG_SIZ];
3048               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3049               DisplayIcsInteractionTitle(buf);
3050               have_set_title = TRUE;
3051             }
3052
3053             /* skip finger notes */
3054             if (started == STARTED_NONE &&
3055                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3056                  (buf[i] == '1' && buf[i+1] == '0')) &&
3057                 buf[i+2] == ':' && buf[i+3] == ' ') {
3058               started = STARTED_CHATTER;
3059               i += 3;
3060               continue;
3061             }
3062
3063             oldi = i;
3064             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3065             if(appData.seekGraph) {
3066                 if(soughtPending && MatchSoughtLine(buf+i)) {
3067                     i = strstr(buf+i, "rated") - buf;
3068                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3069                     next_out = leftover_start = i;
3070                     started = STARTED_CHATTER;
3071                     suppressKibitz = TRUE;
3072                     continue;
3073                 }
3074                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3075                         && looking_at(buf, &i, "* ads displayed")) {
3076                     soughtPending = FALSE;
3077                     seekGraphUp = TRUE;
3078                     DrawSeekGraph();
3079                     continue;
3080                 }
3081                 if(appData.autoRefresh) {
3082                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3083                         int s = (ics_type == ICS_ICC); // ICC format differs
3084                         if(seekGraphUp)
3085                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3086                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3087                         looking_at(buf, &i, "*% "); // eat prompt
3088                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3089                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3090                         next_out = i; // suppress
3091                         continue;
3092                     }
3093                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3094                         char *p = star_match[0];
3095                         while(*p) {
3096                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3097                             while(*p && *p++ != ' '); // next
3098                         }
3099                         looking_at(buf, &i, "*% "); // eat prompt
3100                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3101                         next_out = i;
3102                         continue;
3103                     }
3104                 }
3105             }
3106
3107             /* skip formula vars */
3108             if (started == STARTED_NONE &&
3109                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3110               started = STARTED_CHATTER;
3111               i += 3;
3112               continue;
3113             }
3114
3115             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3116             if (appData.autoKibitz && started == STARTED_NONE &&
3117                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3118                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3119                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3120                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3121                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3122                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3123                         suppressKibitz = TRUE;
3124                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3125                         next_out = i;
3126                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3127                                 && (gameMode == IcsPlayingWhite)) ||
3128                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3129                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3130                             started = STARTED_CHATTER; // own kibitz we simply discard
3131                         else {
3132                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3133                             parse_pos = 0; parse[0] = NULLCHAR;
3134                             savingComment = TRUE;
3135                             suppressKibitz = gameMode != IcsObserving ? 2 :
3136                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3137                         }
3138                         continue;
3139                 } else
3140                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3141                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3142                          && atoi(star_match[0])) {
3143                     // suppress the acknowledgements of our own autoKibitz
3144                     char *p;
3145                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3146                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3147                     SendToPlayer(star_match[0], strlen(star_match[0]));
3148                     if(looking_at(buf, &i, "*% ")) // eat prompt
3149                         suppressKibitz = FALSE;
3150                     next_out = i;
3151                     continue;
3152                 }
3153             } // [HGM] kibitz: end of patch
3154
3155             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3156
3157             // [HGM] chat: intercept tells by users for which we have an open chat window
3158             channel = -1;
3159             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3160                                            looking_at(buf, &i, "* whispers:") ||
3161                                            looking_at(buf, &i, "* kibitzes:") ||
3162                                            looking_at(buf, &i, "* shouts:") ||
3163                                            looking_at(buf, &i, "* c-shouts:") ||
3164                                            looking_at(buf, &i, "--> * ") ||
3165                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3166                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3167                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3168                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3169                 int p;
3170                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3171                 chattingPartner = -1;
3172
3173                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3174                 for(p=0; p<MAX_CHAT; p++) {
3175                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3176                     talker[0] = '['; strcat(talker, "] ");
3177                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3178                     chattingPartner = p; break;
3179                     }
3180                 } else
3181                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3182                 for(p=0; p<MAX_CHAT; p++) {
3183                     if(!strcmp("kibitzes", chatPartner[p])) {
3184                         talker[0] = '['; strcat(talker, "] ");
3185                         chattingPartner = p; break;
3186                     }
3187                 } else
3188                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3189                 for(p=0; p<MAX_CHAT; p++) {
3190                     if(!strcmp("whispers", chatPartner[p])) {
3191                         talker[0] = '['; strcat(talker, "] ");
3192                         chattingPartner = p; break;
3193                     }
3194                 } else
3195                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3196                   if(buf[i-8] == '-' && buf[i-3] == 't')
3197                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3198                     if(!strcmp("c-shouts", chatPartner[p])) {
3199                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3200                         chattingPartner = p; break;
3201                     }
3202                   }
3203                   if(chattingPartner < 0)
3204                   for(p=0; p<MAX_CHAT; p++) {
3205                     if(!strcmp("shouts", chatPartner[p])) {
3206                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3207                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3208                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3209                         chattingPartner = p; break;
3210                     }
3211                   }
3212                 }
3213                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3214                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3215                     talker[0] = 0; Colorize(ColorTell, FALSE);
3216                     chattingPartner = p; break;
3217                 }
3218                 if(chattingPartner<0) i = oldi; else {
3219                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3220                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3221                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3222                     started = STARTED_COMMENT;
3223                     parse_pos = 0; parse[0] = NULLCHAR;
3224                     savingComment = 3 + chattingPartner; // counts as TRUE
3225                     suppressKibitz = TRUE;
3226                     continue;
3227                 }
3228             } // [HGM] chat: end of patch
3229
3230           backup = i;
3231             if (appData.zippyTalk || appData.zippyPlay) {
3232                 /* [DM] Backup address for color zippy lines */
3233 #if ZIPPY
3234                if (loggedOn == TRUE)
3235                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3236                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3237 #endif
3238             } // [DM] 'else { ' deleted
3239                 if (
3240                     /* Regular tells and says */
3241                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3242                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3243                     looking_at(buf, &i, "* says: ") ||
3244                     /* Don't color "message" or "messages" output */
3245                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3246                     looking_at(buf, &i, "*. * at *:*: ") ||
3247                     looking_at(buf, &i, "--* (*:*): ") ||
3248                     /* Message notifications (same color as tells) */
3249                     looking_at(buf, &i, "* has left a message ") ||
3250                     looking_at(buf, &i, "* just sent you a message:\n") ||
3251                     /* Whispers and kibitzes */
3252                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3253                     looking_at(buf, &i, "* kibitzes: ") ||
3254                     /* Channel tells */
3255                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3256
3257                   if (tkind == 1 && strchr(star_match[0], ':')) {
3258                       /* Avoid "tells you:" spoofs in channels */
3259                      tkind = 3;
3260                   }
3261                   if (star_match[0][0] == NULLCHAR ||
3262                       strchr(star_match[0], ' ') ||
3263                       (tkind == 3 && strchr(star_match[1], ' '))) {
3264                     /* Reject bogus matches */
3265                     i = oldi;
3266                   } else {
3267                     if (appData.colorize) {
3268                       if (oldi > next_out) {
3269                         SendToPlayer(&buf[next_out], oldi - next_out);
3270                         next_out = oldi;
3271                       }
3272                       switch (tkind) {
3273                       case 1:
3274                         Colorize(ColorTell, FALSE);
3275                         curColor = ColorTell;
3276                         break;
3277                       case 2:
3278                         Colorize(ColorKibitz, FALSE);
3279                         curColor = ColorKibitz;
3280                         break;
3281                       case 3:
3282                         p = strrchr(star_match[1], '(');
3283                         if (p == NULL) {
3284                           p = star_match[1];
3285                         } else {
3286                           p++;
3287                         }
3288                         if (atoi(p) == 1) {
3289                           Colorize(ColorChannel1, FALSE);
3290                           curColor = ColorChannel1;
3291                         } else {
3292                           Colorize(ColorChannel, FALSE);
3293                           curColor = ColorChannel;
3294                         }
3295                         break;
3296                       case 5:
3297                         curColor = ColorNormal;
3298                         break;
3299                       }
3300                     }
3301                     if (started == STARTED_NONE && appData.autoComment &&
3302                         (gameMode == IcsObserving ||
3303                          gameMode == IcsPlayingWhite ||
3304                          gameMode == IcsPlayingBlack)) {
3305                       parse_pos = i - oldi;
3306                       memcpy(parse, &buf[oldi], parse_pos);
3307                       parse[parse_pos] = NULLCHAR;
3308                       started = STARTED_COMMENT;
3309                       savingComment = TRUE;
3310                     } else {
3311                       started = STARTED_CHATTER;
3312                       savingComment = FALSE;
3313                     }
3314                     loggedOn = TRUE;
3315                     continue;
3316                   }
3317                 }
3318
3319                 if (looking_at(buf, &i, "* s-shouts: ") ||
3320                     looking_at(buf, &i, "* c-shouts: ")) {
3321                     if (appData.colorize) {
3322                         if (oldi > next_out) {
3323                             SendToPlayer(&buf[next_out], oldi - next_out);
3324                             next_out = oldi;
3325                         }
3326                         Colorize(ColorSShout, FALSE);
3327                         curColor = ColorSShout;
3328                     }
3329                     loggedOn = TRUE;
3330                     started = STARTED_CHATTER;
3331                     continue;
3332                 }
3333
3334                 if (looking_at(buf, &i, "--->")) {
3335                     loggedOn = TRUE;
3336                     continue;
3337                 }
3338
3339                 if (looking_at(buf, &i, "* shouts: ") ||
3340                     looking_at(buf, &i, "--> ")) {
3341                     if (appData.colorize) {
3342                         if (oldi > next_out) {
3343                             SendToPlayer(&buf[next_out], oldi - next_out);
3344                             next_out = oldi;
3345                         }
3346                         Colorize(ColorShout, FALSE);
3347                         curColor = ColorShout;
3348                     }
3349                     loggedOn = TRUE;
3350                     started = STARTED_CHATTER;
3351                     continue;
3352                 }
3353
3354                 if (looking_at( buf, &i, "Challenge:")) {
3355                     if (appData.colorize) {
3356                         if (oldi > next_out) {
3357                             SendToPlayer(&buf[next_out], oldi - next_out);
3358                             next_out = oldi;
3359                         }
3360                         Colorize(ColorChallenge, FALSE);
3361                         curColor = ColorChallenge;
3362                     }
3363                     loggedOn = TRUE;
3364                     continue;
3365                 }
3366
3367                 if (looking_at(buf, &i, "* offers you") ||
3368                     looking_at(buf, &i, "* offers to be") ||
3369                     looking_at(buf, &i, "* would like to") ||
3370                     looking_at(buf, &i, "* requests to") ||
3371                     looking_at(buf, &i, "Your opponent offers") ||
3372                     looking_at(buf, &i, "Your opponent requests")) {
3373
3374                     if (appData.colorize) {
3375                         if (oldi > next_out) {
3376                             SendToPlayer(&buf[next_out], oldi - next_out);
3377                             next_out = oldi;
3378                         }
3379                         Colorize(ColorRequest, FALSE);
3380                         curColor = ColorRequest;
3381                     }
3382                     continue;
3383                 }
3384
3385                 if (looking_at(buf, &i, "* (*) seeking")) {
3386                     if (appData.colorize) {
3387                         if (oldi > next_out) {
3388                             SendToPlayer(&buf[next_out], oldi - next_out);
3389                             next_out = oldi;
3390                         }
3391                         Colorize(ColorSeek, FALSE);
3392                         curColor = ColorSeek;
3393                     }
3394                     continue;
3395             }
3396
3397           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3398
3399             if (looking_at(buf, &i, "\\   ")) {
3400                 if (prevColor != ColorNormal) {
3401                     if (oldi > next_out) {
3402                         SendToPlayer(&buf[next_out], oldi - next_out);
3403                         next_out = oldi;
3404                     }
3405                     Colorize(prevColor, TRUE);
3406                     curColor = prevColor;
3407                 }
3408                 if (savingComment) {
3409                     parse_pos = i - oldi;
3410                     memcpy(parse, &buf[oldi], parse_pos);
3411                     parse[parse_pos] = NULLCHAR;
3412                     started = STARTED_COMMENT;
3413                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3414                         chattingPartner = savingComment - 3; // kludge to remember the box
3415                 } else {
3416                     started = STARTED_CHATTER;
3417                 }
3418                 continue;
3419             }
3420
3421             if (looking_at(buf, &i, "Black Strength :") ||
3422                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3423                 looking_at(buf, &i, "<10>") ||
3424                 looking_at(buf, &i, "#@#")) {
3425                 /* Wrong board style */
3426                 loggedOn = TRUE;
3427                 SendToICS(ics_prefix);
3428                 SendToICS("set style 12\n");
3429                 SendToICS(ics_prefix);
3430                 SendToICS("refresh\n");
3431                 continue;
3432             }
3433
3434             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3435                 ICSInitScript();
3436                 have_sent_ICS_logon = 1;
3437                 continue;
3438             }
3439
3440             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3441                 (looking_at(buf, &i, "\n<12> ") ||
3442                  looking_at(buf, &i, "<12> "))) {
3443                 loggedOn = TRUE;
3444                 if (oldi > next_out) {
3445                     SendToPlayer(&buf[next_out], oldi - next_out);
3446                 }
3447                 next_out = i;
3448                 started = STARTED_BOARD;
3449                 parse_pos = 0;
3450                 continue;
3451             }
3452
3453             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3454                 looking_at(buf, &i, "<b1> ")) {
3455                 if (oldi > next_out) {
3456                     SendToPlayer(&buf[next_out], oldi - next_out);
3457                 }
3458                 next_out = i;
3459                 started = STARTED_HOLDINGS;
3460                 parse_pos = 0;
3461                 continue;
3462             }
3463
3464             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3465                 loggedOn = TRUE;
3466                 /* Header for a move list -- first line */
3467
3468                 switch (ics_getting_history) {
3469                   case H_FALSE:
3470                     switch (gameMode) {
3471                       case IcsIdle:
3472                       case BeginningOfGame:
3473                         /* User typed "moves" or "oldmoves" while we
3474                            were idle.  Pretend we asked for these
3475                            moves and soak them up so user can step
3476                            through them and/or save them.
3477                            */
3478                         Reset(FALSE, TRUE);
3479                         gameMode = IcsObserving;
3480                         ModeHighlight();
3481                         ics_gamenum = -1;
3482                         ics_getting_history = H_GOT_UNREQ_HEADER;
3483                         break;
3484                       case EditGame: /*?*/
3485                       case EditPosition: /*?*/
3486                         /* Should above feature work in these modes too? */
3487                         /* For now it doesn't */
3488                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3489                         break;
3490                       default:
3491                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3492                         break;
3493                     }
3494                     break;
3495                   case H_REQUESTED:
3496                     /* Is this the right one? */
3497                     if (gameInfo.white && gameInfo.black &&
3498                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3499                         strcmp(gameInfo.black, star_match[2]) == 0) {
3500                         /* All is well */
3501                         ics_getting_history = H_GOT_REQ_HEADER;
3502                     }
3503                     break;
3504                   case H_GOT_REQ_HEADER:
3505                   case H_GOT_UNREQ_HEADER:
3506                   case H_GOT_UNWANTED_HEADER:
3507                   case H_GETTING_MOVES:
3508                     /* Should not happen */
3509                     DisplayError(_("Error gathering move list: two headers"), 0);
3510                     ics_getting_history = H_FALSE;
3511                     break;
3512                 }
3513
3514                 /* Save player ratings into gameInfo if needed */
3515                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3516                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3517                     (gameInfo.whiteRating == -1 ||
3518                      gameInfo.blackRating == -1)) {
3519
3520                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3521                     gameInfo.blackRating = string_to_rating(star_match[3]);
3522                     if (appData.debugMode)
3523                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3524                               gameInfo.whiteRating, gameInfo.blackRating);
3525                 }
3526                 continue;
3527             }
3528
3529             if (looking_at(buf, &i,
3530               "* * match, initial time: * minute*, increment: * second")) {
3531                 /* Header for a move list -- second line */
3532                 /* Initial board will follow if this is a wild game */
3533                 if (gameInfo.event != NULL) free(gameInfo.event);
3534                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3535                 gameInfo.event = StrSave(str);
3536                 /* [HGM] we switched variant. Translate boards if needed. */
3537                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3538                 continue;
3539             }
3540
3541             if (looking_at(buf, &i, "Move  ")) {
3542                 /* Beginning of a move list */
3543                 switch (ics_getting_history) {
3544                   case H_FALSE:
3545                     /* Normally should not happen */
3546                     /* Maybe user hit reset while we were parsing */
3547                     break;
3548                   case H_REQUESTED:
3549                     /* Happens if we are ignoring a move list that is not
3550                      * the one we just requested.  Common if the user
3551                      * tries to observe two games without turning off
3552                      * getMoveList */
3553                     break;
3554                   case H_GETTING_MOVES:
3555                     /* Should not happen */
3556                     DisplayError(_("Error gathering move list: nested"), 0);
3557                     ics_getting_history = H_FALSE;
3558                     break;
3559                   case H_GOT_REQ_HEADER:
3560                     ics_getting_history = H_GETTING_MOVES;
3561                     started = STARTED_MOVES;
3562                     parse_pos = 0;
3563                     if (oldi > next_out) {
3564                         SendToPlayer(&buf[next_out], oldi - next_out);
3565                     }
3566                     break;
3567                   case H_GOT_UNREQ_HEADER:
3568                     ics_getting_history = H_GETTING_MOVES;
3569                     started = STARTED_MOVES_NOHIDE;
3570                     parse_pos = 0;
3571                     break;
3572                   case H_GOT_UNWANTED_HEADER:
3573                     ics_getting_history = H_FALSE;
3574                     break;
3575                 }
3576                 continue;
3577             }
3578
3579             if (looking_at(buf, &i, "% ") ||
3580                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3581                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3582                 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3583                     soughtPending = FALSE;
3584                     seekGraphUp = TRUE;
3585                     DrawSeekGraph();
3586                 }
3587                 if(suppressKibitz) next_out = i;
3588                 savingComment = FALSE;
3589                 suppressKibitz = 0;
3590                 switch (started) {
3591                   case STARTED_MOVES:
3592                   case STARTED_MOVES_NOHIDE:
3593                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3594                     parse[parse_pos + i - oldi] = NULLCHAR;
3595                     ParseGameHistory(parse);
3596 #if ZIPPY
3597                     if (appData.zippyPlay && first.initDone) {
3598                         FeedMovesToProgram(&first, forwardMostMove);
3599                         if (gameMode == IcsPlayingWhite) {
3600                             if (WhiteOnMove(forwardMostMove)) {
3601                                 if (first.sendTime) {
3602                                   if (first.useColors) {
3603                                     SendToProgram("black\n", &first);
3604                                   }
3605                                   SendTimeRemaining(&first, TRUE);
3606                                 }
3607                                 if (first.useColors) {
3608                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3609                                 }
3610                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3611                                 first.maybeThinking = TRUE;
3612                             } else {
3613                                 if (first.usePlayother) {
3614                                   if (first.sendTime) {
3615                                     SendTimeRemaining(&first, TRUE);
3616                                   }
3617                                   SendToProgram("playother\n", &first);
3618                                   firstMove = FALSE;
3619                                 } else {
3620                                   firstMove = TRUE;
3621                                 }
3622                             }
3623                         } else if (gameMode == IcsPlayingBlack) {
3624                             if (!WhiteOnMove(forwardMostMove)) {
3625                                 if (first.sendTime) {
3626                                   if (first.useColors) {
3627                                     SendToProgram("white\n", &first);
3628                                   }
3629                                   SendTimeRemaining(&first, FALSE);
3630                                 }
3631                                 if (first.useColors) {
3632                                   SendToProgram("black\n", &first);
3633                                 }
3634                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3635                                 first.maybeThinking = TRUE;
3636                             } else {
3637                                 if (first.usePlayother) {
3638                                   if (first.sendTime) {
3639                                     SendTimeRemaining(&first, FALSE);
3640                                   }
3641                                   SendToProgram("playother\n", &first);
3642                                   firstMove = FALSE;
3643                                 } else {
3644                                   firstMove = TRUE;
3645                                 }
3646                             }
3647                         }
3648                     }
3649 #endif
3650                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3651                         /* Moves came from oldmoves or moves command
3652                            while we weren't doing anything else.
3653                            */
3654                         currentMove = forwardMostMove;
3655                         ClearHighlights();/*!!could figure this out*/
3656                         flipView = appData.flipView;
3657                         DrawPosition(TRUE, boards[currentMove]);
3658                         DisplayBothClocks();
3659                         snprintf(str, MSG_SIZ, "%s %s %s",
3660                                 gameInfo.white, _("vs."),  gameInfo.black);
3661                         DisplayTitle(str);
3662                         gameMode = IcsIdle;
3663                     } else {
3664                         /* Moves were history of an active game */
3665                         if (gameInfo.resultDetails != NULL) {
3666                             free(gameInfo.resultDetails);
3667                             gameInfo.resultDetails = NULL;
3668                         }
3669                     }
3670                     HistorySet(parseList, backwardMostMove,
3671                                forwardMostMove, currentMove-1);
3672                     DisplayMove(currentMove - 1);
3673                     if (started == STARTED_MOVES) next_out = i;
3674                     started = STARTED_NONE;
3675                     ics_getting_history = H_FALSE;
3676                     break;
3677
3678                   case STARTED_OBSERVE:
3679                     started = STARTED_NONE;
3680                     SendToICS(ics_prefix);
3681                     SendToICS("refresh\n");
3682                     break;
3683
3684                   default:
3685                     break;
3686                 }
3687                 if(bookHit) { // [HGM] book: simulate book reply
3688                     static char bookMove[MSG_SIZ]; // a bit generous?
3689
3690                     programStats.nodes = programStats.depth = programStats.time =
3691                     programStats.score = programStats.got_only_move = 0;
3692                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3693
3694                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3695                     strcat(bookMove, bookHit);
3696                     HandleMachineMove(bookMove, &first);
3697                 }
3698                 continue;
3699             }
3700
3701             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3702                  started == STARTED_HOLDINGS ||
3703                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3704                 /* Accumulate characters in move list or board */
3705                 parse[parse_pos++] = buf[i];
3706             }
3707
3708             /* Start of game messages.  Mostly we detect start of game
3709                when the first board image arrives.  On some versions
3710                of the ICS, though, we need to do a "refresh" after starting
3711                to observe in order to get the current board right away. */
3712             if (looking_at(buf, &i, "Adding game * to observation list")) {
3713                 started = STARTED_OBSERVE;
3714                 continue;
3715             }
3716
3717             /* Handle auto-observe */
3718             if (appData.autoObserve &&
3719                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3720                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3721                 char *player;
3722                 /* Choose the player that was highlighted, if any. */
3723                 if (star_match[0][0] == '\033' ||
3724                     star_match[1][0] != '\033') {
3725                     player = star_match[0];
3726                 } else {
3727                     player = star_match[2];
3728                 }
3729                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3730                         ics_prefix, StripHighlightAndTitle(player));
3731                 SendToICS(str);
3732
3733                 /* Save ratings from notify string */
3734                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3735                 player1Rating = string_to_rating(star_match[1]);
3736                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3737                 player2Rating = string_to_rating(star_match[3]);
3738
3739                 if (appData.debugMode)
3740                   fprintf(debugFP,
3741                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3742                           player1Name, player1Rating,
3743                           player2Name, player2Rating);
3744
3745                 continue;
3746             }
3747
3748             /* Deal with automatic examine mode after a game,
3749                and with IcsObserving -> IcsExamining transition */
3750             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3751                 looking_at(buf, &i, "has made you an examiner of game *")) {
3752
3753                 int gamenum = atoi(star_match[0]);
3754                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3755                     gamenum == ics_gamenum) {
3756                     /* We were already playing or observing this game;
3757                        no need to refetch history */
3758                     gameMode = IcsExamining;
3759                     if (pausing) {
3760                         pauseExamForwardMostMove = forwardMostMove;
3761                     } else if (currentMove < forwardMostMove) {
3762                         ForwardInner(forwardMostMove);
3763                     }
3764                 } else {
3765                     /* I don't think this case really can happen */
3766                     SendToICS(ics_prefix);
3767                     SendToICS("refresh\n");
3768                 }
3769                 continue;
3770             }
3771
3772             /* Error messages */
3773 //          if (ics_user_moved) {
3774             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3775                 if (looking_at(buf, &i, "Illegal move") ||
3776                     looking_at(buf, &i, "Not a legal move") ||
3777                     looking_at(buf, &i, "Your king is in check") ||
3778                     looking_at(buf, &i, "It isn't your turn") ||
3779                     looking_at(buf, &i, "It is not your move")) {
3780                     /* Illegal move */
3781                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3782                         currentMove = forwardMostMove-1;
3783                         DisplayMove(currentMove - 1); /* before DMError */
3784                         DrawPosition(FALSE, boards[currentMove]);
3785                         SwitchClocks(forwardMostMove-1); // [HGM] race
3786                         DisplayBothClocks();
3787                     }
3788                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3789                     ics_user_moved = 0;
3790                     continue;
3791                 }
3792             }
3793
3794             if (looking_at(buf, &i, "still have time") ||
3795                 looking_at(buf, &i, "not out of time") ||
3796                 looking_at(buf, &i, "either player is out of time") ||
3797                 looking_at(buf, &i, "has timeseal; checking")) {
3798                 /* We must have called his flag a little too soon */
3799                 whiteFlag = blackFlag = FALSE;
3800                 continue;
3801             }
3802
3803             if (looking_at(buf, &i, "added * seconds to") ||
3804                 looking_at(buf, &i, "seconds were added to")) {
3805                 /* Update the clocks */
3806                 SendToICS(ics_prefix);
3807                 SendToICS("refresh\n");
3808                 continue;
3809             }
3810
3811             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3812                 ics_clock_paused = TRUE;
3813                 StopClocks();
3814                 continue;
3815             }
3816
3817             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3818                 ics_clock_paused = FALSE;
3819                 StartClocks();
3820                 continue;
3821             }
3822
3823             /* Grab player ratings from the Creating: message.
3824                Note we have to check for the special case when
3825                the ICS inserts things like [white] or [black]. */
3826             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3827                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3828                 /* star_matches:
3829                    0    player 1 name (not necessarily white)
3830                    1    player 1 rating
3831                    2    empty, white, or black (IGNORED)
3832                    3    player 2 name (not necessarily black)
3833                    4    player 2 rating
3834
3835                    The names/ratings are sorted out when the game
3836                    actually starts (below).
3837                 */
3838                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3839                 player1Rating = string_to_rating(star_match[1]);
3840                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3841                 player2Rating = string_to_rating(star_match[4]);
3842
3843                 if (appData.debugMode)
3844                   fprintf(debugFP,
3845                           "Ratings from 'Creating:' %s %d, %s %d\n",
3846                           player1Name, player1Rating,
3847                           player2Name, player2Rating);
3848
3849                 continue;
3850             }
3851
3852             /* Improved generic start/end-of-game messages */
3853             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3854                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3855                 /* If tkind == 0: */
3856                 /* star_match[0] is the game number */
3857                 /*           [1] is the white player's name */
3858                 /*           [2] is the black player's name */
3859                 /* For end-of-game: */
3860                 /*           [3] is the reason for the game end */
3861                 /*           [4] is a PGN end game-token, preceded by " " */
3862                 /* For start-of-game: */
3863                 /*           [3] begins with "Creating" or "Continuing" */
3864                 /*           [4] is " *" or empty (don't care). */
3865                 int gamenum = atoi(star_match[0]);
3866                 char *whitename, *blackname, *why, *endtoken;
3867                 ChessMove endtype = EndOfFile;
3868
3869                 if (tkind == 0) {
3870                   whitename = star_match[1];
3871                   blackname = star_match[2];
3872                   why = star_match[3];
3873                   endtoken = star_match[4];
3874                 } else {
3875                   whitename = star_match[1];
3876                   blackname = star_match[3];
3877                   why = star_match[5];
3878                   endtoken = star_match[6];
3879                 }
3880
3881                 /* Game start messages */
3882                 if (strncmp(why, "Creating ", 9) == 0 ||
3883                     strncmp(why, "Continuing ", 11) == 0) {
3884                     gs_gamenum = gamenum;
3885                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3886                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
3887                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3888 #if ZIPPY
3889                     if (appData.zippyPlay) {
3890                         ZippyGameStart(whitename, blackname);
3891                     }
3892 #endif /*ZIPPY*/
3893                     partnerBoardValid = FALSE; // [HGM] bughouse
3894                     continue;
3895                 }
3896
3897                 /* Game end messages */
3898                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3899                     ics_gamenum != gamenum) {
3900                     continue;
3901                 }
3902                 while (endtoken[0] == ' ') endtoken++;
3903                 switch (endtoken[0]) {
3904                   case '*':
3905                   default:
3906                     endtype = GameUnfinished;
3907                     break;
3908                   case '0':
3909                     endtype = BlackWins;
3910                     break;
3911                   case '1':
3912                     if (endtoken[1] == '/')
3913                       endtype = GameIsDrawn;
3914                     else
3915                       endtype = WhiteWins;
3916                     break;
3917                 }
3918                 GameEnds(endtype, why, GE_ICS);
3919 #if ZIPPY
3920                 if (appData.zippyPlay && first.initDone) {
3921                     ZippyGameEnd(endtype, why);
3922                     if (first.pr == NoProc) {
3923                       /* Start the next process early so that we'll
3924                          be ready for the next challenge */
3925                       StartChessProgram(&first);
3926                     }
3927                     /* Send "new" early, in case this command takes
3928                        a long time to finish, so that we'll be ready
3929                        for the next challenge. */
3930                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3931                     Reset(TRUE, TRUE);
3932                 }
3933 #endif /*ZIPPY*/
3934                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3935                 continue;
3936             }
3937
3938             if (looking_at(buf, &i, "Removing game * from observation") ||
3939                 looking_at(buf, &i, "no longer observing game *") ||
3940                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3941                 if (gameMode == IcsObserving &&
3942                     atoi(star_match[0]) == ics_gamenum)
3943                   {
3944                       /* icsEngineAnalyze */
3945                       if (appData.icsEngineAnalyze) {
3946                             ExitAnalyzeMode();
3947                             ModeHighlight();
3948                       }
3949                       StopClocks();
3950                       gameMode = IcsIdle;
3951                       ics_gamenum = -1;
3952                       ics_user_moved = FALSE;
3953                   }
3954                 continue;
3955             }
3956
3957             if (looking_at(buf, &i, "no longer examining game *")) {
3958                 if (gameMode == IcsExamining &&
3959                     atoi(star_match[0]) == ics_gamenum)
3960                   {
3961                       gameMode = IcsIdle;
3962                       ics_gamenum = -1;
3963                       ics_user_moved = FALSE;
3964                   }
3965                 continue;
3966             }
3967
3968             /* Advance leftover_start past any newlines we find,
3969                so only partial lines can get reparsed */
3970             if (looking_at(buf, &i, "\n")) {
3971                 prevColor = curColor;
3972                 if (curColor != ColorNormal) {
3973                     if (oldi > next_out) {
3974                         SendToPlayer(&buf[next_out], oldi - next_out);
3975                         next_out = oldi;
3976                     }
3977                     Colorize(ColorNormal, FALSE);
3978                     curColor = ColorNormal;
3979                 }
3980                 if (started == STARTED_BOARD) {
3981                     started = STARTED_NONE;
3982                     parse[parse_pos] = NULLCHAR;
3983                     ParseBoard12(parse);
3984                     ics_user_moved = 0;
3985
3986                     /* Send premove here */
3987                     if (appData.premove) {
3988                       char str[MSG_SIZ];
3989                       if (currentMove == 0 &&
3990                           gameMode == IcsPlayingWhite &&
3991                           appData.premoveWhite) {
3992                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3993                         if (appData.debugMode)
3994                           fprintf(debugFP, "Sending premove:\n");
3995                         SendToICS(str);
3996                       } else if (currentMove == 1 &&
3997                                  gameMode == IcsPlayingBlack &&
3998                                  appData.premoveBlack) {
3999                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
4000                         if (appData.debugMode)
4001                           fprintf(debugFP, "Sending premove:\n");
4002                         SendToICS(str);
4003                       } else if (gotPremove) {
4004                         gotPremove = 0;
4005                         ClearPremoveHighlights();
4006                         if (appData.debugMode)
4007                           fprintf(debugFP, "Sending premove:\n");
4008                           UserMoveEvent(premoveFromX, premoveFromY,
4009                                         premoveToX, premoveToY,
4010                                         premovePromoChar);
4011                       }
4012                     }
4013
4014                     /* Usually suppress following prompt */
4015                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4016                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4017                         if (looking_at(buf, &i, "*% ")) {
4018                             savingComment = FALSE;
4019                             suppressKibitz = 0;
4020                         }
4021                     }
4022                     next_out = i;
4023                 } else if (started == STARTED_HOLDINGS) {
4024                     int gamenum;
4025                     char new_piece[MSG_SIZ];
4026                     started = STARTED_NONE;
4027                     parse[parse_pos] = NULLCHAR;
4028                     if (appData.debugMode)
4029                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4030                                                         parse, currentMove);
4031                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4032                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4033                         if (gameInfo.variant == VariantNormal) {
4034                           /* [HGM] We seem to switch variant during a game!
4035                            * Presumably no holdings were displayed, so we have
4036                            * to move the position two files to the right to
4037                            * create room for them!
4038                            */
4039                           VariantClass newVariant;
4040                           switch(gameInfo.boardWidth) { // base guess on board width
4041                                 case 9:  newVariant = VariantShogi; break;
4042                                 case 10: newVariant = VariantGreat; break;
4043                                 default: newVariant = VariantCrazyhouse; break;
4044                           }
4045                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4046                           /* Get a move list just to see the header, which
4047                              will tell us whether this is really bug or zh */
4048                           if (ics_getting_history == H_FALSE) {
4049                             ics_getting_history = H_REQUESTED;
4050                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4051                             SendToICS(str);
4052                           }
4053                         }
4054                         new_piece[0] = NULLCHAR;
4055                         sscanf(parse, "game %d white [%s black [%s <- %s",
4056                                &gamenum, white_holding, black_holding,
4057                                new_piece);
4058                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4059                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4060                         /* [HGM] copy holdings to board holdings area */
4061                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4062                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4063                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4064 #if ZIPPY
4065                         if (appData.zippyPlay && first.initDone) {
4066                             ZippyHoldings(white_holding, black_holding,
4067                                           new_piece);
4068                         }
4069 #endif /*ZIPPY*/
4070                         if (tinyLayout || smallLayout) {
4071                             char wh[16], bh[16];
4072                             PackHolding(wh, white_holding);
4073                             PackHolding(bh, black_holding);
4074                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4075                                     gameInfo.white, gameInfo.black);
4076                         } else {
4077                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4078                                     gameInfo.white, white_holding, _("vs."),
4079                                     gameInfo.black, black_holding);
4080                         }
4081                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4082                         DrawPosition(FALSE, boards[currentMove]);
4083                         DisplayTitle(str);
4084                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4085                         sscanf(parse, "game %d white [%s black [%s <- %s",
4086                                &gamenum, white_holding, black_holding,
4087                                new_piece);
4088                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4089                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4090                         /* [HGM] copy holdings to partner-board holdings area */
4091                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4092                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4093                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4094                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4095                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4096                       }
4097                     }
4098                     /* Suppress following prompt */
4099                     if (looking_at(buf, &i, "*% ")) {
4100                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4101                         savingComment = FALSE;
4102                         suppressKibitz = 0;
4103                     }
4104                     next_out = i;
4105                 }
4106                 continue;
4107             }
4108
4109             i++;                /* skip unparsed character and loop back */
4110         }
4111
4112         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4113 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4114 //          SendToPlayer(&buf[next_out], i - next_out);
4115             started != STARTED_HOLDINGS && leftover_start > next_out) {
4116             SendToPlayer(&buf[next_out], leftover_start - next_out);
4117             next_out = i;
4118         }
4119
4120         leftover_len = buf_len - leftover_start;
4121         /* if buffer ends with something we couldn't parse,
4122            reparse it after appending the next read */
4123
4124     } else if (count == 0) {
4125         RemoveInputSource(isr);
4126         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4127     } else {
4128         DisplayFatalError(_("Error reading from ICS"), error, 1);
4129     }
4130 }
4131
4132
4133 /* Board style 12 looks like this:
4134
4135    <12> r-b---k- pp----pp ---bP--- ---p---- q------- ------P- P--Q--BP -----R-K W -1 0 0 0 0 0 0 paf MaxII 0 2 12 21 25 234 174 24 Q/d7-a4 (0:06) Qxa4 0 0
4136
4137  * The "<12> " is stripped before it gets to this routine.  The two
4138  * trailing 0's (flip state and clock ticking) are later addition, and
4139  * some chess servers may not have them, or may have only the first.
4140  * Additional trailing fields may be added in the future.
4141  */
4142
4143 #define PATTERN "%c%d%d%d%d%d%d%d%s%s%d%d%d%d%d%d%d%d%s%s%s%d%d"
4144
4145 #define RELATION_OBSERVING_PLAYED    0
4146 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4147 #define RELATION_PLAYING_MYMOVE      1
4148 #define RELATION_PLAYING_NOTMYMOVE  -1
4149 #define RELATION_EXAMINING           2
4150 #define RELATION_ISOLATED_BOARD     -3
4151 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4152
4153 void
4154 ParseBoard12 (char *string)
4155 {
4156     GameMode newGameMode;
4157     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
4158     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
4159     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4160     char to_play, board_chars[200];
4161     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4162     char black[32], white[32];
4163     Board board;
4164     int prevMove = currentMove;
4165     int ticking = 2;
4166     ChessMove moveType;
4167     int fromX, fromY, toX, toY;
4168     char promoChar;
4169     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4170     char *bookHit = NULL; // [HGM] book
4171     Boolean weird = FALSE, reqFlag = FALSE;
4172
4173     fromX = fromY = toX = toY = -1;
4174
4175     newGame = FALSE;
4176
4177     if (appData.debugMode)
4178       fprintf(debugFP, _("Parsing board: %s\n"), string);
4179
4180     move_str[0] = NULLCHAR;
4181     elapsed_time[0] = NULLCHAR;
4182     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4183         int  i = 0, j;
4184         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4185             if(string[i] == ' ') { ranks++; files = 0; }
4186             else files++;
4187             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4188             i++;
4189         }
4190         for(j = 0; j <i; j++) board_chars[j] = string[j];
4191         board_chars[i] = '\0';
4192         string += i + 1;
4193     }
4194     n = sscanf(string, PATTERN, &to_play, &double_push,
4195                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4196                &gamenum, white, black, &relation, &basetime, &increment,
4197                &white_stren, &black_stren, &white_time, &black_time,
4198                &moveNum, str, elapsed_time, move_str, &ics_flip,
4199                &ticking);
4200
4201     if (n < 21) {
4202         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4203         DisplayError(str, 0);
4204         return;
4205     }
4206
4207     /* Convert the move number to internal form */
4208     moveNum = (moveNum - 1) * 2;
4209     if (to_play == 'B') moveNum++;
4210     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4211       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4212                         0, 1);
4213       return;
4214     }
4215
4216     switch (relation) {
4217       case RELATION_OBSERVING_PLAYED:
4218       case RELATION_OBSERVING_STATIC:
4219         if (gamenum == -1) {
4220             /* Old ICC buglet */
4221             relation = RELATION_OBSERVING_STATIC;
4222         }
4223         newGameMode = IcsObserving;
4224         break;
4225       case RELATION_PLAYING_MYMOVE:
4226       case RELATION_PLAYING_NOTMYMOVE:
4227         newGameMode =
4228           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4229             IcsPlayingWhite : IcsPlayingBlack;
4230         soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4231         break;
4232       case RELATION_EXAMINING:
4233         newGameMode = IcsExamining;
4234         break;
4235       case RELATION_ISOLATED_BOARD:
4236       default:
4237         /* Just display this board.  If user was doing something else,
4238            we will forget about it until the next board comes. */
4239         newGameMode = IcsIdle;
4240         break;
4241       case RELATION_STARTING_POSITION:
4242         newGameMode = gameMode;
4243         break;
4244     }
4245
4246     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4247         gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4248          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4249       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4250       int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4251       static int lastBgGame = -1;
4252       char *toSqr;
4253       for (k = 0; k < ranks; k++) {
4254         for (j = 0; j < files; j++)
4255           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4256         if(gameInfo.holdingsWidth > 1) {
4257              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4258              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4259         }
4260       }
4261       CopyBoard(partnerBoard, board);
4262       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4263         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4264         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4265       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4266       if(toSqr = strchr(str, '-')) {
4267         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4268         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4269       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4270       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4271       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4272       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4273       if(twoBoards) {
4274           DisplayWhiteClock(white_time*fac, to_play == 'W');
4275           DisplayBlackClock(black_time*fac, to_play != 'W');
4276           activePartner = to_play;
4277           if(gamenum != lastBgGame) {
4278               char buf[MSG_SIZ];
4279               snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4280               DisplayTitle(buf);
4281           }
4282           lastBgGame = gamenum;
4283           activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4284                       partnerUp = 0; flipView = !flipView; } // [HGM] dual
4285       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4286                  (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4287       DisplayMessage(partnerStatus, "");
4288         partnerBoardValid = TRUE;
4289       return;
4290     }
4291
4292     if(appData.dualBoard && appData.bgObserve) {
4293         if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4294             SendToICS(ics_prefix), SendToICS("pobserve\n");
4295         else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4296             char buf[MSG_SIZ];
4297             snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4298             SendToICS(buf);
4299         }
4300     }
4301
4302     /* Modify behavior for initial board display on move listing
4303        of wild games.
4304        */
4305     switch (ics_getting_history) {
4306       case H_FALSE:
4307       case H_REQUESTED:
4308         break;
4309       case H_GOT_REQ_HEADER:
4310       case H_GOT_UNREQ_HEADER:
4311         /* This is the initial position of the current game */
4312         gamenum = ics_gamenum;
4313         moveNum = 0;            /* old ICS bug workaround */
4314         if (to_play == 'B') {
4315           startedFromSetupPosition = TRUE;
4316           blackPlaysFirst = TRUE;
4317           moveNum = 1;
4318           if (forwardMostMove == 0) forwardMostMove = 1;
4319           if (backwardMostMove == 0) backwardMostMove = 1;
4320           if (currentMove == 0) currentMove = 1;
4321         }
4322         newGameMode = gameMode;
4323         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4324         break;
4325       case H_GOT_UNWANTED_HEADER:
4326         /* This is an initial board that we don't want */
4327         return;
4328       case H_GETTING_MOVES:
4329         /* Should not happen */
4330         DisplayError(_("Error gathering move list: extra board"), 0);
4331         ics_getting_history = H_FALSE;
4332         return;
4333     }
4334
4335    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4336                                         move_str[1] == '@' && !gameInfo.holdingsWidth ||
4337                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4338      /* [HGM] We seem to have switched variant unexpectedly
4339       * Try to guess new variant from board size
4340       */
4341           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4342           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4343           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4344           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4345           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4346           if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4347           if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4348           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4349           /* Get a move list just to see the header, which
4350              will tell us whether this is really bug or zh */
4351           if (ics_getting_history == H_FALSE) {
4352             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4353             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4354             SendToICS(str);
4355           }
4356     }
4357
4358     /* Take action if this is the first board of a new game, or of a
4359        different game than is currently being displayed.  */
4360     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4361         relation == RELATION_ISOLATED_BOARD) {
4362
4363         /* Forget the old game and get the history (if any) of the new one */
4364         if (gameMode != BeginningOfGame) {
4365           Reset(TRUE, TRUE);
4366         }
4367         newGame = TRUE;
4368         if (appData.autoRaiseBoard) BoardToTop();
4369         prevMove = -3;
4370         if (gamenum == -1) {
4371             newGameMode = IcsIdle;
4372         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4373                    appData.getMoveList && !reqFlag) {
4374             /* Need to get game history */
4375             ics_getting_history = H_REQUESTED;
4376             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4377             SendToICS(str);
4378         }
4379
4380         /* Initially flip the board to have black on the bottom if playing
4381            black or if the ICS flip flag is set, but let the user change
4382            it with the Flip View button. */
4383         flipView = appData.autoFlipView ?
4384           (newGameMode == IcsPlayingBlack) || ics_flip :
4385           appData.flipView;
4386
4387         /* Done with values from previous mode; copy in new ones */
4388         gameMode = newGameMode;
4389         ModeHighlight();
4390         ics_gamenum = gamenum;
4391         if (gamenum == gs_gamenum) {
4392             int klen = strlen(gs_kind);
4393             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4394             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4395             gameInfo.event = StrSave(str);
4396         } else {
4397             gameInfo.event = StrSave("ICS game");
4398         }
4399         gameInfo.site = StrSave(appData.icsHost);
4400         gameInfo.date = PGNDate();
4401         gameInfo.round = StrSave("-");
4402         gameInfo.white = StrSave(white);
4403         gameInfo.black = StrSave(black);
4404         timeControl = basetime * 60 * 1000;
4405         timeControl_2 = 0;
4406         timeIncrement = increment * 1000;
4407         movesPerSession = 0;
4408         gameInfo.timeControl = TimeControlTagValue();
4409         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4410   if (appData.debugMode) {
4411     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4412     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4413     setbuf(debugFP, NULL);
4414   }
4415
4416         gameInfo.outOfBook = NULL;
4417
4418         /* Do we have the ratings? */
4419         if (strcmp(player1Name, white) == 0 &&
4420             strcmp(player2Name, black) == 0) {
4421             if (appData.debugMode)
4422               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4423                       player1Rating, player2Rating);
4424             gameInfo.whiteRating = player1Rating;
4425             gameInfo.blackRating = player2Rating;
4426         } else if (strcmp(player2Name, white) == 0 &&
4427                    strcmp(player1Name, black) == 0) {
4428             if (appData.debugMode)
4429               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4430                       player2Rating, player1Rating);
4431             gameInfo.whiteRating = player2Rating;
4432             gameInfo.blackRating = player1Rating;
4433         }
4434         player1Name[0] = player2Name[0] = NULLCHAR;
4435
4436         /* Silence shouts if requested */
4437         if (appData.quietPlay &&
4438             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4439             SendToICS(ics_prefix);
4440             SendToICS("set shout 0\n");
4441         }
4442     }
4443
4444     /* Deal with midgame name changes */
4445     if (!newGame) {
4446         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4447             if (gameInfo.white) free(gameInfo.white);
4448             gameInfo.white = StrSave(white);
4449         }
4450         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4451             if (gameInfo.black) free(gameInfo.black);
4452             gameInfo.black = StrSave(black);
4453         }
4454     }
4455
4456     /* Throw away game result if anything actually changes in examine mode */
4457     if (gameMode == IcsExamining && !newGame) {
4458         gameInfo.result = GameUnfinished;
4459         if (gameInfo.resultDetails != NULL) {
4460             free(gameInfo.resultDetails);
4461             gameInfo.resultDetails = NULL;
4462         }
4463     }
4464
4465     /* In pausing && IcsExamining mode, we ignore boards coming
4466        in if they are in a different variation than we are. */
4467     if (pauseExamInvalid) return;
4468     if (pausing && gameMode == IcsExamining) {
4469         if (moveNum <= pauseExamForwardMostMove) {
4470             pauseExamInvalid = TRUE;
4471             forwardMostMove = pauseExamForwardMostMove;
4472             return;
4473         }
4474     }
4475
4476   if (appData.debugMode) {
4477     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4478   }
4479     /* Parse the board */
4480     for (k = 0; k < ranks; k++) {
4481       for (j = 0; j < files; j++)
4482         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4483       if(gameInfo.holdingsWidth > 1) {
4484            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4485            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4486       }
4487     }
4488     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4489       board[5][BOARD_RGHT+1] = WhiteAngel;
4490       board[6][BOARD_RGHT+1] = WhiteMarshall;
4491       board[1][0] = BlackMarshall;
4492       board[2][0] = BlackAngel;
4493       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4494     }
4495     CopyBoard(boards[moveNum], board);
4496     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4497     if (moveNum == 0) {
4498         startedFromSetupPosition =
4499           !CompareBoards(board, initialPosition);
4500         if(startedFromSetupPosition)
4501             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4502     }
4503
4504     /* [HGM] Set castling rights. Take the outermost Rooks,
4505        to make it also work for FRC opening positions. Note that board12
4506        is really defective for later FRC positions, as it has no way to
4507        indicate which Rook can castle if they are on the same side of King.
4508        For the initial position we grant rights to the outermost Rooks,
4509        and remember thos rights, and we then copy them on positions
4510        later in an FRC game. This means WB might not recognize castlings with
4511        Rooks that have moved back to their original position as illegal,
4512        but in ICS mode that is not its job anyway.
4513     */
4514     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4515     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4516
4517         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4518             if(board[0][i] == WhiteRook) j = i;
4519         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4520         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4521             if(board[0][i] == WhiteRook) j = i;
4522         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4523         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4524             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4525         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4526         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4527             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4528         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4529
4530         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4531         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4532         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4533             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4534         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4535             if(board[BOARD_HEIGHT-1][k] == bKing)
4536                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4537         if(gameInfo.variant == VariantTwoKings) {
4538             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4539             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4540             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4541         }
4542     } else { int r;
4543         r = boards[moveNum][CASTLING][0] = initialRights[0];
4544         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4545         r = boards[moveNum][CASTLING][1] = initialRights[1];
4546         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4547         r = boards[moveNum][CASTLING][3] = initialRights[3];
4548         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4549         r = boards[moveNum][CASTLING][4] = initialRights[4];
4550         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4551         /* wildcastle kludge: always assume King has rights */
4552         r = boards[moveNum][CASTLING][2] = initialRights[2];
4553         r = boards[moveNum][CASTLING][5] = initialRights[5];
4554     }
4555     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4556     boards[moveNum][EP_STATUS] = EP_NONE;
4557     if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4558     if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4559     if(double_push !=  -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4560
4561
4562     if (ics_getting_history == H_GOT_REQ_HEADER ||
4563         ics_getting_history == H_GOT_UNREQ_HEADER) {
4564         /* This was an initial position from a move list, not
4565            the current position */
4566         return;
4567     }
4568
4569     /* Update currentMove and known move number limits */
4570     newMove = newGame || moveNum > forwardMostMove;
4571
4572     if (newGame) {
4573         forwardMostMove = backwardMostMove = currentMove = moveNum;
4574         if (gameMode == IcsExamining && moveNum == 0) {
4575           /* Workaround for ICS limitation: we are not told the wild
4576              type when starting to examine a game.  But if we ask for
4577              the move list, the move list header will tell us */
4578             ics_getting_history = H_REQUESTED;
4579             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4580             SendToICS(str);
4581         }
4582     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4583                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4584 #if ZIPPY
4585         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4586         /* [HGM] applied this also to an engine that is silently watching        */
4587         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4588             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4589             gameInfo.variant == currentlyInitializedVariant) {
4590           takeback = forwardMostMove - moveNum;
4591           for (i = 0; i < takeback; i++) {
4592             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4593             SendToProgram("undo\n", &first);
4594           }
4595         }
4596 #endif
4597
4598         forwardMostMove = moveNum;
4599         if (!pausing || currentMove > forwardMostMove)
4600           currentMove = forwardMostMove;
4601     } else {
4602         /* New part of history that is not contiguous with old part */
4603         if (pausing && gameMode == IcsExamining) {
4604             pauseExamInvalid = TRUE;
4605             forwardMostMove = pauseExamForwardMostMove;
4606             return;
4607         }
4608         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4609 #if ZIPPY
4610             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4611                 // [HGM] when we will receive the move list we now request, it will be
4612                 // fed to the engine from the first move on. So if the engine is not
4613                 // in the initial position now, bring it there.
4614                 InitChessProgram(&first, 0);
4615             }
4616 #endif
4617             ics_getting_history = H_REQUESTED;
4618             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4619             SendToICS(str);
4620         }
4621         forwardMostMove = backwardMostMove = currentMove = moveNum;
4622     }
4623
4624     /* Update the clocks */
4625     if (strchr(elapsed_time, '.')) {
4626       /* Time is in ms */
4627       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4628       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4629     } else {
4630       /* Time is in seconds */
4631       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4632       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4633     }
4634
4635
4636 #if ZIPPY
4637     if (appData.zippyPlay && newGame &&
4638         gameMode != IcsObserving && gameMode != IcsIdle &&
4639         gameMode != IcsExamining)
4640       ZippyFirstBoard(moveNum, basetime, increment);
4641 #endif
4642
4643     /* Put the move on the move list, first converting
4644        to canonical algebraic form. */
4645     if (moveNum > 0) {
4646   if (appData.debugMode) {
4647     if (appData.debugMode) { int f = forwardMostMove;
4648         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4649                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4650                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4651     }
4652     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4653     fprintf(debugFP, "moveNum = %d\n", moveNum);
4654     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4655     setbuf(debugFP, NULL);
4656   }
4657         if (moveNum <= backwardMostMove) {
4658             /* We don't know what the board looked like before
4659                this move.  Punt. */
4660           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4661             strcat(parseList[moveNum - 1], " ");
4662             strcat(parseList[moveNum - 1], elapsed_time);
4663             moveList[moveNum - 1][0] = NULLCHAR;
4664         } else if (strcmp(move_str, "none") == 0) {
4665             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4666             /* Again, we don't know what the board looked like;
4667                this is really the start of the game. */
4668             parseList[moveNum - 1][0] = NULLCHAR;
4669             moveList[moveNum - 1][0] = NULLCHAR;
4670             backwardMostMove = moveNum;
4671             startedFromSetupPosition = TRUE;
4672             fromX = fromY = toX = toY = -1;
4673         } else {
4674           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4675           //                 So we parse the long-algebraic move string in stead of the SAN move
4676           int valid; char buf[MSG_SIZ], *prom;
4677
4678           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4679                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4680           // str looks something like "Q/a1-a2"; kill the slash
4681           if(str[1] == '/')
4682             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4683           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4684           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4685                 strcat(buf, prom); // long move lacks promo specification!
4686           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4687                 if(appData.debugMode)
4688                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4689                 safeStrCpy(move_str, buf, MSG_SIZ);
4690           }
4691           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4692                                 &fromX, &fromY, &toX, &toY, &promoChar)
4693                || ParseOneMove(buf, moveNum - 1, &moveType,
4694                                 &fromX, &fromY, &toX, &toY, &promoChar);
4695           // end of long SAN patch
4696           if (valid) {
4697             (void) CoordsToAlgebraic(boards[moveNum - 1],
4698                                      PosFlags(moveNum - 1),
4699                                      fromY, fromX, toY, toX, promoChar,
4700                                      parseList[moveNum-1]);
4701             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4702               case MT_NONE:
4703               case MT_STALEMATE:
4704               default:
4705                 break;
4706               case MT_CHECK:
4707                 if(gameInfo.variant != VariantShogi)
4708                     strcat(parseList[moveNum - 1], "+");
4709                 break;
4710               case MT_CHECKMATE:
4711               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4712                 strcat(parseList[moveNum - 1], "#");
4713                 break;
4714             }
4715             strcat(parseList[moveNum - 1], " ");
4716             strcat(parseList[moveNum - 1], elapsed_time);
4717             /* currentMoveString is set as a side-effect of ParseOneMove */
4718             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4719             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4720             strcat(moveList[moveNum - 1], "\n");
4721
4722             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4723                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4724               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4725                 ChessSquare old, new = boards[moveNum][k][j];
4726                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4727                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4728                   if(old == new) continue;
4729                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4730                   else if(new == WhiteWazir || new == BlackWazir) {
4731                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4732                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4733                       else boards[moveNum][k][j] = old; // preserve type of Gold
4734                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4735                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4736               }
4737           } else {
4738             /* Move from ICS was illegal!?  Punt. */
4739             if (appData.debugMode) {
4740               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4741               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4742             }
4743             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4744             strcat(parseList[moveNum - 1], " ");
4745             strcat(parseList[moveNum - 1], elapsed_time);
4746             moveList[moveNum - 1][0] = NULLCHAR;
4747             fromX = fromY = toX = toY = -1;
4748           }
4749         }
4750   if (appData.debugMode) {
4751     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4752     setbuf(debugFP, NULL);
4753   }
4754
4755 #if ZIPPY
4756         /* Send move to chess program (BEFORE animating it). */
4757         if (appData.zippyPlay && !newGame && newMove &&
4758            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4759
4760             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4761                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4762                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4763                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4764                             move_str);
4765                     DisplayError(str, 0);
4766                 } else {
4767                     if (first.sendTime) {
4768                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4769                     }
4770                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4771                     if (firstMove && !bookHit) {
4772                         firstMove = FALSE;
4773                         if (first.useColors) {
4774                           SendToProgram(gameMode == IcsPlayingWhite ?
4775                                         "white\ngo\n" :
4776                                         "black\ngo\n", &first);
4777                         } else {
4778                           SendToProgram("go\n", &first);
4779                         }
4780                         first.maybeThinking = TRUE;
4781                     }
4782                 }
4783             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4784               if (moveList[moveNum - 1][0] == NULLCHAR) {
4785                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4786                 DisplayError(str, 0);
4787               } else {
4788                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4789                 SendMoveToProgram(moveNum - 1, &first);
4790               }
4791             }
4792         }
4793 #endif
4794     }
4795
4796     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4797         /* If move comes from a remote source, animate it.  If it
4798            isn't remote, it will have already been animated. */
4799         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4800             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4801         }
4802         if (!pausing && appData.highlightLastMove) {
4803             SetHighlights(fromX, fromY, toX, toY);
4804         }
4805     }
4806
4807     /* Start the clocks */
4808     whiteFlag = blackFlag = FALSE;
4809     appData.clockMode = !(basetime == 0 && increment == 0);
4810     if (ticking == 0) {
4811       ics_clock_paused = TRUE;
4812       StopClocks();
4813     } else if (ticking == 1) {
4814       ics_clock_paused = FALSE;
4815     }
4816     if (gameMode == IcsIdle ||
4817         relation == RELATION_OBSERVING_STATIC ||
4818         relation == RELATION_EXAMINING ||
4819         ics_clock_paused)
4820       DisplayBothClocks();
4821     else
4822       StartClocks();
4823
4824     /* Display opponents and material strengths */
4825     if (gameInfo.variant != VariantBughouse &&
4826         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4827         if (tinyLayout || smallLayout) {
4828             if(gameInfo.variant == VariantNormal)
4829               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4830                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4831                     basetime, increment);
4832             else
4833               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4834                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4835                     basetime, increment, (int) gameInfo.variant);
4836         } else {
4837             if(gameInfo.variant == VariantNormal)
4838               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4839                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4840                     basetime, increment);
4841             else
4842               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
4843                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4844                     basetime, increment, VariantName(gameInfo.variant));
4845         }
4846         DisplayTitle(str);
4847   if (appData.debugMode) {
4848     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4849   }
4850     }
4851
4852
4853     /* Display the board */
4854     if (!pausing && !appData.noGUI) {
4855
4856       if (appData.premove)
4857           if (!gotPremove ||
4858              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4859              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4860               ClearPremoveHighlights();
4861
4862       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4863         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4864       DrawPosition(j, boards[currentMove]);
4865
4866       DisplayMove(moveNum - 1);
4867       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4868             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4869               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4870         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4871       }
4872     }
4873
4874     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4875 #if ZIPPY
4876     if(bookHit) { // [HGM] book: simulate book reply
4877         static char bookMove[MSG_SIZ]; // a bit generous?
4878
4879         programStats.nodes = programStats.depth = programStats.time =
4880         programStats.score = programStats.got_only_move = 0;
4881         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4882
4883         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4884         strcat(bookMove, bookHit);
4885         HandleMachineMove(bookMove, &first);
4886     }
4887 #endif
4888 }
4889
4890 void
4891 GetMoveListEvent ()
4892 {
4893     char buf[MSG_SIZ];
4894     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4895         ics_getting_history = H_REQUESTED;
4896         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4897         SendToICS(buf);
4898     }
4899 }
4900
4901 void
4902 SendToBoth (char *msg)
4903 {   // to make it easy to keep two engines in step in dual analysis
4904     SendToProgram(msg, &first);
4905     if(second.analyzing) SendToProgram(msg, &second);
4906 }
4907
4908 void
4909 AnalysisPeriodicEvent (int force)
4910 {
4911     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4912          && !force) || !appData.periodicUpdates)
4913       return;
4914
4915     /* Send . command to Crafty to collect stats */
4916     SendToBoth(".\n");
4917
4918     /* Don't send another until we get a response (this makes
4919        us stop sending to old Crafty's which don't understand
4920        the "." command (sending illegal cmds resets node count & time,
4921        which looks bad)) */
4922     programStats.ok_to_send = 0;
4923 }
4924
4925 void
4926 ics_update_width (int new_width)
4927 {
4928         ics_printf("set width %d\n", new_width);
4929 }
4930
4931 void
4932 SendMoveToProgram (int moveNum, ChessProgramState *cps)
4933 {
4934     char buf[MSG_SIZ];
4935
4936     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
4937         // null move in variant where engine does not understand it (for analysis purposes)
4938         SendBoard(cps, moveNum + 1); // send position after move in stead.
4939         return;
4940     }
4941     if (cps->useUsermove) {
4942       SendToProgram("usermove ", cps);
4943     }
4944     if (cps->useSAN) {
4945       char *space;
4946       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4947         int len = space - parseList[moveNum];
4948         memcpy(buf, parseList[moveNum], len);
4949         buf[len++] = '\n';
4950         buf[len] = NULLCHAR;
4951       } else {
4952         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4953       }
4954       SendToProgram(buf, cps);
4955     } else {
4956       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4957         AlphaRank(moveList[moveNum], 4);
4958         SendToProgram(moveList[moveNum], cps);
4959         AlphaRank(moveList[moveNum], 4); // and back
4960       } else
4961       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4962        * the engine. It would be nice to have a better way to identify castle
4963        * moves here. */
4964       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4965                                                                          && cps->useOOCastle) {
4966         int fromX = moveList[moveNum][0] - AAA;
4967         int fromY = moveList[moveNum][1] - ONE;
4968         int toX = moveList[moveNum][2] - AAA;
4969         int toY = moveList[moveNum][3] - ONE;
4970         if((boards[moveNum][fromY][fromX] == WhiteKing
4971             && boards[moveNum][toY][toX] == WhiteRook)
4972            || (boards[moveNum][fromY][fromX] == BlackKing
4973                && boards[moveNum][toY][toX] == BlackRook)) {
4974           if(toX > fromX) SendToProgram("O-O\n", cps);
4975           else SendToProgram("O-O-O\n", cps);
4976         }
4977         else SendToProgram(moveList[moveNum], cps);
4978       } else
4979       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
4980         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
4981           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
4982           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
4983                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4984         } else
4985           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
4986                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4987         SendToProgram(buf, cps);
4988       }
4989       else SendToProgram(moveList[moveNum], cps);
4990       /* End of additions by Tord */
4991     }
4992
4993     /* [HGM] setting up the opening has brought engine in force mode! */
4994     /*       Send 'go' if we are in a mode where machine should play. */
4995     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4996         (gameMode == TwoMachinesPlay   ||
4997 #if ZIPPY
4998          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4999 #endif
5000          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5001         SendToProgram("go\n", cps);
5002   if (appData.debugMode) {
5003     fprintf(debugFP, "(extra)\n");
5004   }
5005     }
5006     setboardSpoiledMachineBlack = 0;
5007 }
5008
5009 void
5010 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5011 {
5012     char user_move[MSG_SIZ];
5013     char suffix[4];
5014
5015     if(gameInfo.variant == VariantSChess && promoChar) {
5016         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5017         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5018     } else suffix[0] = NULLCHAR;
5019
5020     switch (moveType) {
5021       default:
5022         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5023                 (int)moveType, fromX, fromY, toX, toY);
5024         DisplayError(user_move + strlen("say "), 0);
5025         break;
5026       case WhiteKingSideCastle:
5027       case BlackKingSideCastle:
5028       case WhiteQueenSideCastleWild:
5029       case BlackQueenSideCastleWild:
5030       /* PUSH Fabien */
5031       case WhiteHSideCastleFR:
5032       case BlackHSideCastleFR:
5033       /* POP Fabien */
5034         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5035         break;
5036       case WhiteQueenSideCastle:
5037       case BlackQueenSideCastle:
5038       case WhiteKingSideCastleWild:
5039       case BlackKingSideCastleWild:
5040       /* PUSH Fabien */
5041       case WhiteASideCastleFR:
5042       case BlackASideCastleFR:
5043       /* POP Fabien */
5044         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5045         break;
5046       case WhiteNonPromotion:
5047       case BlackNonPromotion:
5048         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5049         break;
5050       case WhitePromotion:
5051       case BlackPromotion:
5052         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
5053           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5054                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5055                 PieceToChar(WhiteFerz));
5056         else if(gameInfo.variant == VariantGreat)
5057           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5058                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5059                 PieceToChar(WhiteMan));
5060         else
5061           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5062                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5063                 promoChar);
5064         break;
5065       case WhiteDrop:
5066       case BlackDrop:
5067       drop:
5068         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5069                  ToUpper(PieceToChar((ChessSquare) fromX)),
5070                  AAA + toX, ONE + toY);
5071         break;
5072       case IllegalMove:  /* could be a variant we don't quite understand */
5073         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5074       case NormalMove:
5075       case WhiteCapturesEnPassant:
5076       case BlackCapturesEnPassant:
5077         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5078                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5079         break;
5080     }
5081     SendToICS(user_move);
5082     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5083         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5084 }
5085
5086 void
5087 UploadGameEvent ()
5088 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5089     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5090     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5091     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5092       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5093       return;
5094     }
5095     if(gameMode != IcsExamining) { // is this ever not the case?
5096         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5097
5098         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5099           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5100         } else { // on FICS we must first go to general examine mode
5101           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5102         }
5103         if(gameInfo.variant != VariantNormal) {
5104             // try figure out wild number, as xboard names are not always valid on ICS
5105             for(i=1; i<=36; i++) {
5106               snprintf(buf, MSG_SIZ, "wild/%d", i);
5107                 if(StringToVariant(buf) == gameInfo.variant) break;
5108             }
5109             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5110             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5111             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5112         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5113         SendToICS(ics_prefix);
5114         SendToICS(buf);
5115         if(startedFromSetupPosition || backwardMostMove != 0) {
5116           fen = PositionToFEN(backwardMostMove, NULL);
5117           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5118             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5119             SendToICS(buf);
5120           } else { // FICS: everything has to set by separate bsetup commands
5121             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5122             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5123             SendToICS(buf);
5124             if(!WhiteOnMove(backwardMostMove)) {
5125                 SendToICS("bsetup tomove black\n");
5126             }
5127             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5128             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5129             SendToICS(buf);
5130             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5131             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5132             SendToICS(buf);
5133             i = boards[backwardMostMove][EP_STATUS];
5134             if(i >= 0) { // set e.p.
5135               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5136                 SendToICS(buf);
5137             }
5138             bsetup++;
5139           }
5140         }
5141       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5142             SendToICS("bsetup done\n"); // switch to normal examining.
5143     }
5144     for(i = backwardMostMove; i<last; i++) {
5145         char buf[20];
5146         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5147         if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5148             int len = strlen(moveList[i]);
5149             snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5150             if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5151         }
5152         SendToICS(buf);
5153     }
5154     SendToICS(ics_prefix);
5155     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5156 }
5157
5158 void
5159 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5160 {
5161     if (rf == DROP_RANK) {
5162       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5163       sprintf(move, "%c@%c%c\n",
5164                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5165     } else {
5166         if (promoChar == 'x' || promoChar == NULLCHAR) {
5167           sprintf(move, "%c%c%c%c\n",
5168                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5169         } else {
5170             sprintf(move, "%c%c%c%c%c\n",
5171                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5172         }
5173     }
5174 }
5175
5176 void
5177 ProcessICSInitScript (FILE *f)
5178 {
5179     char buf[MSG_SIZ];
5180
5181     while (fgets(buf, MSG_SIZ, f)) {
5182         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5183     }
5184
5185     fclose(f);
5186 }
5187
5188
5189 static int lastX, lastY, selectFlag, dragging;
5190
5191 void
5192 Sweep (int step)
5193 {
5194     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5195     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5196     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5197     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5198     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5199     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5200     do {
5201         promoSweep -= step;
5202         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5203         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5204         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5205         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5206         if(!step) step = -1;
5207     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5208             appData.testLegality && (promoSweep == king ||
5209             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5210     if(toX >= 0) {
5211         int victim = boards[currentMove][toY][toX];
5212         boards[currentMove][toY][toX] = promoSweep;
5213         DrawPosition(FALSE, boards[currentMove]);
5214         boards[currentMove][toY][toX] = victim;
5215     } else
5216     ChangeDragPiece(promoSweep);
5217 }
5218
5219 int
5220 PromoScroll (int x, int y)
5221 {
5222   int step = 0;
5223
5224   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5225   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5226   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5227   if(!step) return FALSE;
5228   lastX = x; lastY = y;
5229   if((promoSweep < BlackPawn) == flipView) step = -step;
5230   if(step > 0) selectFlag = 1;
5231   if(!selectFlag) Sweep(step);
5232   return FALSE;
5233 }
5234
5235 void
5236 NextPiece (int step)
5237 {
5238     ChessSquare piece = boards[currentMove][toY][toX];
5239     do {
5240         pieceSweep -= step;
5241         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5242         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5243         if(!step) step = -1;
5244     } while(PieceToChar(pieceSweep) == '.');
5245     boards[currentMove][toY][toX] = pieceSweep;
5246     DrawPosition(FALSE, boards[currentMove]);
5247     boards[currentMove][toY][toX] = piece;
5248 }
5249 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5250 void
5251 AlphaRank (char *move, int n)
5252 {
5253 //    char *p = move, c; int x, y;
5254
5255     if (appData.debugMode) {
5256         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5257     }
5258
5259     if(move[1]=='*' &&
5260        move[2]>='0' && move[2]<='9' &&
5261        move[3]>='a' && move[3]<='x'    ) {
5262         move[1] = '@';
5263         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5264         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5265     } else
5266     if(move[0]>='0' && move[0]<='9' &&
5267        move[1]>='a' && move[1]<='x' &&
5268        move[2]>='0' && move[2]<='9' &&
5269        move[3]>='a' && move[3]<='x'    ) {
5270         /* input move, Shogi -> normal */
5271         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5272         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5273         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5274         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5275     } else
5276     if(move[1]=='@' &&
5277        move[3]>='0' && move[3]<='9' &&
5278        move[2]>='a' && move[2]<='x'    ) {
5279         move[1] = '*';
5280         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5281         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5282     } else
5283     if(
5284        move[0]>='a' && move[0]<='x' &&
5285        move[3]>='0' && move[3]<='9' &&
5286        move[2]>='a' && move[2]<='x'    ) {
5287          /* output move, normal -> Shogi */
5288         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5289         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5290         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5291         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5292         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5293     }
5294     if (appData.debugMode) {
5295         fprintf(debugFP, "   out = '%s'\n", move);
5296     }
5297 }
5298
5299 char yy_textstr[8000];
5300
5301 /* Parser for moves from gnuchess, ICS, or user typein box */
5302 Boolean
5303 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5304 {
5305     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5306
5307     switch (*moveType) {
5308       case WhitePromotion:
5309       case BlackPromotion:
5310       case WhiteNonPromotion:
5311       case BlackNonPromotion:
5312       case NormalMove:
5313       case WhiteCapturesEnPassant:
5314       case BlackCapturesEnPassant:
5315       case WhiteKingSideCastle:
5316       case WhiteQueenSideCastle:
5317       case BlackKingSideCastle:
5318       case BlackQueenSideCastle:
5319       case WhiteKingSideCastleWild:
5320       case WhiteQueenSideCastleWild:
5321       case BlackKingSideCastleWild:
5322       case BlackQueenSideCastleWild:
5323       /* Code added by Tord: */
5324       case WhiteHSideCastleFR:
5325       case WhiteASideCastleFR:
5326       case BlackHSideCastleFR:
5327       case BlackASideCastleFR:
5328       /* End of code added by Tord */
5329       case IllegalMove:         /* bug or odd chess variant */
5330         *fromX = currentMoveString[0] - AAA;
5331         *fromY = currentMoveString[1] - ONE;
5332         *toX = currentMoveString[2] - AAA;
5333         *toY = currentMoveString[3] - ONE;
5334         *promoChar = currentMoveString[4];
5335         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5336             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5337     if (appData.debugMode) {
5338         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5339     }
5340             *fromX = *fromY = *toX = *toY = 0;
5341             return FALSE;
5342         }
5343         if (appData.testLegality) {
5344           return (*moveType != IllegalMove);
5345         } else {
5346           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5347                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5348         }
5349
5350       case WhiteDrop:
5351       case BlackDrop:
5352         *fromX = *moveType == WhiteDrop ?
5353           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5354           (int) CharToPiece(ToLower(currentMoveString[0]));
5355         *fromY = DROP_RANK;
5356         *toX = currentMoveString[2] - AAA;
5357         *toY = currentMoveString[3] - ONE;
5358         *promoChar = NULLCHAR;
5359         return TRUE;
5360
5361       case AmbiguousMove:
5362       case ImpossibleMove:
5363       case EndOfFile:
5364       case ElapsedTime:
5365       case Comment:
5366       case PGNTag:
5367       case NAG:
5368       case WhiteWins:
5369       case BlackWins:
5370       case GameIsDrawn:
5371       default:
5372     if (appData.debugMode) {
5373         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5374     }
5375         /* bug? */
5376         *fromX = *fromY = *toX = *toY = 0;
5377         *promoChar = NULLCHAR;
5378         return FALSE;
5379     }
5380 }
5381
5382 Boolean pushed = FALSE;
5383 char *lastParseAttempt;
5384
5385 void
5386 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5387 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5388   int fromX, fromY, toX, toY; char promoChar;
5389   ChessMove moveType;
5390   Boolean valid;
5391   int nr = 0;
5392
5393   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5394     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5395     pushed = TRUE;
5396   }
5397   endPV = forwardMostMove;
5398   do {
5399     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5400     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5401     lastParseAttempt = pv;
5402     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5403     if(!valid && nr == 0 &&
5404        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5405         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5406         // Hande case where played move is different from leading PV move
5407         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5408         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5409         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5410         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5411           endPV += 2; // if position different, keep this
5412           moveList[endPV-1][0] = fromX + AAA;
5413           moveList[endPV-1][1] = fromY + ONE;
5414           moveList[endPV-1][2] = toX + AAA;
5415           moveList[endPV-1][3] = toY + ONE;
5416           parseList[endPV-1][0] = NULLCHAR;
5417           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5418         }
5419       }
5420     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5421     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5422     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5423     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5424         valid++; // allow comments in PV
5425         continue;
5426     }
5427     nr++;
5428     if(endPV+1 > framePtr) break; // no space, truncate
5429     if(!valid) break;
5430     endPV++;
5431     CopyBoard(boards[endPV], boards[endPV-1]);
5432     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5433     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5434     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5435     CoordsToAlgebraic(boards[endPV - 1],
5436                              PosFlags(endPV - 1),
5437                              fromY, fromX, toY, toX, promoChar,
5438                              parseList[endPV - 1]);
5439   } while(valid);
5440   if(atEnd == 2) return; // used hidden, for PV conversion
5441   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5442   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5443   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5444                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5445   DrawPosition(TRUE, boards[currentMove]);
5446 }
5447
5448 int
5449 MultiPV (ChessProgramState *cps)
5450 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5451         int i;
5452         for(i=0; i<cps->nrOptions; i++)
5453             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5454                 return i;
5455         return -1;
5456 }
5457
5458 Boolean
5459 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5460 {
5461         int startPV, multi, lineStart, origIndex = index;
5462         char *p, buf2[MSG_SIZ];
5463         ChessProgramState *cps = (pane ? &second : &first);
5464
5465         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5466         lastX = x; lastY = y;
5467         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5468         lineStart = startPV = index;
5469         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5470         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5471         index = startPV;
5472         do{ while(buf[index] && buf[index] != '\n') index++;
5473         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5474         buf[index] = 0;
5475         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(cps)) >= 0) {
5476                 int n = cps->option[multi].value;
5477                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5478                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5479                 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5480                 cps->option[multi].value = n;
5481                 *start = *end = 0;
5482                 return FALSE;
5483         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5484                 ExcludeClick(origIndex - lineStart);
5485                 return FALSE;
5486         }
5487         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5488         *start = startPV; *end = index-1;
5489         return TRUE;
5490 }
5491
5492 char *
5493 PvToSAN (char *pv)
5494 {
5495         static char buf[10*MSG_SIZ];
5496         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5497         *buf = NULLCHAR;
5498         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV);
5499         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5500         for(i = forwardMostMove; i<endPV; i++){
5501             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5502             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5503             k += strlen(buf+k);
5504         }
5505         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5506         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5507         endPV = savedEnd;
5508         return buf;
5509 }
5510
5511 Boolean
5512 LoadPV (int x, int y)
5513 { // called on right mouse click to load PV
5514   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5515   lastX = x; lastY = y;
5516   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5517   return TRUE;
5518 }
5519
5520 void
5521 UnLoadPV ()
5522 {
5523   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5524   if(endPV < 0) return;
5525   if(appData.autoCopyPV) CopyFENToClipboard();
5526   endPV = -1;
5527   if(gameMode == AnalyzeMode && currentMove > forwardMostMove) {
5528         Boolean saveAnimate = appData.animate;
5529         if(pushed) {
5530             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5531                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5532             } else storedGames--; // abandon shelved tail of original game
5533         }
5534         pushed = FALSE;
5535         forwardMostMove = currentMove;
5536         currentMove = oldFMM;
5537         appData.animate = FALSE;
5538         ToNrEvent(forwardMostMove);
5539         appData.animate = saveAnimate;
5540   }
5541   currentMove = forwardMostMove;
5542   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5543   ClearPremoveHighlights();
5544   DrawPosition(TRUE, boards[currentMove]);
5545 }
5546
5547 void
5548 MovePV (int x, int y, int h)
5549 { // step through PV based on mouse coordinates (called on mouse move)
5550   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5551
5552   // we must somehow check if right button is still down (might be released off board!)
5553   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5554   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5555   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5556   if(!step) return;
5557   lastX = x; lastY = y;
5558
5559   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5560   if(endPV < 0) return;
5561   if(y < margin) step = 1; else
5562   if(y > h - margin) step = -1;
5563   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5564   currentMove += step;
5565   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5566   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5567                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5568   DrawPosition(FALSE, boards[currentMove]);
5569 }
5570
5571
5572 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5573 // All positions will have equal probability, but the current method will not provide a unique
5574 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5575 #define DARK 1
5576 #define LITE 2
5577 #define ANY 3
5578
5579 int squaresLeft[4];
5580 int piecesLeft[(int)BlackPawn];
5581 int seed, nrOfShuffles;
5582
5583 void
5584 GetPositionNumber ()
5585 {       // sets global variable seed
5586         int i;
5587
5588         seed = appData.defaultFrcPosition;
5589         if(seed < 0) { // randomize based on time for negative FRC position numbers
5590                 for(i=0; i<50; i++) seed += random();
5591                 seed = random() ^ random() >> 8 ^ random() << 8;
5592                 if(seed<0) seed = -seed;
5593         }
5594 }
5595
5596 int
5597 put (Board board, int pieceType, int rank, int n, int shade)
5598 // put the piece on the (n-1)-th empty squares of the given shade
5599 {
5600         int i;
5601
5602         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5603                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5604                         board[rank][i] = (ChessSquare) pieceType;
5605                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5606                         squaresLeft[ANY]--;
5607                         piecesLeft[pieceType]--;
5608                         return i;
5609                 }
5610         }
5611         return -1;
5612 }
5613
5614
5615 void
5616 AddOnePiece (Board board, int pieceType, int rank, int shade)
5617 // calculate where the next piece goes, (any empty square), and put it there
5618 {
5619         int i;
5620
5621         i = seed % squaresLeft[shade];
5622         nrOfShuffles *= squaresLeft[shade];
5623         seed /= squaresLeft[shade];
5624         put(board, pieceType, rank, i, shade);
5625 }
5626
5627 void
5628 AddTwoPieces (Board board, int pieceType, int rank)
5629 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5630 {
5631         int i, n=squaresLeft[ANY], j=n-1, k;
5632
5633         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5634         i = seed % k;  // pick one
5635         nrOfShuffles *= k;
5636         seed /= k;
5637         while(i >= j) i -= j--;
5638         j = n - 1 - j; i += j;
5639         put(board, pieceType, rank, j, ANY);
5640         put(board, pieceType, rank, i, ANY);
5641 }
5642
5643 void
5644 SetUpShuffle (Board board, int number)
5645 {
5646         int i, p, first=1;
5647
5648         GetPositionNumber(); nrOfShuffles = 1;
5649
5650         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5651         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5652         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5653
5654         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5655
5656         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5657             p = (int) board[0][i];
5658             if(p < (int) BlackPawn) piecesLeft[p] ++;
5659             board[0][i] = EmptySquare;
5660         }
5661
5662         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5663             // shuffles restricted to allow normal castling put KRR first
5664             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5665                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5666             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5667                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5668             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5669                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5670             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5671                 put(board, WhiteRook, 0, 0, ANY);
5672             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5673         }
5674
5675         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5676             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5677             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5678                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5679                 while(piecesLeft[p] >= 2) {
5680                     AddOnePiece(board, p, 0, LITE);
5681                     AddOnePiece(board, p, 0, DARK);
5682                 }
5683                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5684             }
5685
5686         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5687             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5688             // but we leave King and Rooks for last, to possibly obey FRC restriction
5689             if(p == (int)WhiteRook) continue;
5690             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5691             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5692         }
5693
5694         // now everything is placed, except perhaps King (Unicorn) and Rooks
5695
5696         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5697             // Last King gets castling rights
5698             while(piecesLeft[(int)WhiteUnicorn]) {
5699                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5700                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5701             }
5702
5703             while(piecesLeft[(int)WhiteKing]) {
5704                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5705                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5706             }
5707
5708
5709         } else {
5710             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5711             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5712         }
5713
5714         // Only Rooks can be left; simply place them all
5715         while(piecesLeft[(int)WhiteRook]) {
5716                 i = put(board, WhiteRook, 0, 0, ANY);
5717                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5718                         if(first) {
5719                                 first=0;
5720                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5721                         }
5722                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5723                 }
5724         }
5725         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5726             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5727         }
5728
5729         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5730 }
5731
5732 int
5733 SetCharTable (char *table, const char * map)
5734 /* [HGM] moved here from winboard.c because of its general usefulness */
5735 /*       Basically a safe strcpy that uses the last character as King */
5736 {
5737     int result = FALSE; int NrPieces;
5738
5739     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5740                     && NrPieces >= 12 && !(NrPieces&1)) {
5741         int i; /* [HGM] Accept even length from 12 to 34 */
5742
5743         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5744         for( i=0; i<NrPieces/2-1; i++ ) {
5745             table[i] = map[i];
5746             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5747         }
5748         table[(int) WhiteKing]  = map[NrPieces/2-1];
5749         table[(int) BlackKing]  = map[NrPieces-1];
5750
5751         result = TRUE;
5752     }
5753
5754     return result;
5755 }
5756
5757 void
5758 Prelude (Board board)
5759 {       // [HGM] superchess: random selection of exo-pieces
5760         int i, j, k; ChessSquare p;
5761         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5762
5763         GetPositionNumber(); // use FRC position number
5764
5765         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5766             SetCharTable(pieceToChar, appData.pieceToCharTable);
5767             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5768                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5769         }
5770
5771         j = seed%4;                 seed /= 4;
5772         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5773         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5774         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5775         j = seed%3 + (seed%3 >= j); seed /= 3;
5776         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5777         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5778         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5779         j = seed%3;                 seed /= 3;
5780         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5781         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5782         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5783         j = seed%2 + (seed%2 >= j); seed /= 2;
5784         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5785         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5786         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5787         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5788         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5789         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5790         put(board, exoPieces[0],    0, 0, ANY);
5791         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5792 }
5793
5794 void
5795 InitPosition (int redraw)
5796 {
5797     ChessSquare (* pieces)[BOARD_FILES];
5798     int i, j, pawnRow, overrule,
5799     oldx = gameInfo.boardWidth,
5800     oldy = gameInfo.boardHeight,
5801     oldh = gameInfo.holdingsWidth;
5802     static int oldv;
5803
5804     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5805
5806     /* [AS] Initialize pv info list [HGM] and game status */
5807     {
5808         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5809             pvInfoList[i].depth = 0;
5810             boards[i][EP_STATUS] = EP_NONE;
5811             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5812         }
5813
5814         initialRulePlies = 0; /* 50-move counter start */
5815
5816         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5817         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5818     }
5819
5820
5821     /* [HGM] logic here is completely changed. In stead of full positions */
5822     /* the initialized data only consist of the two backranks. The switch */
5823     /* selects which one we will use, which is than copied to the Board   */
5824     /* initialPosition, which for the rest is initialized by Pawns and    */
5825     /* empty squares. This initial position is then copied to boards[0],  */
5826     /* possibly after shuffling, so that it remains available.            */
5827
5828     gameInfo.holdingsWidth = 0; /* default board sizes */
5829     gameInfo.boardWidth    = 8;
5830     gameInfo.boardHeight   = 8;
5831     gameInfo.holdingsSize  = 0;
5832     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5833     for(i=0; i<BOARD_FILES-2; i++)
5834       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5835     initialPosition[EP_STATUS] = EP_NONE;
5836     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5837     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5838          SetCharTable(pieceNickName, appData.pieceNickNames);
5839     else SetCharTable(pieceNickName, "............");
5840     pieces = FIDEArray;
5841
5842     switch (gameInfo.variant) {
5843     case VariantFischeRandom:
5844       shuffleOpenings = TRUE;
5845     default:
5846       break;
5847     case VariantShatranj:
5848       pieces = ShatranjArray;
5849       nrCastlingRights = 0;
5850       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5851       break;
5852     case VariantMakruk:
5853       pieces = makrukArray;
5854       nrCastlingRights = 0;
5855       startedFromSetupPosition = TRUE;
5856       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5857       break;
5858     case VariantTwoKings:
5859       pieces = twoKingsArray;
5860       break;
5861     case VariantGrand:
5862       pieces = GrandArray;
5863       nrCastlingRights = 0;
5864       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5865       gameInfo.boardWidth = 10;
5866       gameInfo.boardHeight = 10;
5867       gameInfo.holdingsSize = 7;
5868       break;
5869     case VariantCapaRandom:
5870       shuffleOpenings = TRUE;
5871     case VariantCapablanca:
5872       pieces = CapablancaArray;
5873       gameInfo.boardWidth = 10;
5874       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5875       break;
5876     case VariantGothic:
5877       pieces = GothicArray;
5878       gameInfo.boardWidth = 10;
5879       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5880       break;
5881     case VariantSChess:
5882       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5883       gameInfo.holdingsSize = 7;
5884       for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
5885       break;
5886     case VariantJanus:
5887       pieces = JanusArray;
5888       gameInfo.boardWidth = 10;
5889       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5890       nrCastlingRights = 6;
5891         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5892         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5893         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5894         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5895         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5896         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5897       break;
5898     case VariantFalcon:
5899       pieces = FalconArray;
5900       gameInfo.boardWidth = 10;
5901       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5902       break;
5903     case VariantXiangqi:
5904       pieces = XiangqiArray;
5905       gameInfo.boardWidth  = 9;
5906       gameInfo.boardHeight = 10;
5907       nrCastlingRights = 0;
5908       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5909       break;
5910     case VariantShogi:
5911       pieces = ShogiArray;
5912       gameInfo.boardWidth  = 9;
5913       gameInfo.boardHeight = 9;
5914       gameInfo.holdingsSize = 7;
5915       nrCastlingRights = 0;
5916       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5917       break;
5918     case VariantCourier:
5919       pieces = CourierArray;
5920       gameInfo.boardWidth  = 12;
5921       nrCastlingRights = 0;
5922       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5923       break;
5924     case VariantKnightmate:
5925       pieces = KnightmateArray;
5926       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5927       break;
5928     case VariantSpartan:
5929       pieces = SpartanArray;
5930       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5931       break;
5932     case VariantFairy:
5933       pieces = fairyArray;
5934       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5935       break;
5936     case VariantGreat:
5937       pieces = GreatArray;
5938       gameInfo.boardWidth = 10;
5939       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5940       gameInfo.holdingsSize = 8;
5941       break;
5942     case VariantSuper:
5943       pieces = FIDEArray;
5944       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5945       gameInfo.holdingsSize = 8;
5946       startedFromSetupPosition = TRUE;
5947       break;
5948     case VariantCrazyhouse:
5949     case VariantBughouse:
5950       pieces = FIDEArray;
5951       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5952       gameInfo.holdingsSize = 5;
5953       break;
5954     case VariantWildCastle:
5955       pieces = FIDEArray;
5956       /* !!?shuffle with kings guaranteed to be on d or e file */
5957       shuffleOpenings = 1;
5958       break;
5959     case VariantNoCastle:
5960       pieces = FIDEArray;
5961       nrCastlingRights = 0;
5962       /* !!?unconstrained back-rank shuffle */
5963       shuffleOpenings = 1;
5964       break;
5965     }
5966
5967     overrule = 0;
5968     if(appData.NrFiles >= 0) {
5969         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5970         gameInfo.boardWidth = appData.NrFiles;
5971     }
5972     if(appData.NrRanks >= 0) {
5973         gameInfo.boardHeight = appData.NrRanks;
5974     }
5975     if(appData.holdingsSize >= 0) {
5976         i = appData.holdingsSize;
5977         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5978         gameInfo.holdingsSize = i;
5979     }
5980     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5981     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5982         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5983
5984     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5985     if(pawnRow < 1) pawnRow = 1;
5986     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) pawnRow = 2;
5987
5988     /* User pieceToChar list overrules defaults */
5989     if(appData.pieceToCharTable != NULL)
5990         SetCharTable(pieceToChar, appData.pieceToCharTable);
5991
5992     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5993
5994         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5995             s = (ChessSquare) 0; /* account holding counts in guard band */
5996         for( i=0; i<BOARD_HEIGHT; i++ )
5997             initialPosition[i][j] = s;
5998
5999         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6000         initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
6001         initialPosition[pawnRow][j] = WhitePawn;
6002         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6003         if(gameInfo.variant == VariantXiangqi) {
6004             if(j&1) {
6005                 initialPosition[pawnRow][j] =
6006                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6007                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6008                    initialPosition[2][j] = WhiteCannon;
6009                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6010                 }
6011             }
6012         }
6013         if(gameInfo.variant == VariantGrand) {
6014             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6015                initialPosition[0][j] = WhiteRook;
6016                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6017             }
6018         }
6019         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] =  pieces[1][j-gameInfo.holdingsWidth];
6020     }
6021     if( (gameInfo.variant == VariantShogi) && !overrule ) {
6022
6023             j=BOARD_LEFT+1;
6024             initialPosition[1][j] = WhiteBishop;
6025             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6026             j=BOARD_RGHT-2;
6027             initialPosition[1][j] = WhiteRook;
6028             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6029     }
6030
6031     if( nrCastlingRights == -1) {
6032         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6033         /*       This sets default castling rights from none to normal corners   */
6034         /* Variants with other castling rights must set them themselves above    */
6035         nrCastlingRights = 6;
6036
6037         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6038         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6039         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6040         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6041         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6042         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6043      }
6044
6045      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6046      if(gameInfo.variant == VariantGreat) { // promotion commoners
6047         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6048         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6049         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6050         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6051      }
6052      if( gameInfo.variant == VariantSChess ) {
6053       initialPosition[1][0] = BlackMarshall;
6054       initialPosition[2][0] = BlackAngel;
6055       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6056       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6057       initialPosition[1][1] = initialPosition[2][1] = 
6058       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6059      }
6060   if (appData.debugMode) {
6061     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6062   }
6063     if(shuffleOpenings) {
6064         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6065         startedFromSetupPosition = TRUE;
6066     }
6067     if(startedFromPositionFile) {
6068       /* [HGM] loadPos: use PositionFile for every new game */
6069       CopyBoard(initialPosition, filePosition);
6070       for(i=0; i<nrCastlingRights; i++)
6071           initialRights[i] = filePosition[CASTLING][i];
6072       startedFromSetupPosition = TRUE;
6073     }
6074
6075     CopyBoard(boards[0], initialPosition);
6076
6077     if(oldx != gameInfo.boardWidth ||
6078        oldy != gameInfo.boardHeight ||
6079        oldv != gameInfo.variant ||
6080        oldh != gameInfo.holdingsWidth
6081                                          )
6082             InitDrawingSizes(-2 ,0);
6083
6084     oldv = gameInfo.variant;
6085     if (redraw)
6086       DrawPosition(TRUE, boards[currentMove]);
6087 }
6088
6089 void
6090 SendBoard (ChessProgramState *cps, int moveNum)
6091 {
6092     char message[MSG_SIZ];
6093
6094     if (cps->useSetboard) {
6095       char* fen = PositionToFEN(moveNum, cps->fenOverride);
6096       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6097       SendToProgram(message, cps);
6098       free(fen);
6099
6100     } else {
6101       ChessSquare *bp;
6102       int i, j, left=0, right=BOARD_WIDTH;
6103       /* Kludge to set black to move, avoiding the troublesome and now
6104        * deprecated "black" command.
6105        */
6106       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6107         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6108
6109       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6110
6111       SendToProgram("edit\n", cps);
6112       SendToProgram("#\n", cps);
6113       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6114         bp = &boards[moveNum][i][left];
6115         for (j = left; j < right; j++, bp++) {
6116           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6117           if ((int) *bp < (int) BlackPawn) {
6118             if(j == BOARD_RGHT+1)
6119                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6120             else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6121             if(message[0] == '+' || message[0] == '~') {
6122               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6123                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6124                         AAA + j, ONE + i);
6125             }
6126             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6127                 message[1] = BOARD_RGHT   - 1 - j + '1';
6128                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6129             }
6130             SendToProgram(message, cps);
6131           }
6132         }
6133       }
6134
6135       SendToProgram("c\n", cps);
6136       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6137         bp = &boards[moveNum][i][left];
6138         for (j = left; j < right; j++, bp++) {
6139           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6140           if (((int) *bp != (int) EmptySquare)
6141               && ((int) *bp >= (int) BlackPawn)) {
6142             if(j == BOARD_LEFT-2)
6143                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6144             else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6145                     AAA + j, ONE + i);
6146             if(message[0] == '+' || message[0] == '~') {
6147               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6148                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6149                         AAA + j, ONE + i);
6150             }
6151             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6152                 message[1] = BOARD_RGHT   - 1 - j + '1';
6153                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6154             }
6155             SendToProgram(message, cps);
6156           }
6157         }
6158       }
6159
6160       SendToProgram(".\n", cps);
6161     }
6162     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6163 }
6164
6165 char exclusionHeader[MSG_SIZ];
6166 int exCnt, excludePtr;
6167 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6168 static Exclusion excluTab[200];
6169 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6170
6171 static void
6172 WriteMap (int s)
6173 {
6174     int j;
6175     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6176     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6177 }
6178
6179 static void
6180 ClearMap ()
6181 {
6182     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6183     excludePtr = 24; exCnt = 0;
6184     WriteMap(0);
6185 }
6186
6187 static void
6188 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6189 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6190     char buf[2*MOVE_LEN], *p;
6191     Exclusion *e = excluTab;
6192     int i;
6193     for(i=0; i<exCnt; i++)
6194         if(e[i].ff == fromX && e[i].fr == fromY &&
6195            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6196     if(i == exCnt) { // was not in exclude list; add it
6197         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6198         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6199             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6200             return; // abort
6201         }
6202         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6203         excludePtr++; e[i].mark = excludePtr++;
6204         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6205         exCnt++;
6206     }
6207     exclusionHeader[e[i].mark] = state;
6208 }
6209
6210 static int
6211 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6212 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6213     char buf[MSG_SIZ];
6214     int j, k;
6215     ChessMove moveType;
6216     if((signed char)promoChar == -1) { // kludge to indicate best move
6217         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6218             return 1; // if unparsable, abort
6219     }
6220     // update exclusion map (resolving toggle by consulting existing state)
6221     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6222     j = k%8; k >>= 3;
6223     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6224     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6225          excludeMap[k] |=   1<<j;
6226     else excludeMap[k] &= ~(1<<j);
6227     // update header
6228     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6229     // inform engine
6230     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6231     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6232     SendToBoth(buf);
6233     return (state == '+');
6234 }
6235
6236 static void
6237 ExcludeClick (int index)
6238 {
6239     int i, j;
6240     Exclusion *e = excluTab;
6241     if(index < 25) { // none, best or tail clicked
6242         if(index < 13) { // none: include all
6243             WriteMap(0); // clear map
6244             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6245             SendToBoth("include all\n"); // and inform engine
6246         } else if(index > 18) { // tail
6247             if(exclusionHeader[19] == '-') { // tail was excluded
6248                 SendToBoth("include all\n");
6249                 WriteMap(0); // clear map completely
6250                 // now re-exclude selected moves
6251                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6252                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6253             } else { // tail was included or in mixed state
6254                 SendToBoth("exclude all\n");
6255                 WriteMap(0xFF); // fill map completely
6256                 // now re-include selected moves
6257                 j = 0; // count them
6258                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6259                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6260                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6261             }
6262         } else { // best
6263             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6264         }
6265     } else {
6266         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6267             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6268             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6269             break;
6270         }
6271     }
6272 }
6273
6274 ChessSquare
6275 DefaultPromoChoice (int white)
6276 {
6277     ChessSquare result;
6278     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
6279         result = WhiteFerz; // no choice
6280     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6281         result= WhiteKing; // in Suicide Q is the last thing we want
6282     else if(gameInfo.variant == VariantSpartan)
6283         result = white ? WhiteQueen : WhiteAngel;
6284     else result = WhiteQueen;
6285     if(!white) result = WHITE_TO_BLACK result;
6286     return result;
6287 }
6288
6289 static int autoQueen; // [HGM] oneclick
6290
6291 int
6292 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6293 {
6294     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6295     /* [HGM] add Shogi promotions */
6296     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6297     ChessSquare piece;
6298     ChessMove moveType;
6299     Boolean premove;
6300
6301     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6302     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6303
6304     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6305       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6306         return FALSE;
6307
6308     piece = boards[currentMove][fromY][fromX];
6309     if(gameInfo.variant == VariantShogi) {
6310         promotionZoneSize = BOARD_HEIGHT/3;
6311         highestPromotingPiece = (int)WhiteFerz;
6312     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6313         promotionZoneSize = 3;
6314     }
6315
6316     // Treat Lance as Pawn when it is not representing Amazon
6317     if(gameInfo.variant != VariantSuper) {
6318         if(piece == WhiteLance) piece = WhitePawn; else
6319         if(piece == BlackLance) piece = BlackPawn;
6320     }
6321
6322     // next weed out all moves that do not touch the promotion zone at all
6323     if((int)piece >= BlackPawn) {
6324         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6325              return FALSE;
6326         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6327     } else {
6328         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6329            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6330     }
6331
6332     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6333
6334     // weed out mandatory Shogi promotions
6335     if(gameInfo.variant == VariantShogi) {
6336         if(piece >= BlackPawn) {
6337             if(toY == 0 && piece == BlackPawn ||
6338                toY == 0 && piece == BlackQueen ||
6339                toY <= 1 && piece == BlackKnight) {
6340                 *promoChoice = '+';
6341                 return FALSE;
6342             }
6343         } else {
6344             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6345                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6346                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6347                 *promoChoice = '+';
6348                 return FALSE;
6349             }
6350         }
6351     }
6352
6353     // weed out obviously illegal Pawn moves
6354     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6355         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6356         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6357         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6358         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6359         // note we are not allowed to test for valid (non-)capture, due to premove
6360     }
6361
6362     // we either have a choice what to promote to, or (in Shogi) whether to promote
6363     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6364         *promoChoice = PieceToChar(BlackFerz);  // no choice
6365         return FALSE;
6366     }
6367     // no sense asking what we must promote to if it is going to explode...
6368     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6369         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6370         return FALSE;
6371     }
6372     // give caller the default choice even if we will not make it
6373     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6374     if(gameInfo.variant == VariantShogi) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6375     if(        sweepSelect && gameInfo.variant != VariantGreat
6376                            && gameInfo.variant != VariantGrand
6377                            && gameInfo.variant != VariantSuper) return FALSE;
6378     if(autoQueen) return FALSE; // predetermined
6379
6380     // suppress promotion popup on illegal moves that are not premoves
6381     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6382               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6383     if(appData.testLegality && !premove) {
6384         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6385                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6386         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6387             return FALSE;
6388     }
6389
6390     return TRUE;
6391 }
6392
6393 int
6394 InPalace (int row, int column)
6395 {   /* [HGM] for Xiangqi */
6396     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6397          column < (BOARD_WIDTH + 4)/2 &&
6398          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6399     return FALSE;
6400 }
6401
6402 int
6403 PieceForSquare (int x, int y)
6404 {
6405   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6406      return -1;
6407   else
6408      return boards[currentMove][y][x];
6409 }
6410
6411 int
6412 OKToStartUserMove (int x, int y)
6413 {
6414     ChessSquare from_piece;
6415     int white_piece;
6416
6417     if (matchMode) return FALSE;
6418     if (gameMode == EditPosition) return TRUE;
6419
6420     if (x >= 0 && y >= 0)
6421       from_piece = boards[currentMove][y][x];
6422     else
6423       from_piece = EmptySquare;
6424
6425     if (from_piece == EmptySquare) return FALSE;
6426
6427     white_piece = (int)from_piece >= (int)WhitePawn &&
6428       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6429
6430     switch (gameMode) {
6431       case AnalyzeFile:
6432       case TwoMachinesPlay:
6433       case EndOfGame:
6434         return FALSE;
6435
6436       case IcsObserving:
6437       case IcsIdle:
6438         return FALSE;
6439
6440       case MachinePlaysWhite:
6441       case IcsPlayingBlack:
6442         if (appData.zippyPlay) return FALSE;
6443         if (white_piece) {
6444             DisplayMoveError(_("You are playing Black"));
6445             return FALSE;
6446         }
6447         break;
6448
6449       case MachinePlaysBlack:
6450       case IcsPlayingWhite:
6451         if (appData.zippyPlay) return FALSE;
6452         if (!white_piece) {
6453             DisplayMoveError(_("You are playing White"));
6454             return FALSE;
6455         }
6456         break;
6457
6458       case PlayFromGameFile:
6459             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6460       case EditGame:
6461         if (!white_piece && WhiteOnMove(currentMove)) {
6462             DisplayMoveError(_("It is White's turn"));
6463             return FALSE;
6464         }
6465         if (white_piece && !WhiteOnMove(currentMove)) {
6466             DisplayMoveError(_("It is Black's turn"));
6467             return FALSE;
6468         }
6469         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6470             /* Editing correspondence game history */
6471             /* Could disallow this or prompt for confirmation */
6472             cmailOldMove = -1;
6473         }
6474         break;
6475
6476       case BeginningOfGame:
6477         if (appData.icsActive) return FALSE;
6478         if (!appData.noChessProgram) {
6479             if (!white_piece) {
6480                 DisplayMoveError(_("You are playing White"));
6481                 return FALSE;
6482             }
6483         }
6484         break;
6485
6486       case Training:
6487         if (!white_piece && WhiteOnMove(currentMove)) {
6488             DisplayMoveError(_("It is White's turn"));
6489             return FALSE;
6490         }
6491         if (white_piece && !WhiteOnMove(currentMove)) {
6492             DisplayMoveError(_("It is Black's turn"));
6493             return FALSE;
6494         }
6495         break;
6496
6497       default:
6498       case IcsExamining:
6499         break;
6500     }
6501     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6502         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6503         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6504         && gameMode != AnalyzeFile && gameMode != Training) {
6505         DisplayMoveError(_("Displayed position is not current"));
6506         return FALSE;
6507     }
6508     return TRUE;
6509 }
6510
6511 Boolean
6512 OnlyMove (int *x, int *y, Boolean captures) 
6513 {
6514     DisambiguateClosure cl;
6515     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6516     switch(gameMode) {
6517       case MachinePlaysBlack:
6518       case IcsPlayingWhite:
6519       case BeginningOfGame:
6520         if(!WhiteOnMove(currentMove)) return FALSE;
6521         break;
6522       case MachinePlaysWhite:
6523       case IcsPlayingBlack:
6524         if(WhiteOnMove(currentMove)) return FALSE;
6525         break;
6526       case EditGame:
6527         break;
6528       default:
6529         return FALSE;
6530     }
6531     cl.pieceIn = EmptySquare;
6532     cl.rfIn = *y;
6533     cl.ffIn = *x;
6534     cl.rtIn = -1;
6535     cl.ftIn = -1;
6536     cl.promoCharIn = NULLCHAR;
6537     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6538     if( cl.kind == NormalMove ||
6539         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6540         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6541         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6542       fromX = cl.ff;
6543       fromY = cl.rf;
6544       *x = cl.ft;
6545       *y = cl.rt;
6546       return TRUE;
6547     }
6548     if(cl.kind != ImpossibleMove) return FALSE;
6549     cl.pieceIn = EmptySquare;
6550     cl.rfIn = -1;
6551     cl.ffIn = -1;
6552     cl.rtIn = *y;
6553     cl.ftIn = *x;
6554     cl.promoCharIn = NULLCHAR;
6555     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6556     if( cl.kind == NormalMove ||
6557         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6558         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6559         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6560       fromX = cl.ff;
6561       fromY = cl.rf;
6562       *x = cl.ft;
6563       *y = cl.rt;
6564       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6565       return TRUE;
6566     }
6567     return FALSE;
6568 }
6569
6570 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6571 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6572 int lastLoadGameUseList = FALSE;
6573 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6574 ChessMove lastLoadGameStart = EndOfFile;
6575 int doubleClick;
6576
6577 void
6578 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6579 {
6580     ChessMove moveType;
6581     ChessSquare pup;
6582     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6583
6584     /* Check if the user is playing in turn.  This is complicated because we
6585        let the user "pick up" a piece before it is his turn.  So the piece he
6586        tried to pick up may have been captured by the time he puts it down!
6587        Therefore we use the color the user is supposed to be playing in this
6588        test, not the color of the piece that is currently on the starting
6589        square---except in EditGame mode, where the user is playing both
6590        sides; fortunately there the capture race can't happen.  (It can
6591        now happen in IcsExamining mode, but that's just too bad.  The user
6592        will get a somewhat confusing message in that case.)
6593        */
6594
6595     switch (gameMode) {
6596       case AnalyzeFile:
6597       case TwoMachinesPlay:
6598       case EndOfGame:
6599       case IcsObserving:
6600       case IcsIdle:
6601         /* We switched into a game mode where moves are not accepted,
6602            perhaps while the mouse button was down. */
6603         return;
6604
6605       case MachinePlaysWhite:
6606         /* User is moving for Black */
6607         if (WhiteOnMove(currentMove)) {
6608             DisplayMoveError(_("It is White's turn"));
6609             return;
6610         }
6611         break;
6612
6613       case MachinePlaysBlack:
6614         /* User is moving for White */
6615         if (!WhiteOnMove(currentMove)) {
6616             DisplayMoveError(_("It is Black's turn"));
6617             return;
6618         }
6619         break;
6620
6621       case PlayFromGameFile:
6622             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6623       case EditGame:
6624       case IcsExamining:
6625       case BeginningOfGame:
6626       case AnalyzeMode:
6627       case Training:
6628         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6629         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6630             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6631             /* User is moving for Black */
6632             if (WhiteOnMove(currentMove)) {
6633                 DisplayMoveError(_("It is White's turn"));
6634                 return;
6635             }
6636         } else {
6637             /* User is moving for White */
6638             if (!WhiteOnMove(currentMove)) {
6639                 DisplayMoveError(_("It is Black's turn"));
6640                 return;
6641             }
6642         }
6643         break;
6644
6645       case IcsPlayingBlack:
6646         /* User is moving for Black */
6647         if (WhiteOnMove(currentMove)) {
6648             if (!appData.premove) {
6649                 DisplayMoveError(_("It is White's turn"));
6650             } else if (toX >= 0 && toY >= 0) {
6651                 premoveToX = toX;
6652                 premoveToY = toY;
6653                 premoveFromX = fromX;
6654                 premoveFromY = fromY;
6655                 premovePromoChar = promoChar;
6656                 gotPremove = 1;
6657                 if (appData.debugMode)
6658                     fprintf(debugFP, "Got premove: fromX %d,"
6659                             "fromY %d, toX %d, toY %d\n",
6660                             fromX, fromY, toX, toY);
6661             }
6662             return;
6663         }
6664         break;
6665
6666       case IcsPlayingWhite:
6667         /* User is moving for White */
6668         if (!WhiteOnMove(currentMove)) {
6669             if (!appData.premove) {
6670                 DisplayMoveError(_("It is Black's turn"));
6671             } else if (toX >= 0 && toY >= 0) {
6672                 premoveToX = toX;
6673                 premoveToY = toY;
6674                 premoveFromX = fromX;
6675                 premoveFromY = fromY;
6676                 premovePromoChar = promoChar;
6677                 gotPremove = 1;
6678                 if (appData.debugMode)
6679                     fprintf(debugFP, "Got premove: fromX %d,"
6680                             "fromY %d, toX %d, toY %d\n",
6681                             fromX, fromY, toX, toY);
6682             }
6683             return;
6684         }
6685         break;
6686
6687       default:
6688         break;
6689
6690       case EditPosition:
6691         /* EditPosition, empty square, or different color piece;
6692            click-click move is possible */
6693         if (toX == -2 || toY == -2) {
6694             boards[0][fromY][fromX] = EmptySquare;
6695             DrawPosition(FALSE, boards[currentMove]);
6696             return;
6697         } else if (toX >= 0 && toY >= 0) {
6698             boards[0][toY][toX] = boards[0][fromY][fromX];
6699             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6700                 if(boards[0][fromY][0] != EmptySquare) {
6701                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6702                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6703                 }
6704             } else
6705             if(fromX == BOARD_RGHT+1) {
6706                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6707                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6708                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6709                 }
6710             } else
6711             boards[0][fromY][fromX] = gatingPiece;
6712             DrawPosition(FALSE, boards[currentMove]);
6713             return;
6714         }
6715         return;
6716     }
6717
6718     if(toX < 0 || toY < 0) return;
6719     pup = boards[currentMove][toY][toX];
6720
6721     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6722     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6723          if( pup != EmptySquare ) return;
6724          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6725            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6726                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6727            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6728            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6729            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6730            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6731          fromY = DROP_RANK;
6732     }
6733
6734     /* [HGM] always test for legality, to get promotion info */
6735     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6736                                          fromY, fromX, toY, toX, promoChar);
6737
6738     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6739
6740     /* [HGM] but possibly ignore an IllegalMove result */
6741     if (appData.testLegality) {
6742         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6743             DisplayMoveError(_("Illegal move"));
6744             return;
6745         }
6746     }
6747
6748     if(doubleClick) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
6749         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
6750              ClearPremoveHighlights(); // was included
6751         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
6752         return;
6753     }
6754
6755     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6756 }
6757
6758 /* Common tail of UserMoveEvent and DropMenuEvent */
6759 int
6760 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
6761 {
6762     char *bookHit = 0;
6763
6764     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6765         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6766         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6767         if(WhiteOnMove(currentMove)) {
6768             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6769         } else {
6770             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6771         }
6772     }
6773
6774     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6775        move type in caller when we know the move is a legal promotion */
6776     if(moveType == NormalMove && promoChar)
6777         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6778
6779     /* [HGM] <popupFix> The following if has been moved here from
6780        UserMoveEvent(). Because it seemed to belong here (why not allow
6781        piece drops in training games?), and because it can only be
6782        performed after it is known to what we promote. */
6783     if (gameMode == Training) {
6784       /* compare the move played on the board to the next move in the
6785        * game. If they match, display the move and the opponent's response.
6786        * If they don't match, display an error message.
6787        */
6788       int saveAnimate;
6789       Board testBoard;
6790       CopyBoard(testBoard, boards[currentMove]);
6791       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6792
6793       if (CompareBoards(testBoard, boards[currentMove+1])) {
6794         ForwardInner(currentMove+1);
6795
6796         /* Autoplay the opponent's response.
6797          * if appData.animate was TRUE when Training mode was entered,
6798          * the response will be animated.
6799          */
6800         saveAnimate = appData.animate;
6801         appData.animate = animateTraining;
6802         ForwardInner(currentMove+1);
6803         appData.animate = saveAnimate;
6804
6805         /* check for the end of the game */
6806         if (currentMove >= forwardMostMove) {
6807           gameMode = PlayFromGameFile;
6808           ModeHighlight();
6809           SetTrainingModeOff();
6810           DisplayInformation(_("End of game"));
6811         }
6812       } else {
6813         DisplayError(_("Incorrect move"), 0);
6814       }
6815       return 1;
6816     }
6817
6818   /* Ok, now we know that the move is good, so we can kill
6819      the previous line in Analysis Mode */
6820   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6821                                 && currentMove < forwardMostMove) {
6822     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6823     else forwardMostMove = currentMove;
6824   }
6825
6826   ClearMap();
6827
6828   /* If we need the chess program but it's dead, restart it */
6829   ResurrectChessProgram();
6830
6831   /* A user move restarts a paused game*/
6832   if (pausing)
6833     PauseEvent();
6834
6835   thinkOutput[0] = NULLCHAR;
6836
6837   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6838
6839   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6840     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6841     return 1;
6842   }
6843
6844   if (gameMode == BeginningOfGame) {
6845     if (appData.noChessProgram) {
6846       gameMode = EditGame;
6847       SetGameInfo();
6848     } else {
6849       char buf[MSG_SIZ];
6850       gameMode = MachinePlaysBlack;
6851       StartClocks();
6852       SetGameInfo();
6853       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
6854       DisplayTitle(buf);
6855       if (first.sendName) {
6856         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6857         SendToProgram(buf, &first);
6858       }
6859       StartClocks();
6860     }
6861     ModeHighlight();
6862   }
6863
6864   /* Relay move to ICS or chess engine */
6865   if (appData.icsActive) {
6866     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6867         gameMode == IcsExamining) {
6868       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6869         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6870         SendToICS("draw ");
6871         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6872       }
6873       // also send plain move, in case ICS does not understand atomic claims
6874       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6875       ics_user_moved = 1;
6876     }
6877   } else {
6878     if (first.sendTime && (gameMode == BeginningOfGame ||
6879                            gameMode == MachinePlaysWhite ||
6880                            gameMode == MachinePlaysBlack)) {
6881       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6882     }
6883     if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
6884          // [HGM] book: if program might be playing, let it use book
6885         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6886         first.maybeThinking = TRUE;
6887     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
6888         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
6889         SendBoard(&first, currentMove+1);
6890         if(second.analyzing) {
6891             if(!second.useSetboard) SendToProgram("undo\n", &second);
6892             SendBoard(&second, currentMove+1);
6893         }
6894     } else {
6895         SendMoveToProgram(forwardMostMove-1, &first);
6896         if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
6897     }
6898     if (currentMove == cmailOldMove + 1) {
6899       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6900     }
6901   }
6902
6903   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6904
6905   switch (gameMode) {
6906   case EditGame:
6907     if(appData.testLegality)
6908     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6909     case MT_NONE:
6910     case MT_CHECK:
6911       break;
6912     case MT_CHECKMATE:
6913     case MT_STAINMATE:
6914       if (WhiteOnMove(currentMove)) {
6915         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6916       } else {
6917         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6918       }
6919       break;
6920     case MT_STALEMATE:
6921       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6922       break;
6923     }
6924     break;
6925
6926   case MachinePlaysBlack:
6927   case MachinePlaysWhite:
6928     /* disable certain menu options while machine is thinking */
6929     SetMachineThinkingEnables();
6930     break;
6931
6932   default:
6933     break;
6934   }
6935
6936   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6937   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6938
6939   if(bookHit) { // [HGM] book: simulate book reply
6940         static char bookMove[MSG_SIZ]; // a bit generous?
6941
6942         programStats.nodes = programStats.depth = programStats.time =
6943         programStats.score = programStats.got_only_move = 0;
6944         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6945
6946         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6947         strcat(bookMove, bookHit);
6948         HandleMachineMove(bookMove, &first);
6949   }
6950   return 1;
6951 }
6952
6953 void
6954 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
6955 {
6956     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6957     Markers *m = (Markers *) closure;
6958     if(rf == fromY && ff == fromX)
6959         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6960                          || kind == WhiteCapturesEnPassant
6961                          || kind == BlackCapturesEnPassant);
6962     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6963 }
6964
6965 void
6966 MarkTargetSquares (int clear)
6967 {
6968   int x, y;
6969   if(clear) // no reason to ever suppress clearing
6970     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6971   if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
6972      !appData.testLegality || gameMode == EditPosition) return;
6973   if(!clear) {
6974     int capt = 0;
6975     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
6976     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6977       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6978       if(capt)
6979       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6980     }
6981   }
6982   DrawPosition(FALSE, NULL);
6983 }
6984
6985 int
6986 Explode (Board board, int fromX, int fromY, int toX, int toY)
6987 {
6988     if(gameInfo.variant == VariantAtomic &&
6989        (board[toY][toX] != EmptySquare ||                     // capture?
6990         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6991                          board[fromY][fromX] == BlackPawn   )
6992       )) {
6993         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6994         return TRUE;
6995     }
6996     return FALSE;
6997 }
6998
6999 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7000
7001 int
7002 CanPromote (ChessSquare piece, int y)
7003 {
7004         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7005         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7006         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
7007            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7008            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7009                                                   gameInfo.variant == VariantMakruk) return FALSE;
7010         return (piece == BlackPawn && y == 1 ||
7011                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
7012                 piece == BlackLance && y == 1 ||
7013                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
7014 }
7015
7016 void
7017 LeftClick (ClickType clickType, int xPix, int yPix)
7018 {
7019     int x, y;
7020     Boolean saveAnimate;
7021     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7022     char promoChoice = NULLCHAR;
7023     ChessSquare piece;
7024     static TimeMark lastClickTime, prevClickTime;
7025
7026     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7027
7028     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7029
7030     if (clickType == Press) ErrorPopDown();
7031
7032     x = EventToSquare(xPix, BOARD_WIDTH);
7033     y = EventToSquare(yPix, BOARD_HEIGHT);
7034     if (!flipView && y >= 0) {
7035         y = BOARD_HEIGHT - 1 - y;
7036     }
7037     if (flipView && x >= 0) {
7038         x = BOARD_WIDTH - 1 - x;
7039     }
7040
7041     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7042         defaultPromoChoice = promoSweep;
7043         promoSweep = EmptySquare;   // terminate sweep
7044         promoDefaultAltered = TRUE;
7045         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7046     }
7047
7048     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7049         if(clickType == Release) return; // ignore upclick of click-click destination
7050         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7051         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7052         if(gameInfo.holdingsWidth &&
7053                 (WhiteOnMove(currentMove)
7054                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7055                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7056             // click in right holdings, for determining promotion piece
7057             ChessSquare p = boards[currentMove][y][x];
7058             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7059             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7060             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7061                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7062                 fromX = fromY = -1;
7063                 return;
7064             }
7065         }
7066         DrawPosition(FALSE, boards[currentMove]);
7067         return;
7068     }
7069
7070     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7071     if(clickType == Press
7072             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7073               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7074               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7075         return;
7076
7077     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7078         // could be static click on premove from-square: abort premove
7079         gotPremove = 0;
7080         ClearPremoveHighlights();
7081     }
7082
7083     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7084         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7085
7086     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7087         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7088                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7089         defaultPromoChoice = DefaultPromoChoice(side);
7090     }
7091
7092     autoQueen = appData.alwaysPromoteToQueen;
7093
7094     if (fromX == -1) {
7095       int originalY = y;
7096       gatingPiece = EmptySquare;
7097       if (clickType != Press) {
7098         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7099             DragPieceEnd(xPix, yPix); dragging = 0;
7100             DrawPosition(FALSE, NULL);
7101         }
7102         return;
7103       }
7104       doubleClick = FALSE;
7105       fromX = x; fromY = y; toX = toY = -1;
7106       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7107          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7108          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7109             /* First square */
7110             if (OKToStartUserMove(fromX, fromY)) {
7111                 second = 0;
7112                 MarkTargetSquares(0);
7113                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7114                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7115                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7116                     promoSweep = defaultPromoChoice;
7117                     selectFlag = 0; lastX = xPix; lastY = yPix;
7118                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7119                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7120                 }
7121                 if (appData.highlightDragging) {
7122                     SetHighlights(fromX, fromY, -1, -1);
7123                 } else {
7124                     ClearHighlights();
7125                 }
7126             } else fromX = fromY = -1;
7127             return;
7128         }
7129     }
7130
7131     /* fromX != -1 */
7132     if (clickType == Press && gameMode != EditPosition) {
7133         ChessSquare fromP;
7134         ChessSquare toP;
7135         int frc;
7136
7137         // ignore off-board to clicks
7138         if(y < 0 || x < 0) return;
7139
7140         /* Check if clicking again on the same color piece */
7141         fromP = boards[currentMove][fromY][fromX];
7142         toP = boards[currentMove][y][x];
7143         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
7144         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
7145              WhitePawn <= toP && toP <= WhiteKing &&
7146              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7147              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7148             (BlackPawn <= fromP && fromP <= BlackKing &&
7149              BlackPawn <= toP && toP <= BlackKing &&
7150              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7151              !(fromP == BlackKing && toP == BlackRook && frc))) {
7152             /* Clicked again on same color piece -- changed his mind */
7153             second = (x == fromX && y == fromY);
7154             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7155                 second = FALSE; // first double-click rather than scond click
7156                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7157             }
7158             promoDefaultAltered = FALSE;
7159             MarkTargetSquares(1);
7160            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
7161             if (appData.highlightDragging) {
7162                 SetHighlights(x, y, -1, -1);
7163             } else {
7164                 ClearHighlights();
7165             }
7166             if (OKToStartUserMove(x, y)) {
7167                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7168                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7169                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7170                  gatingPiece = boards[currentMove][fromY][fromX];
7171                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7172                 fromX = x;
7173                 fromY = y; dragging = 1;
7174                 MarkTargetSquares(0);
7175                 DragPieceBegin(xPix, yPix, FALSE);
7176                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7177                     promoSweep = defaultPromoChoice;
7178                     selectFlag = 0; lastX = xPix; lastY = yPix;
7179                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7180                 }
7181             }
7182            }
7183            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7184            second = FALSE; 
7185         }
7186         // ignore clicks on holdings
7187         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7188     }
7189
7190     if (clickType == Release && x == fromX && y == fromY) {
7191         DragPieceEnd(xPix, yPix); dragging = 0;
7192         if(clearFlag) {
7193             // a deferred attempt to click-click move an empty square on top of a piece
7194             boards[currentMove][y][x] = EmptySquare;
7195             ClearHighlights();
7196             DrawPosition(FALSE, boards[currentMove]);
7197             fromX = fromY = -1; clearFlag = 0;
7198             return;
7199         }
7200         if (appData.animateDragging) {
7201             /* Undo animation damage if any */
7202             DrawPosition(FALSE, NULL);
7203         }
7204         if (second || sweepSelecting) {
7205             /* Second up/down in same square; just abort move */
7206             if(sweepSelecting) DrawPosition(FALSE, boards[currentMove]);
7207             second = sweepSelecting = 0;
7208             fromX = fromY = -1;
7209             gatingPiece = EmptySquare;
7210             ClearHighlights();
7211             gotPremove = 0;
7212             ClearPremoveHighlights();
7213         } else {
7214             /* First upclick in same square; start click-click mode */
7215             SetHighlights(x, y, -1, -1);
7216         }
7217         return;
7218     }
7219
7220     clearFlag = 0;
7221
7222     /* we now have a different from- and (possibly off-board) to-square */
7223     /* Completed move */
7224     if(!sweepSelecting) {
7225         toX = x;
7226         toY = y;
7227     } else sweepSelecting = 0; // this must be the up-click corresponding to the down-click that started the sweep
7228
7229     saveAnimate = appData.animate;
7230     if (clickType == Press) {
7231         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7232             // must be Edit Position mode with empty-square selected
7233             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7234             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7235             return;
7236         }
7237         if(HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7238           if(appData.sweepSelect) {
7239             ChessSquare piece = boards[currentMove][fromY][fromX];
7240             promoSweep = defaultPromoChoice;
7241             if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
7242             selectFlag = 0; lastX = xPix; lastY = yPix;
7243             Sweep(0); // Pawn that is going to promote: preview promotion piece
7244             sweepSelecting = 1;
7245             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7246             MarkTargetSquares(1);
7247           }
7248           return; // promo popup appears on up-click
7249         }
7250         /* Finish clickclick move */
7251         if (appData.animate || appData.highlightLastMove) {
7252             SetHighlights(fromX, fromY, toX, toY);
7253         } else {
7254             ClearHighlights();
7255         }
7256     } else {
7257         /* Finish drag move */
7258         if (appData.highlightLastMove) {
7259             SetHighlights(fromX, fromY, toX, toY);
7260         } else {
7261             ClearHighlights();
7262         }
7263         DragPieceEnd(xPix, yPix); dragging = 0;
7264         /* Don't animate move and drag both */
7265         appData.animate = FALSE;
7266     }
7267     MarkTargetSquares(1);
7268
7269     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7270     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7271         ChessSquare piece = boards[currentMove][fromY][fromX];
7272         if(gameMode == EditPosition && piece != EmptySquare &&
7273            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7274             int n;
7275
7276             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7277                 n = PieceToNumber(piece - (int)BlackPawn);
7278                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7279                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7280                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7281             } else
7282             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7283                 n = PieceToNumber(piece);
7284                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7285                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7286                 boards[currentMove][n][BOARD_WIDTH-2]++;
7287             }
7288             boards[currentMove][fromY][fromX] = EmptySquare;
7289         }
7290         ClearHighlights();
7291         fromX = fromY = -1;
7292         DrawPosition(TRUE, boards[currentMove]);
7293         return;
7294     }
7295
7296     // off-board moves should not be highlighted
7297     if(x < 0 || y < 0) ClearHighlights();
7298
7299     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7300
7301     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7302         SetHighlights(fromX, fromY, toX, toY);
7303         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7304             // [HGM] super: promotion to captured piece selected from holdings
7305             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7306             promotionChoice = TRUE;
7307             // kludge follows to temporarily execute move on display, without promoting yet
7308             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7309             boards[currentMove][toY][toX] = p;
7310             DrawPosition(FALSE, boards[currentMove]);
7311             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7312             boards[currentMove][toY][toX] = q;
7313             DisplayMessage("Click in holdings to choose piece", "");
7314             return;
7315         }
7316         PromotionPopUp();
7317     } else {
7318         int oldMove = currentMove;
7319         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7320         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7321         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7322         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7323            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7324             DrawPosition(TRUE, boards[currentMove]);
7325         fromX = fromY = -1;
7326     }
7327     appData.animate = saveAnimate;
7328     if (appData.animate || appData.animateDragging) {
7329         /* Undo animation damage if needed */
7330         DrawPosition(FALSE, NULL);
7331     }
7332 }
7333
7334 int
7335 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7336 {   // front-end-free part taken out of PieceMenuPopup
7337     int whichMenu; int xSqr, ySqr;
7338
7339     if(seekGraphUp) { // [HGM] seekgraph
7340         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7341         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7342         return -2;
7343     }
7344
7345     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7346          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7347         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7348         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7349         if(action == Press)   {
7350             originalFlip = flipView;
7351             flipView = !flipView; // temporarily flip board to see game from partners perspective
7352             DrawPosition(TRUE, partnerBoard);
7353             DisplayMessage(partnerStatus, "");
7354             partnerUp = TRUE;
7355         } else if(action == Release) {
7356             flipView = originalFlip;
7357             DrawPosition(TRUE, boards[currentMove]);
7358             partnerUp = FALSE;
7359         }
7360         return -2;
7361     }
7362
7363     xSqr = EventToSquare(x, BOARD_WIDTH);
7364     ySqr = EventToSquare(y, BOARD_HEIGHT);
7365     if (action == Release) {
7366         if(pieceSweep != EmptySquare) {
7367             EditPositionMenuEvent(pieceSweep, toX, toY);
7368             pieceSweep = EmptySquare;
7369         } else UnLoadPV(); // [HGM] pv
7370     }
7371     if (action != Press) return -2; // return code to be ignored
7372     switch (gameMode) {
7373       case IcsExamining:
7374         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7375       case EditPosition:
7376         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7377         if (xSqr < 0 || ySqr < 0) return -1;
7378         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7379         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7380         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7381         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7382         NextPiece(0);
7383         return 2; // grab
7384       case IcsObserving:
7385         if(!appData.icsEngineAnalyze) return -1;
7386       case IcsPlayingWhite:
7387       case IcsPlayingBlack:
7388         if(!appData.zippyPlay) goto noZip;
7389       case AnalyzeMode:
7390       case AnalyzeFile:
7391       case MachinePlaysWhite:
7392       case MachinePlaysBlack:
7393       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7394         if (!appData.dropMenu) {
7395           LoadPV(x, y);
7396           return 2; // flag front-end to grab mouse events
7397         }
7398         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7399            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7400       case EditGame:
7401       noZip:
7402         if (xSqr < 0 || ySqr < 0) return -1;
7403         if (!appData.dropMenu || appData.testLegality &&
7404             gameInfo.variant != VariantBughouse &&
7405             gameInfo.variant != VariantCrazyhouse) return -1;
7406         whichMenu = 1; // drop menu
7407         break;
7408       default:
7409         return -1;
7410     }
7411
7412     if (((*fromX = xSqr) < 0) ||
7413         ((*fromY = ySqr) < 0)) {
7414         *fromX = *fromY = -1;
7415         return -1;
7416     }
7417     if (flipView)
7418       *fromX = BOARD_WIDTH - 1 - *fromX;
7419     else
7420       *fromY = BOARD_HEIGHT - 1 - *fromY;
7421
7422     return whichMenu;
7423 }
7424
7425 void
7426 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7427 {
7428 //    char * hint = lastHint;
7429     FrontEndProgramStats stats;
7430
7431     stats.which = cps == &first ? 0 : 1;
7432     stats.depth = cpstats->depth;
7433     stats.nodes = cpstats->nodes;
7434     stats.score = cpstats->score;
7435     stats.time = cpstats->time;
7436     stats.pv = cpstats->movelist;
7437     stats.hint = lastHint;
7438     stats.an_move_index = 0;
7439     stats.an_move_count = 0;
7440
7441     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7442         stats.hint = cpstats->move_name;
7443         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7444         stats.an_move_count = cpstats->nr_moves;
7445     }
7446
7447     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
7448
7449     SetProgramStats( &stats );
7450 }
7451
7452 void
7453 ClearEngineOutputPane (int which)
7454 {
7455     static FrontEndProgramStats dummyStats;
7456     dummyStats.which = which;
7457     dummyStats.pv = "#";
7458     SetProgramStats( &dummyStats );
7459 }
7460
7461 #define MAXPLAYERS 500
7462
7463 char *
7464 TourneyStandings (int display)
7465 {
7466     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7467     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7468     char result, *p, *names[MAXPLAYERS];
7469
7470     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7471         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7472     names[0] = p = strdup(appData.participants);
7473     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7474
7475     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7476
7477     while(result = appData.results[nr]) {
7478         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7479         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7480         wScore = bScore = 0;
7481         switch(result) {
7482           case '+': wScore = 2; break;
7483           case '-': bScore = 2; break;
7484           case '=': wScore = bScore = 1; break;
7485           case ' ':
7486           case '*': return strdup("busy"); // tourney not finished
7487         }
7488         score[w] += wScore;
7489         score[b] += bScore;
7490         games[w]++;
7491         games[b]++;
7492         nr++;
7493     }
7494     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7495     for(w=0; w<nPlayers; w++) {
7496         bScore = -1;
7497         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7498         ranking[w] = b; points[w] = bScore; score[b] = -2;
7499     }
7500     p = malloc(nPlayers*34+1);
7501     for(w=0; w<nPlayers && w<display; w++)
7502         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7503     free(names[0]);
7504     return p;
7505 }
7506
7507 void
7508 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7509 {       // count all piece types
7510         int p, f, r;
7511         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7512         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7513         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7514                 p = board[r][f];
7515                 pCnt[p]++;
7516                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7517                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7518                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7519                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7520                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7521                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7522         }
7523 }
7524
7525 int
7526 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7527 {
7528         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7529         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7530
7531         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7532         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7533         if(myPawns == 2 && nMine == 3) // KPP
7534             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7535         if(myPawns == 1 && nMine == 2) // KP
7536             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7537         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7538             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7539         if(myPawns) return FALSE;
7540         if(pCnt[WhiteRook+side])
7541             return pCnt[BlackRook-side] ||
7542                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7543                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7544                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7545         if(pCnt[WhiteCannon+side]) {
7546             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7547             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7548         }
7549         if(pCnt[WhiteKnight+side])
7550             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7551         return FALSE;
7552 }
7553
7554 int
7555 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7556 {
7557         VariantClass v = gameInfo.variant;
7558
7559         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7560         if(v == VariantShatranj) return TRUE; // always winnable through baring
7561         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7562         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7563
7564         if(v == VariantXiangqi) {
7565                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7566
7567                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7568                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7569                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7570                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7571                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7572                 if(stale) // we have at least one last-rank P plus perhaps C
7573                     return majors // KPKX
7574                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7575                 else // KCA*E*
7576                     return pCnt[WhiteFerz+side] // KCAK
7577                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7578                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7579                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7580
7581         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7582                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7583
7584                 if(nMine == 1) return FALSE; // bare King
7585                 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
7586                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7587                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7588                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7589                 if(pCnt[WhiteKnight+side])
7590                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7591                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7592                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7593                 if(nBishops)
7594                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7595                 if(pCnt[WhiteAlfil+side])
7596                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7597                 if(pCnt[WhiteWazir+side])
7598                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7599         }
7600
7601         return TRUE;
7602 }
7603
7604 int
7605 CompareWithRights (Board b1, Board b2)
7606 {
7607     int rights = 0;
7608     if(!CompareBoards(b1, b2)) return FALSE;
7609     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7610     /* compare castling rights */
7611     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7612            rights++; /* King lost rights, while rook still had them */
7613     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7614         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7615            rights++; /* but at least one rook lost them */
7616     }
7617     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7618            rights++;
7619     if( b1[CASTLING][5] != NoRights ) {
7620         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7621            rights++;
7622     }
7623     return rights == 0;
7624 }
7625
7626 int
7627 Adjudicate (ChessProgramState *cps)
7628 {       // [HGM] some adjudications useful with buggy engines
7629         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7630         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7631         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7632         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7633         int k, count = 0; static int bare = 1;
7634         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7635         Boolean canAdjudicate = !appData.icsActive;
7636
7637         // most tests only when we understand the game, i.e. legality-checking on
7638             if( appData.testLegality )
7639             {   /* [HGM] Some more adjudications for obstinate engines */
7640                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7641                 static int moveCount = 6;
7642                 ChessMove result;
7643                 char *reason = NULL;
7644
7645                 /* Count what is on board. */
7646                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7647
7648                 /* Some material-based adjudications that have to be made before stalemate test */
7649                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7650                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7651                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7652                      if(canAdjudicate && appData.checkMates) {
7653                          if(engineOpponent)
7654                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7655                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7656                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7657                          return 1;
7658                      }
7659                 }
7660
7661                 /* Bare King in Shatranj (loses) or Losers (wins) */
7662                 if( nrW == 1 || nrB == 1) {
7663                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7664                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7665                      if(canAdjudicate && appData.checkMates) {
7666                          if(engineOpponent)
7667                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7668                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7669                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7670                          return 1;
7671                      }
7672                   } else
7673                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7674                   {    /* bare King */
7675                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7676                         if(canAdjudicate && appData.checkMates) {
7677                             /* but only adjudicate if adjudication enabled */
7678                             if(engineOpponent)
7679                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7680                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7681                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7682                             return 1;
7683                         }
7684                   }
7685                 } else bare = 1;
7686
7687
7688             // don't wait for engine to announce game end if we can judge ourselves
7689             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7690               case MT_CHECK:
7691                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7692                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7693                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7694                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7695                             checkCnt++;
7696                         if(checkCnt >= 2) {
7697                             reason = "Xboard adjudication: 3rd check";
7698                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7699                             break;
7700                         }
7701                     }
7702                 }
7703               case MT_NONE:
7704               default:
7705                 break;
7706               case MT_STALEMATE:
7707               case MT_STAINMATE:
7708                 reason = "Xboard adjudication: Stalemate";
7709                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7710                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7711                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7712                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7713                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7714                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7715                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7716                                                                         EP_CHECKMATE : EP_WINS);
7717                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7718                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7719                 }
7720                 break;
7721               case MT_CHECKMATE:
7722                 reason = "Xboard adjudication: Checkmate";
7723                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7724                 break;
7725             }
7726
7727                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7728                     case EP_STALEMATE:
7729                         result = GameIsDrawn; break;
7730                     case EP_CHECKMATE:
7731                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7732                     case EP_WINS:
7733                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7734                     default:
7735                         result = EndOfFile;
7736                 }
7737                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7738                     if(engineOpponent)
7739                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7740                     GameEnds( result, reason, GE_XBOARD );
7741                     return 1;
7742                 }
7743
7744                 /* Next absolutely insufficient mating material. */
7745                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7746                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7747                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7748
7749                      /* always flag draws, for judging claims */
7750                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7751
7752                      if(canAdjudicate && appData.materialDraws) {
7753                          /* but only adjudicate them if adjudication enabled */
7754                          if(engineOpponent) {
7755                            SendToProgram("force\n", engineOpponent); // suppress reply
7756                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7757                          }
7758                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7759                          return 1;
7760                      }
7761                 }
7762
7763                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7764                 if(gameInfo.variant == VariantXiangqi ?
7765                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7766                  : nrW + nrB == 4 &&
7767                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7768                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7769                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7770                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7771                    ) ) {
7772                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7773                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7774                           if(engineOpponent) {
7775                             SendToProgram("force\n", engineOpponent); // suppress reply
7776                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7777                           }
7778                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7779                           return 1;
7780                      }
7781                 } else moveCount = 6;
7782             }
7783
7784         // Repetition draws and 50-move rule can be applied independently of legality testing
7785
7786                 /* Check for rep-draws */
7787                 count = 0;
7788                 for(k = forwardMostMove-2;
7789                     k>=backwardMostMove && k>=forwardMostMove-100 &&
7790                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7791                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7792                     k-=2)
7793                 {   int rights=0;
7794                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7795                         /* compare castling rights */
7796                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7797                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7798                                 rights++; /* King lost rights, while rook still had them */
7799                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7800                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7801                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7802                                    rights++; /* but at least one rook lost them */
7803                         }
7804                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7805                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7806                                 rights++;
7807                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7808                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7809                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7810                                    rights++;
7811                         }
7812                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7813                             && appData.drawRepeats > 1) {
7814                              /* adjudicate after user-specified nr of repeats */
7815                              int result = GameIsDrawn;
7816                              char *details = "XBoard adjudication: repetition draw";
7817                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7818                                 // [HGM] xiangqi: check for forbidden perpetuals
7819                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7820                                 for(m=forwardMostMove; m>k; m-=2) {
7821                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7822                                         ourPerpetual = 0; // the current mover did not always check
7823                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7824                                         hisPerpetual = 0; // the opponent did not always check
7825                                 }
7826                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7827                                                                         ourPerpetual, hisPerpetual);
7828                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7829                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7830                                     details = "Xboard adjudication: perpetual checking";
7831                                 } else
7832                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7833                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7834                                 } else
7835                                 // Now check for perpetual chases
7836                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7837                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7838                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7839                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7840                                         static char resdet[MSG_SIZ];
7841                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7842                                         details = resdet;
7843                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
7844                                     } else
7845                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7846                                         break; // Abort repetition-checking loop.
7847                                 }
7848                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7849                              }
7850                              if(engineOpponent) {
7851                                SendToProgram("force\n", engineOpponent); // suppress reply
7852                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7853                              }
7854                              GameEnds( result, details, GE_XBOARD );
7855                              return 1;
7856                         }
7857                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7858                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7859                     }
7860                 }
7861
7862                 /* Now we test for 50-move draws. Determine ply count */
7863                 count = forwardMostMove;
7864                 /* look for last irreversble move */
7865                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7866                     count--;
7867                 /* if we hit starting position, add initial plies */
7868                 if( count == backwardMostMove )
7869                     count -= initialRulePlies;
7870                 count = forwardMostMove - count;
7871                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7872                         // adjust reversible move counter for checks in Xiangqi
7873                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7874                         if(i < backwardMostMove) i = backwardMostMove;
7875                         while(i <= forwardMostMove) {
7876                                 lastCheck = inCheck; // check evasion does not count
7877                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7878                                 if(inCheck || lastCheck) count--; // check does not count
7879                                 i++;
7880                         }
7881                 }
7882                 if( count >= 100)
7883                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7884                          /* this is used to judge if draw claims are legal */
7885                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7886                          if(engineOpponent) {
7887                            SendToProgram("force\n", engineOpponent); // suppress reply
7888                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7889                          }
7890                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7891                          return 1;
7892                 }
7893
7894                 /* if draw offer is pending, treat it as a draw claim
7895                  * when draw condition present, to allow engines a way to
7896                  * claim draws before making their move to avoid a race
7897                  * condition occurring after their move
7898                  */
7899                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7900                          char *p = NULL;
7901                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7902                              p = "Draw claim: 50-move rule";
7903                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7904                              p = "Draw claim: 3-fold repetition";
7905                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7906                              p = "Draw claim: insufficient mating material";
7907                          if( p != NULL && canAdjudicate) {
7908                              if(engineOpponent) {
7909                                SendToProgram("force\n", engineOpponent); // suppress reply
7910                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7911                              }
7912                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7913                              return 1;
7914                          }
7915                 }
7916
7917                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7918                     if(engineOpponent) {
7919                       SendToProgram("force\n", engineOpponent); // suppress reply
7920                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7921                     }
7922                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7923                     return 1;
7924                 }
7925         return 0;
7926 }
7927
7928 char *
7929 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
7930 {   // [HGM] book: this routine intercepts moves to simulate book replies
7931     char *bookHit = NULL;
7932
7933     //first determine if the incoming move brings opponent into his book
7934     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7935         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7936     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7937     if(bookHit != NULL && !cps->bookSuspend) {
7938         // make sure opponent is not going to reply after receiving move to book position
7939         SendToProgram("force\n", cps);
7940         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7941     }
7942     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7943     // now arrange restart after book miss
7944     if(bookHit) {
7945         // after a book hit we never send 'go', and the code after the call to this routine
7946         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7947         char buf[MSG_SIZ], *move = bookHit;
7948         if(cps->useSAN) {
7949             int fromX, fromY, toX, toY;
7950             char promoChar;
7951             ChessMove moveType;
7952             move = buf + 30;
7953             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
7954                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7955                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7956                                     PosFlags(forwardMostMove),
7957                                     fromY, fromX, toY, toX, promoChar, move);
7958             } else {
7959                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
7960                 bookHit = NULL;
7961             }
7962         }
7963         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
7964         SendToProgram(buf, cps);
7965         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7966     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7967         SendToProgram("go\n", cps);
7968         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7969     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7970         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7971             SendToProgram("go\n", cps);
7972         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7973     }
7974     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7975 }
7976
7977 int
7978 LoadError (char *errmess, ChessProgramState *cps)
7979 {   // unloads engine and switches back to -ncp mode if it was first
7980     if(cps->initDone) return FALSE;
7981     cps->isr = NULL; // this should suppress further error popups from breaking pipes
7982     DestroyChildProcess(cps->pr, 9 ); // just to be sure
7983     cps->pr = NoProc; 
7984     if(cps == &first) {
7985         appData.noChessProgram = TRUE;
7986         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
7987         gameMode = BeginningOfGame; ModeHighlight();
7988         SetNCPMode();
7989     }
7990     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
7991     DisplayMessage("", ""); // erase waiting message
7992     if(errmess) DisplayError(errmess, 0); // announce reason, if given
7993     return TRUE;
7994 }
7995
7996 char *savedMessage;
7997 ChessProgramState *savedState;
7998 void
7999 DeferredBookMove (void)
8000 {
8001         if(savedState->lastPing != savedState->lastPong)
8002                     ScheduleDelayedEvent(DeferredBookMove, 10);
8003         else
8004         HandleMachineMove(savedMessage, savedState);
8005 }
8006
8007 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8008
8009 void
8010 HandleMachineMove (char *message, ChessProgramState *cps)
8011 {
8012     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8013     char realname[MSG_SIZ];
8014     int fromX, fromY, toX, toY;
8015     ChessMove moveType;
8016     char promoChar;
8017     char *p, *pv=buf1;
8018     int machineWhite, oldError;
8019     char *bookHit;
8020
8021     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8022         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8023         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8024             DisplayError(_("Invalid pairing from pairing engine"), 0);
8025             return;
8026         }
8027         pairingReceived = 1;
8028         NextMatchGame();
8029         return; // Skim the pairing messages here.
8030     }
8031
8032     oldError = cps->userError; cps->userError = 0;
8033
8034 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8035     /*
8036      * Kludge to ignore BEL characters
8037      */
8038     while (*message == '\007') message++;
8039
8040     /*
8041      * [HGM] engine debug message: ignore lines starting with '#' character
8042      */
8043     if(cps->debug && *message == '#') return;
8044
8045     /*
8046      * Look for book output
8047      */
8048     if (cps == &first && bookRequested) {
8049         if (message[0] == '\t' || message[0] == ' ') {
8050             /* Part of the book output is here; append it */
8051             strcat(bookOutput, message);
8052             strcat(bookOutput, "  \n");
8053             return;
8054         } else if (bookOutput[0] != NULLCHAR) {
8055             /* All of book output has arrived; display it */
8056             char *p = bookOutput;
8057             while (*p != NULLCHAR) {
8058                 if (*p == '\t') *p = ' ';
8059                 p++;
8060             }
8061             DisplayInformation(bookOutput);
8062             bookRequested = FALSE;
8063             /* Fall through to parse the current output */
8064         }
8065     }
8066
8067     /*
8068      * Look for machine move.
8069      */
8070     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8071         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8072     {
8073         /* This method is only useful on engines that support ping */
8074         if (cps->lastPing != cps->lastPong) {
8075           if (gameMode == BeginningOfGame) {
8076             /* Extra move from before last new; ignore */
8077             if (appData.debugMode) {
8078                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8079             }
8080           } else {
8081             if (appData.debugMode) {
8082                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8083                         cps->which, gameMode);
8084             }
8085
8086             SendToProgram("undo\n", cps);
8087           }
8088           return;
8089         }
8090
8091         switch (gameMode) {
8092           case BeginningOfGame:
8093             /* Extra move from before last reset; ignore */
8094             if (appData.debugMode) {
8095                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8096             }
8097             return;
8098
8099           case EndOfGame:
8100           case IcsIdle:
8101           default:
8102             /* Extra move after we tried to stop.  The mode test is
8103                not a reliable way of detecting this problem, but it's
8104                the best we can do on engines that don't support ping.
8105             */
8106             if (appData.debugMode) {
8107                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8108                         cps->which, gameMode);
8109             }
8110             SendToProgram("undo\n", cps);
8111             return;
8112
8113           case MachinePlaysWhite:
8114           case IcsPlayingWhite:
8115             machineWhite = TRUE;
8116             break;
8117
8118           case MachinePlaysBlack:
8119           case IcsPlayingBlack:
8120             machineWhite = FALSE;
8121             break;
8122
8123           case TwoMachinesPlay:
8124             machineWhite = (cps->twoMachinesColor[0] == 'w');
8125             break;
8126         }
8127         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8128             if (appData.debugMode) {
8129                 fprintf(debugFP,
8130                         "Ignoring move out of turn by %s, gameMode %d"
8131                         ", forwardMost %d\n",
8132                         cps->which, gameMode, forwardMostMove);
8133             }
8134             return;
8135         }
8136
8137         if(cps->alphaRank) AlphaRank(machineMove, 4);
8138         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8139                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8140             /* Machine move could not be parsed; ignore it. */
8141           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8142                     machineMove, _(cps->which));
8143             DisplayError(buf1, 0);
8144             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
8145                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
8146             if (gameMode == TwoMachinesPlay) {
8147               GameEnds(machineWhite ? BlackWins : WhiteWins,
8148                        buf1, GE_XBOARD);
8149             }
8150             return;
8151         }
8152
8153         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8154         /* So we have to redo legality test with true e.p. status here,  */
8155         /* to make sure an illegal e.p. capture does not slip through,   */
8156         /* to cause a forfeit on a justified illegal-move complaint      */
8157         /* of the opponent.                                              */
8158         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8159            ChessMove moveType;
8160            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8161                              fromY, fromX, toY, toX, promoChar);
8162             if(moveType == IllegalMove) {
8163               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8164                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8165                 GameEnds(machineWhite ? BlackWins : WhiteWins,
8166                            buf1, GE_XBOARD);
8167                 return;
8168            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
8169            /* [HGM] Kludge to handle engines that send FRC-style castling
8170               when they shouldn't (like TSCP-Gothic) */
8171            switch(moveType) {
8172              case WhiteASideCastleFR:
8173              case BlackASideCastleFR:
8174                toX+=2;
8175                currentMoveString[2]++;
8176                break;
8177              case WhiteHSideCastleFR:
8178              case BlackHSideCastleFR:
8179                toX--;
8180                currentMoveString[2]--;
8181                break;
8182              default: ; // nothing to do, but suppresses warning of pedantic compilers
8183            }
8184         }
8185         hintRequested = FALSE;
8186         lastHint[0] = NULLCHAR;
8187         bookRequested = FALSE;
8188         /* Program may be pondering now */
8189         cps->maybeThinking = TRUE;
8190         if (cps->sendTime == 2) cps->sendTime = 1;
8191         if (cps->offeredDraw) cps->offeredDraw--;
8192
8193         /* [AS] Save move info*/
8194         pvInfoList[ forwardMostMove ].score = programStats.score;
8195         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8196         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8197
8198         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8199
8200         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8201         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8202             int count = 0;
8203
8204             while( count < adjudicateLossPlies ) {
8205                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8206
8207                 if( count & 1 ) {
8208                     score = -score; /* Flip score for winning side */
8209                 }
8210
8211                 if( score > adjudicateLossThreshold ) {
8212                     break;
8213                 }
8214
8215                 count++;
8216             }
8217
8218             if( count >= adjudicateLossPlies ) {
8219                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8220
8221                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8222                     "Xboard adjudication",
8223                     GE_XBOARD );
8224
8225                 return;
8226             }
8227         }
8228
8229         if(Adjudicate(cps)) {
8230             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8231             return; // [HGM] adjudicate: for all automatic game ends
8232         }
8233
8234 #if ZIPPY
8235         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8236             first.initDone) {
8237           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8238                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8239                 SendToICS("draw ");
8240                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8241           }
8242           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8243           ics_user_moved = 1;
8244           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8245                 char buf[3*MSG_SIZ];
8246
8247                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8248                         programStats.score / 100.,
8249                         programStats.depth,
8250                         programStats.time / 100.,
8251                         (unsigned int)programStats.nodes,
8252                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8253                         programStats.movelist);
8254                 SendToICS(buf);
8255 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8256           }
8257         }
8258 #endif
8259
8260         /* [AS] Clear stats for next move */
8261         ClearProgramStats();
8262         thinkOutput[0] = NULLCHAR;
8263         hiddenThinkOutputState = 0;
8264
8265         bookHit = NULL;
8266         if (gameMode == TwoMachinesPlay) {
8267             /* [HGM] relaying draw offers moved to after reception of move */
8268             /* and interpreting offer as claim if it brings draw condition */
8269             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8270                 SendToProgram("draw\n", cps->other);
8271             }
8272             if (cps->other->sendTime) {
8273                 SendTimeRemaining(cps->other,
8274                                   cps->other->twoMachinesColor[0] == 'w');
8275             }
8276             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8277             if (firstMove && !bookHit) {
8278                 firstMove = FALSE;
8279                 if (cps->other->useColors) {
8280                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8281                 }
8282                 SendToProgram("go\n", cps->other);
8283             }
8284             cps->other->maybeThinking = TRUE;
8285         }
8286
8287         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8288
8289         if (!pausing && appData.ringBellAfterMoves) {
8290             RingBell();
8291         }
8292
8293         /*
8294          * Reenable menu items that were disabled while
8295          * machine was thinking
8296          */
8297         if (gameMode != TwoMachinesPlay)
8298             SetUserThinkingEnables();
8299
8300         // [HGM] book: after book hit opponent has received move and is now in force mode
8301         // force the book reply into it, and then fake that it outputted this move by jumping
8302         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8303         if(bookHit) {
8304                 static char bookMove[MSG_SIZ]; // a bit generous?
8305
8306                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8307                 strcat(bookMove, bookHit);
8308                 message = bookMove;
8309                 cps = cps->other;
8310                 programStats.nodes = programStats.depth = programStats.time =
8311                 programStats.score = programStats.got_only_move = 0;
8312                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8313
8314                 if(cps->lastPing != cps->lastPong) {
8315                     savedMessage = message; // args for deferred call
8316                     savedState = cps;
8317                     ScheduleDelayedEvent(DeferredBookMove, 10);
8318                     return;
8319                 }
8320                 goto FakeBookMove;
8321         }
8322
8323         return;
8324     }
8325
8326     /* Set special modes for chess engines.  Later something general
8327      *  could be added here; for now there is just one kludge feature,
8328      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8329      *  when "xboard" is given as an interactive command.
8330      */
8331     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8332         cps->useSigint = FALSE;
8333         cps->useSigterm = FALSE;
8334     }
8335     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8336       ParseFeatures(message+8, cps);
8337       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8338     }
8339
8340     if ((!appData.testLegality || gameInfo.variant == VariantFairy) && 
8341                                         !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
8342       int dummy, s=6; char buf[MSG_SIZ];
8343       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8344       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8345       if(startedFromSetupPosition) return;
8346       ParseFEN(boards[0], &dummy, message+s);
8347       DrawPosition(TRUE, boards[0]);
8348       startedFromSetupPosition = TRUE;
8349       return;
8350     }
8351     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8352      * want this, I was asked to put it in, and obliged.
8353      */
8354     if (!strncmp(message, "setboard ", 9)) {
8355         Board initial_position;
8356
8357         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8358
8359         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8360             DisplayError(_("Bad FEN received from engine"), 0);
8361             return ;
8362         } else {
8363            Reset(TRUE, FALSE);
8364            CopyBoard(boards[0], initial_position);
8365            initialRulePlies = FENrulePlies;
8366            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8367            else gameMode = MachinePlaysBlack;
8368            DrawPosition(FALSE, boards[currentMove]);
8369         }
8370         return;
8371     }
8372
8373     /*
8374      * Look for communication commands
8375      */
8376     if (!strncmp(message, "telluser ", 9)) {
8377         if(message[9] == '\\' && message[10] == '\\')
8378             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8379         PlayTellSound();
8380         DisplayNote(message + 9);
8381         return;
8382     }
8383     if (!strncmp(message, "tellusererror ", 14)) {
8384         cps->userError = 1;
8385         if(message[14] == '\\' && message[15] == '\\')
8386             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8387         PlayTellSound();
8388         DisplayError(message + 14, 0);
8389         return;
8390     }
8391     if (!strncmp(message, "tellopponent ", 13)) {
8392       if (appData.icsActive) {
8393         if (loggedOn) {
8394           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8395           SendToICS(buf1);
8396         }
8397       } else {
8398         DisplayNote(message + 13);
8399       }
8400       return;
8401     }
8402     if (!strncmp(message, "tellothers ", 11)) {
8403       if (appData.icsActive) {
8404         if (loggedOn) {
8405           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8406           SendToICS(buf1);
8407         }
8408       }
8409       return;
8410     }
8411     if (!strncmp(message, "tellall ", 8)) {
8412       if (appData.icsActive) {
8413         if (loggedOn) {
8414           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8415           SendToICS(buf1);
8416         }
8417       } else {
8418         DisplayNote(message + 8);
8419       }
8420       return;
8421     }
8422     if (strncmp(message, "warning", 7) == 0) {
8423         /* Undocumented feature, use tellusererror in new code */
8424         DisplayError(message, 0);
8425         return;
8426     }
8427     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8428         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8429         strcat(realname, " query");
8430         AskQuestion(realname, buf2, buf1, cps->pr);
8431         return;
8432     }
8433     /* Commands from the engine directly to ICS.  We don't allow these to be
8434      *  sent until we are logged on. Crafty kibitzes have been known to
8435      *  interfere with the login process.
8436      */
8437     if (loggedOn) {
8438         if (!strncmp(message, "tellics ", 8)) {
8439             SendToICS(message + 8);
8440             SendToICS("\n");
8441             return;
8442         }
8443         if (!strncmp(message, "tellicsnoalias ", 15)) {
8444             SendToICS(ics_prefix);
8445             SendToICS(message + 15);
8446             SendToICS("\n");
8447             return;
8448         }
8449         /* The following are for backward compatibility only */
8450         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8451             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8452             SendToICS(ics_prefix);
8453             SendToICS(message);
8454             SendToICS("\n");
8455             return;
8456         }
8457     }
8458     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8459         return;
8460     }
8461     /*
8462      * If the move is illegal, cancel it and redraw the board.
8463      * Also deal with other error cases.  Matching is rather loose
8464      * here to accommodate engines written before the spec.
8465      */
8466     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8467         strncmp(message, "Error", 5) == 0) {
8468         if (StrStr(message, "name") ||
8469             StrStr(message, "rating") || StrStr(message, "?") ||
8470             StrStr(message, "result") || StrStr(message, "board") ||
8471             StrStr(message, "bk") || StrStr(message, "computer") ||
8472             StrStr(message, "variant") || StrStr(message, "hint") ||
8473             StrStr(message, "random") || StrStr(message, "depth") ||
8474             StrStr(message, "accepted")) {
8475             return;
8476         }
8477         if (StrStr(message, "protover")) {
8478           /* Program is responding to input, so it's apparently done
8479              initializing, and this error message indicates it is
8480              protocol version 1.  So we don't need to wait any longer
8481              for it to initialize and send feature commands. */
8482           FeatureDone(cps, 1);
8483           cps->protocolVersion = 1;
8484           return;
8485         }
8486         cps->maybeThinking = FALSE;
8487
8488         if (StrStr(message, "draw")) {
8489             /* Program doesn't have "draw" command */
8490             cps->sendDrawOffers = 0;
8491             return;
8492         }
8493         if (cps->sendTime != 1 &&
8494             (StrStr(message, "time") || StrStr(message, "otim"))) {
8495           /* Program apparently doesn't have "time" or "otim" command */
8496           cps->sendTime = 0;
8497           return;
8498         }
8499         if (StrStr(message, "analyze")) {
8500             cps->analysisSupport = FALSE;
8501             cps->analyzing = FALSE;
8502 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8503             EditGameEvent(); // [HGM] try to preserve loaded game
8504             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8505             DisplayError(buf2, 0);
8506             return;
8507         }
8508         if (StrStr(message, "(no matching move)st")) {
8509           /* Special kludge for GNU Chess 4 only */
8510           cps->stKludge = TRUE;
8511           SendTimeControl(cps, movesPerSession, timeControl,
8512                           timeIncrement, appData.searchDepth,
8513                           searchTime);
8514           return;
8515         }
8516         if (StrStr(message, "(no matching move)sd")) {
8517           /* Special kludge for GNU Chess 4 only */
8518           cps->sdKludge = TRUE;
8519           SendTimeControl(cps, movesPerSession, timeControl,
8520                           timeIncrement, appData.searchDepth,
8521                           searchTime);
8522           return;
8523         }
8524         if (!StrStr(message, "llegal")) {
8525             return;
8526         }
8527         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8528             gameMode == IcsIdle) return;
8529         if (forwardMostMove <= backwardMostMove) return;
8530         if (pausing) PauseEvent();
8531       if(appData.forceIllegal) {
8532             // [HGM] illegal: machine refused move; force position after move into it
8533           SendToProgram("force\n", cps);
8534           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8535                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8536                 // when black is to move, while there might be nothing on a2 or black
8537                 // might already have the move. So send the board as if white has the move.
8538                 // But first we must change the stm of the engine, as it refused the last move
8539                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8540                 if(WhiteOnMove(forwardMostMove)) {
8541                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8542                     SendBoard(cps, forwardMostMove); // kludgeless board
8543                 } else {
8544                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8545                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8546                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8547                 }
8548           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8549             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8550                  gameMode == TwoMachinesPlay)
8551               SendToProgram("go\n", cps);
8552             return;
8553       } else
8554         if (gameMode == PlayFromGameFile) {
8555             /* Stop reading this game file */
8556             gameMode = EditGame;
8557             ModeHighlight();
8558         }
8559         /* [HGM] illegal-move claim should forfeit game when Xboard */
8560         /* only passes fully legal moves                            */
8561         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8562             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8563                                 "False illegal-move claim", GE_XBOARD );
8564             return; // do not take back move we tested as valid
8565         }
8566         currentMove = forwardMostMove-1;
8567         DisplayMove(currentMove-1); /* before DisplayMoveError */
8568         SwitchClocks(forwardMostMove-1); // [HGM] race
8569         DisplayBothClocks();
8570         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8571                 parseList[currentMove], _(cps->which));
8572         DisplayMoveError(buf1);
8573         DrawPosition(FALSE, boards[currentMove]);
8574
8575         SetUserThinkingEnables();
8576         return;
8577     }
8578     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8579         /* Program has a broken "time" command that
8580            outputs a string not ending in newline.
8581            Don't use it. */
8582         cps->sendTime = 0;
8583     }
8584
8585     /*
8586      * If chess program startup fails, exit with an error message.
8587      * Attempts to recover here are futile. [HGM] Well, we try anyway
8588      */
8589     if ((StrStr(message, "unknown host") != NULL)
8590         || (StrStr(message, "No remote directory") != NULL)
8591         || (StrStr(message, "not found") != NULL)
8592         || (StrStr(message, "No such file") != NULL)
8593         || (StrStr(message, "can't alloc") != NULL)
8594         || (StrStr(message, "Permission denied") != NULL)) {
8595
8596         cps->maybeThinking = FALSE;
8597         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8598                 _(cps->which), cps->program, cps->host, message);
8599         RemoveInputSource(cps->isr);
8600         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8601             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
8602             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
8603         }
8604         return;
8605     }
8606
8607     /*
8608      * Look for hint output
8609      */
8610     if (sscanf(message, "Hint: %s", buf1) == 1) {
8611         if (cps == &first && hintRequested) {
8612             hintRequested = FALSE;
8613             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8614                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8615                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8616                                     PosFlags(forwardMostMove),
8617                                     fromY, fromX, toY, toX, promoChar, buf1);
8618                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8619                 DisplayInformation(buf2);
8620             } else {
8621                 /* Hint move could not be parsed!? */
8622               snprintf(buf2, sizeof(buf2),
8623                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8624                         buf1, _(cps->which));
8625                 DisplayError(buf2, 0);
8626             }
8627         } else {
8628           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8629         }
8630         return;
8631     }
8632
8633     /*
8634      * Ignore other messages if game is not in progress
8635      */
8636     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8637         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8638
8639     /*
8640      * look for win, lose, draw, or draw offer
8641      */
8642     if (strncmp(message, "1-0", 3) == 0) {
8643         char *p, *q, *r = "";
8644         p = strchr(message, '{');
8645         if (p) {
8646             q = strchr(p, '}');
8647             if (q) {
8648                 *q = NULLCHAR;
8649                 r = p + 1;
8650             }
8651         }
8652         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8653         return;
8654     } else if (strncmp(message, "0-1", 3) == 0) {
8655         char *p, *q, *r = "";
8656         p = strchr(message, '{');
8657         if (p) {
8658             q = strchr(p, '}');
8659             if (q) {
8660                 *q = NULLCHAR;
8661                 r = p + 1;
8662             }
8663         }
8664         /* Kludge for Arasan 4.1 bug */
8665         if (strcmp(r, "Black resigns") == 0) {
8666             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8667             return;
8668         }
8669         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8670         return;
8671     } else if (strncmp(message, "1/2", 3) == 0) {
8672         char *p, *q, *r = "";
8673         p = strchr(message, '{');
8674         if (p) {
8675             q = strchr(p, '}');
8676             if (q) {
8677                 *q = NULLCHAR;
8678                 r = p + 1;
8679             }
8680         }
8681
8682         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8683         return;
8684
8685     } else if (strncmp(message, "White resign", 12) == 0) {
8686         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8687         return;
8688     } else if (strncmp(message, "Black resign", 12) == 0) {
8689         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8690         return;
8691     } else if (strncmp(message, "White matches", 13) == 0 ||
8692                strncmp(message, "Black matches", 13) == 0   ) {
8693         /* [HGM] ignore GNUShogi noises */
8694         return;
8695     } else if (strncmp(message, "White", 5) == 0 &&
8696                message[5] != '(' &&
8697                StrStr(message, "Black") == NULL) {
8698         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8699         return;
8700     } else if (strncmp(message, "Black", 5) == 0 &&
8701                message[5] != '(') {
8702         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8703         return;
8704     } else if (strcmp(message, "resign") == 0 ||
8705                strcmp(message, "computer resigns") == 0) {
8706         switch (gameMode) {
8707           case MachinePlaysBlack:
8708           case IcsPlayingBlack:
8709             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8710             break;
8711           case MachinePlaysWhite:
8712           case IcsPlayingWhite:
8713             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8714             break;
8715           case TwoMachinesPlay:
8716             if (cps->twoMachinesColor[0] == 'w')
8717               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8718             else
8719               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8720             break;
8721           default:
8722             /* can't happen */
8723             break;
8724         }
8725         return;
8726     } else if (strncmp(message, "opponent mates", 14) == 0) {
8727         switch (gameMode) {
8728           case MachinePlaysBlack:
8729           case IcsPlayingBlack:
8730             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8731             break;
8732           case MachinePlaysWhite:
8733           case IcsPlayingWhite:
8734             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8735             break;
8736           case TwoMachinesPlay:
8737             if (cps->twoMachinesColor[0] == 'w')
8738               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8739             else
8740               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8741             break;
8742           default:
8743             /* can't happen */
8744             break;
8745         }
8746         return;
8747     } else if (strncmp(message, "computer mates", 14) == 0) {
8748         switch (gameMode) {
8749           case MachinePlaysBlack:
8750           case IcsPlayingBlack:
8751             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8752             break;
8753           case MachinePlaysWhite:
8754           case IcsPlayingWhite:
8755             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8756             break;
8757           case TwoMachinesPlay:
8758             if (cps->twoMachinesColor[0] == 'w')
8759               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8760             else
8761               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8762             break;
8763           default:
8764             /* can't happen */
8765             break;
8766         }
8767         return;
8768     } else if (strncmp(message, "checkmate", 9) == 0) {
8769         if (WhiteOnMove(forwardMostMove)) {
8770             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8771         } else {
8772             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8773         }
8774         return;
8775     } else if (strstr(message, "Draw") != NULL ||
8776                strstr(message, "game is a draw") != NULL) {
8777         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8778         return;
8779     } else if (strstr(message, "offer") != NULL &&
8780                strstr(message, "draw") != NULL) {
8781 #if ZIPPY
8782         if (appData.zippyPlay && first.initDone) {
8783             /* Relay offer to ICS */
8784             SendToICS(ics_prefix);
8785             SendToICS("draw\n");
8786         }
8787 #endif
8788         cps->offeredDraw = 2; /* valid until this engine moves twice */
8789         if (gameMode == TwoMachinesPlay) {
8790             if (cps->other->offeredDraw) {
8791                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8792             /* [HGM] in two-machine mode we delay relaying draw offer      */
8793             /* until after we also have move, to see if it is really claim */
8794             }
8795         } else if (gameMode == MachinePlaysWhite ||
8796                    gameMode == MachinePlaysBlack) {
8797           if (userOfferedDraw) {
8798             DisplayInformation(_("Machine accepts your draw offer"));
8799             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8800           } else {
8801             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8802           }
8803         }
8804     }
8805
8806
8807     /*
8808      * Look for thinking output
8809      */
8810     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8811           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8812                                 ) {
8813         int plylev, mvleft, mvtot, curscore, time;
8814         char mvname[MOVE_LEN];
8815         u64 nodes; // [DM]
8816         char plyext;
8817         int ignore = FALSE;
8818         int prefixHint = FALSE;
8819         mvname[0] = NULLCHAR;
8820
8821         switch (gameMode) {
8822           case MachinePlaysBlack:
8823           case IcsPlayingBlack:
8824             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8825             break;
8826           case MachinePlaysWhite:
8827           case IcsPlayingWhite:
8828             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8829             break;
8830           case AnalyzeMode:
8831           case AnalyzeFile:
8832             break;
8833           case IcsObserving: /* [DM] icsEngineAnalyze */
8834             if (!appData.icsEngineAnalyze) ignore = TRUE;
8835             break;
8836           case TwoMachinesPlay:
8837             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8838                 ignore = TRUE;
8839             }
8840             break;
8841           default:
8842             ignore = TRUE;
8843             break;
8844         }
8845
8846         if (!ignore) {
8847             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8848             buf1[0] = NULLCHAR;
8849             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8850                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8851
8852                 if (plyext != ' ' && plyext != '\t') {
8853                     time *= 100;
8854                 }
8855
8856                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8857                 if( cps->scoreIsAbsolute &&
8858                     ( gameMode == MachinePlaysBlack ||
8859                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8860                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8861                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8862                      !WhiteOnMove(currentMove)
8863                     ) )
8864                 {
8865                     curscore = -curscore;
8866                 }
8867
8868                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
8869
8870                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
8871                         char buf[MSG_SIZ];
8872                         FILE *f;
8873                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
8874                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
8875                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
8876                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
8877                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
8878                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
8879                                 fclose(f);
8880                         } else DisplayError(_("failed writing PV"), 0);
8881                 }
8882
8883                 tempStats.depth = plylev;
8884                 tempStats.nodes = nodes;
8885                 tempStats.time = time;
8886                 tempStats.score = curscore;
8887                 tempStats.got_only_move = 0;
8888
8889                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8890                         int ticklen;
8891
8892                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8893                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8894                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8895                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8896                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8897                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8898                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8899                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8900                 }
8901
8902                 /* Buffer overflow protection */
8903                 if (pv[0] != NULLCHAR) {
8904                     if (strlen(pv) >= sizeof(tempStats.movelist)
8905                         && appData.debugMode) {
8906                         fprintf(debugFP,
8907                                 "PV is too long; using the first %u bytes.\n",
8908                                 (unsigned) sizeof(tempStats.movelist) - 1);
8909                     }
8910
8911                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8912                 } else {
8913                     sprintf(tempStats.movelist, " no PV\n");
8914                 }
8915
8916                 if (tempStats.seen_stat) {
8917                     tempStats.ok_to_send = 1;
8918                 }
8919
8920                 if (strchr(tempStats.movelist, '(') != NULL) {
8921                     tempStats.line_is_book = 1;
8922                     tempStats.nr_moves = 0;
8923                     tempStats.moves_left = 0;
8924                 } else {
8925                     tempStats.line_is_book = 0;
8926                 }
8927
8928                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8929                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8930
8931                 SendProgramStatsToFrontend( cps, &tempStats );
8932
8933                 /*
8934                     [AS] Protect the thinkOutput buffer from overflow... this
8935                     is only useful if buf1 hasn't overflowed first!
8936                 */
8937                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8938                          plylev,
8939                          (gameMode == TwoMachinesPlay ?
8940                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8941                          ((double) curscore) / 100.0,
8942                          prefixHint ? lastHint : "",
8943                          prefixHint ? " " : "" );
8944
8945                 if( buf1[0] != NULLCHAR ) {
8946                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8947
8948                     if( strlen(pv) > max_len ) {
8949                         if( appData.debugMode) {
8950                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8951                         }
8952                         pv[max_len+1] = '\0';
8953                     }
8954
8955                     strcat( thinkOutput, pv);
8956                 }
8957
8958                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8959                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8960                     DisplayMove(currentMove - 1);
8961                 }
8962                 return;
8963
8964             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8965                 /* crafty (9.25+) says "(only move) <move>"
8966                  * if there is only 1 legal move
8967                  */
8968                 sscanf(p, "(only move) %s", buf1);
8969                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8970                 sprintf(programStats.movelist, "%s (only move)", buf1);
8971                 programStats.depth = 1;
8972                 programStats.nr_moves = 1;
8973                 programStats.moves_left = 1;
8974                 programStats.nodes = 1;
8975                 programStats.time = 1;
8976                 programStats.got_only_move = 1;
8977
8978                 /* Not really, but we also use this member to
8979                    mean "line isn't going to change" (Crafty
8980                    isn't searching, so stats won't change) */
8981                 programStats.line_is_book = 1;
8982
8983                 SendProgramStatsToFrontend( cps, &programStats );
8984
8985                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8986                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8987                     DisplayMove(currentMove - 1);
8988                 }
8989                 return;
8990             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8991                               &time, &nodes, &plylev, &mvleft,
8992                               &mvtot, mvname) >= 5) {
8993                 /* The stat01: line is from Crafty (9.29+) in response
8994                    to the "." command */
8995                 programStats.seen_stat = 1;
8996                 cps->maybeThinking = TRUE;
8997
8998                 if (programStats.got_only_move || !appData.periodicUpdates)
8999                   return;
9000
9001                 programStats.depth = plylev;
9002                 programStats.time = time;
9003                 programStats.nodes = nodes;
9004                 programStats.moves_left = mvleft;
9005                 programStats.nr_moves = mvtot;
9006                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9007                 programStats.ok_to_send = 1;
9008                 programStats.movelist[0] = '\0';
9009
9010                 SendProgramStatsToFrontend( cps, &programStats );
9011
9012                 return;
9013
9014             } else if (strncmp(message,"++",2) == 0) {
9015                 /* Crafty 9.29+ outputs this */
9016                 programStats.got_fail = 2;
9017                 return;
9018
9019             } else if (strncmp(message,"--",2) == 0) {
9020                 /* Crafty 9.29+ outputs this */
9021                 programStats.got_fail = 1;
9022                 return;
9023
9024             } else if (thinkOutput[0] != NULLCHAR &&
9025                        strncmp(message, "    ", 4) == 0) {
9026                 unsigned message_len;
9027
9028                 p = message;
9029                 while (*p && *p == ' ') p++;
9030
9031                 message_len = strlen( p );
9032
9033                 /* [AS] Avoid buffer overflow */
9034                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9035                     strcat(thinkOutput, " ");
9036                     strcat(thinkOutput, p);
9037                 }
9038
9039                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9040                     strcat(programStats.movelist, " ");
9041                     strcat(programStats.movelist, p);
9042                 }
9043
9044                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9045                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9046                     DisplayMove(currentMove - 1);
9047                 }
9048                 return;
9049             }
9050         }
9051         else {
9052             buf1[0] = NULLCHAR;
9053
9054             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9055                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9056             {
9057                 ChessProgramStats cpstats;
9058
9059                 if (plyext != ' ' && plyext != '\t') {
9060                     time *= 100;
9061                 }
9062
9063                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9064                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9065                     curscore = -curscore;
9066                 }
9067
9068                 cpstats.depth = plylev;
9069                 cpstats.nodes = nodes;
9070                 cpstats.time = time;
9071                 cpstats.score = curscore;
9072                 cpstats.got_only_move = 0;
9073                 cpstats.movelist[0] = '\0';
9074
9075                 if (buf1[0] != NULLCHAR) {
9076                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9077                 }
9078
9079                 cpstats.ok_to_send = 0;
9080                 cpstats.line_is_book = 0;
9081                 cpstats.nr_moves = 0;
9082                 cpstats.moves_left = 0;
9083
9084                 SendProgramStatsToFrontend( cps, &cpstats );
9085             }
9086         }
9087     }
9088 }
9089
9090
9091 /* Parse a game score from the character string "game", and
9092    record it as the history of the current game.  The game
9093    score is NOT assumed to start from the standard position.
9094    The display is not updated in any way.
9095    */
9096 void
9097 ParseGameHistory (char *game)
9098 {
9099     ChessMove moveType;
9100     int fromX, fromY, toX, toY, boardIndex;
9101     char promoChar;
9102     char *p, *q;
9103     char buf[MSG_SIZ];
9104
9105     if (appData.debugMode)
9106       fprintf(debugFP, "Parsing game history: %s\n", game);
9107
9108     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9109     gameInfo.site = StrSave(appData.icsHost);
9110     gameInfo.date = PGNDate();
9111     gameInfo.round = StrSave("-");
9112
9113     /* Parse out names of players */
9114     while (*game == ' ') game++;
9115     p = buf;
9116     while (*game != ' ') *p++ = *game++;
9117     *p = NULLCHAR;
9118     gameInfo.white = StrSave(buf);
9119     while (*game == ' ') game++;
9120     p = buf;
9121     while (*game != ' ' && *game != '\n') *p++ = *game++;
9122     *p = NULLCHAR;
9123     gameInfo.black = StrSave(buf);
9124
9125     /* Parse moves */
9126     boardIndex = blackPlaysFirst ? 1 : 0;
9127     yynewstr(game);
9128     for (;;) {
9129         yyboardindex = boardIndex;
9130         moveType = (ChessMove) Myylex();
9131         switch (moveType) {
9132           case IllegalMove:             /* maybe suicide chess, etc. */
9133   if (appData.debugMode) {
9134     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9135     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9136     setbuf(debugFP, NULL);
9137   }
9138           case WhitePromotion:
9139           case BlackPromotion:
9140           case WhiteNonPromotion:
9141           case BlackNonPromotion:
9142           case NormalMove:
9143           case WhiteCapturesEnPassant:
9144           case BlackCapturesEnPassant:
9145           case WhiteKingSideCastle:
9146           case WhiteQueenSideCastle:
9147           case BlackKingSideCastle:
9148           case BlackQueenSideCastle:
9149           case WhiteKingSideCastleWild:
9150           case WhiteQueenSideCastleWild:
9151           case BlackKingSideCastleWild:
9152           case BlackQueenSideCastleWild:
9153           /* PUSH Fabien */
9154           case WhiteHSideCastleFR:
9155           case WhiteASideCastleFR:
9156           case BlackHSideCastleFR:
9157           case BlackASideCastleFR:
9158           /* POP Fabien */
9159             fromX = currentMoveString[0] - AAA;
9160             fromY = currentMoveString[1] - ONE;
9161             toX = currentMoveString[2] - AAA;
9162             toY = currentMoveString[3] - ONE;
9163             promoChar = currentMoveString[4];
9164             break;
9165           case WhiteDrop:
9166           case BlackDrop:
9167             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9168             fromX = moveType == WhiteDrop ?
9169               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9170             (int) CharToPiece(ToLower(currentMoveString[0]));
9171             fromY = DROP_RANK;
9172             toX = currentMoveString[2] - AAA;
9173             toY = currentMoveString[3] - ONE;
9174             promoChar = NULLCHAR;
9175             break;
9176           case AmbiguousMove:
9177             /* bug? */
9178             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9179   if (appData.debugMode) {
9180     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9181     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9182     setbuf(debugFP, NULL);
9183   }
9184             DisplayError(buf, 0);
9185             return;
9186           case ImpossibleMove:
9187             /* bug? */
9188             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9189   if (appData.debugMode) {
9190     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9191     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9192     setbuf(debugFP, NULL);
9193   }
9194             DisplayError(buf, 0);
9195             return;
9196           case EndOfFile:
9197             if (boardIndex < backwardMostMove) {
9198                 /* Oops, gap.  How did that happen? */
9199                 DisplayError(_("Gap in move list"), 0);
9200                 return;
9201             }
9202             backwardMostMove =  blackPlaysFirst ? 1 : 0;
9203             if (boardIndex > forwardMostMove) {
9204                 forwardMostMove = boardIndex;
9205             }
9206             return;
9207           case ElapsedTime:
9208             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9209                 strcat(parseList[boardIndex-1], " ");
9210                 strcat(parseList[boardIndex-1], yy_text);
9211             }
9212             continue;
9213           case Comment:
9214           case PGNTag:
9215           case NAG:
9216           default:
9217             /* ignore */
9218             continue;
9219           case WhiteWins:
9220           case BlackWins:
9221           case GameIsDrawn:
9222           case GameUnfinished:
9223             if (gameMode == IcsExamining) {
9224                 if (boardIndex < backwardMostMove) {
9225                     /* Oops, gap.  How did that happen? */
9226                     return;
9227                 }
9228                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9229                 return;
9230             }
9231             gameInfo.result = moveType;
9232             p = strchr(yy_text, '{');
9233             if (p == NULL) p = strchr(yy_text, '(');
9234             if (p == NULL) {
9235                 p = yy_text;
9236                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9237             } else {
9238                 q = strchr(p, *p == '{' ? '}' : ')');
9239                 if (q != NULL) *q = NULLCHAR;
9240                 p++;
9241             }
9242             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9243             gameInfo.resultDetails = StrSave(p);
9244             continue;
9245         }
9246         if (boardIndex >= forwardMostMove &&
9247             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9248             backwardMostMove = blackPlaysFirst ? 1 : 0;
9249             return;
9250         }
9251         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9252                                  fromY, fromX, toY, toX, promoChar,
9253                                  parseList[boardIndex]);
9254         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9255         /* currentMoveString is set as a side-effect of yylex */
9256         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9257         strcat(moveList[boardIndex], "\n");
9258         boardIndex++;
9259         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9260         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9261           case MT_NONE:
9262           case MT_STALEMATE:
9263           default:
9264             break;
9265           case MT_CHECK:
9266             if(gameInfo.variant != VariantShogi)
9267                 strcat(parseList[boardIndex - 1], "+");
9268             break;
9269           case MT_CHECKMATE:
9270           case MT_STAINMATE:
9271             strcat(parseList[boardIndex - 1], "#");
9272             break;
9273         }
9274     }
9275 }
9276
9277
9278 /* Apply a move to the given board  */
9279 void
9280 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9281 {
9282   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9283   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9284
9285     /* [HGM] compute & store e.p. status and castling rights for new position */
9286     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9287
9288       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9289       oldEP = (signed char)board[EP_STATUS];
9290       board[EP_STATUS] = EP_NONE;
9291
9292   if (fromY == DROP_RANK) {
9293         /* must be first */
9294         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9295             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9296             return;
9297         }
9298         piece = board[toY][toX] = (ChessSquare) fromX;
9299   } else {
9300       int i;
9301
9302       if( board[toY][toX] != EmptySquare )
9303            board[EP_STATUS] = EP_CAPTURE;
9304
9305       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9306            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9307                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9308       } else
9309       if( board[fromY][fromX] == WhitePawn ) {
9310            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9311                board[EP_STATUS] = EP_PAWN_MOVE;
9312            if( toY-fromY==2) {
9313                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9314                         gameInfo.variant != VariantBerolina || toX < fromX)
9315                       board[EP_STATUS] = toX | berolina;
9316                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9317                         gameInfo.variant != VariantBerolina || toX > fromX)
9318                       board[EP_STATUS] = toX;
9319            }
9320       } else
9321       if( board[fromY][fromX] == BlackPawn ) {
9322            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9323                board[EP_STATUS] = EP_PAWN_MOVE;
9324            if( toY-fromY== -2) {
9325                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9326                         gameInfo.variant != VariantBerolina || toX < fromX)
9327                       board[EP_STATUS] = toX | berolina;
9328                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9329                         gameInfo.variant != VariantBerolina || toX > fromX)
9330                       board[EP_STATUS] = toX;
9331            }
9332        }
9333
9334        for(i=0; i<nrCastlingRights; i++) {
9335            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9336               board[CASTLING][i] == toX   && castlingRank[i] == toY
9337              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9338        }
9339
9340        if(gameInfo.variant == VariantSChess) { // update virginity
9341            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
9342            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
9343            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
9344            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
9345        }
9346
9347      if (fromX == toX && fromY == toY) return;
9348
9349      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9350      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9351      if(gameInfo.variant == VariantKnightmate)
9352          king += (int) WhiteUnicorn - (int) WhiteKing;
9353
9354     /* Code added by Tord: */
9355     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9356     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9357         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9358       board[fromY][fromX] = EmptySquare;
9359       board[toY][toX] = EmptySquare;
9360       if((toX > fromX) != (piece == WhiteRook)) {
9361         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9362       } else {
9363         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9364       }
9365     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9366                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9367       board[fromY][fromX] = EmptySquare;
9368       board[toY][toX] = EmptySquare;
9369       if((toX > fromX) != (piece == BlackRook)) {
9370         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9371       } else {
9372         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9373       }
9374     /* End of code added by Tord */
9375
9376     } else if (board[fromY][fromX] == king
9377         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9378         && toY == fromY && toX > fromX+1) {
9379         board[fromY][fromX] = EmptySquare;
9380         board[toY][toX] = king;
9381         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9382         board[fromY][BOARD_RGHT-1] = EmptySquare;
9383     } else if (board[fromY][fromX] == king
9384         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9385                && toY == fromY && toX < fromX-1) {
9386         board[fromY][fromX] = EmptySquare;
9387         board[toY][toX] = king;
9388         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9389         board[fromY][BOARD_LEFT] = EmptySquare;
9390     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9391                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9392                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9393                ) {
9394         /* white pawn promotion */
9395         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9396         if(gameInfo.variant==VariantBughouse ||
9397            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9398             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9399         board[fromY][fromX] = EmptySquare;
9400     } else if ((fromY >= BOARD_HEIGHT>>1)
9401                && (toX != fromX)
9402                && gameInfo.variant != VariantXiangqi
9403                && gameInfo.variant != VariantBerolina
9404                && (board[fromY][fromX] == WhitePawn)
9405                && (board[toY][toX] == EmptySquare)) {
9406         board[fromY][fromX] = EmptySquare;
9407         board[toY][toX] = WhitePawn;
9408         captured = board[toY - 1][toX];
9409         board[toY - 1][toX] = EmptySquare;
9410     } else if ((fromY == BOARD_HEIGHT-4)
9411                && (toX == fromX)
9412                && gameInfo.variant == VariantBerolina
9413                && (board[fromY][fromX] == WhitePawn)
9414                && (board[toY][toX] == EmptySquare)) {
9415         board[fromY][fromX] = EmptySquare;
9416         board[toY][toX] = WhitePawn;
9417         if(oldEP & EP_BEROLIN_A) {
9418                 captured = board[fromY][fromX-1];
9419                 board[fromY][fromX-1] = EmptySquare;
9420         }else{  captured = board[fromY][fromX+1];
9421                 board[fromY][fromX+1] = EmptySquare;
9422         }
9423     } else if (board[fromY][fromX] == king
9424         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9425                && toY == fromY && toX > fromX+1) {
9426         board[fromY][fromX] = EmptySquare;
9427         board[toY][toX] = king;
9428         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9429         board[fromY][BOARD_RGHT-1] = EmptySquare;
9430     } else if (board[fromY][fromX] == king
9431         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9432                && toY == fromY && toX < fromX-1) {
9433         board[fromY][fromX] = EmptySquare;
9434         board[toY][toX] = king;
9435         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9436         board[fromY][BOARD_LEFT] = EmptySquare;
9437     } else if (fromY == 7 && fromX == 3
9438                && board[fromY][fromX] == BlackKing
9439                && toY == 7 && toX == 5) {
9440         board[fromY][fromX] = EmptySquare;
9441         board[toY][toX] = BlackKing;
9442         board[fromY][7] = EmptySquare;
9443         board[toY][4] = BlackRook;
9444     } else if (fromY == 7 && fromX == 3
9445                && board[fromY][fromX] == BlackKing
9446                && toY == 7 && toX == 1) {
9447         board[fromY][fromX] = EmptySquare;
9448         board[toY][toX] = BlackKing;
9449         board[fromY][0] = EmptySquare;
9450         board[toY][2] = BlackRook;
9451     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9452                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9453                && toY < promoRank && promoChar
9454                ) {
9455         /* black pawn promotion */
9456         board[toY][toX] = CharToPiece(ToLower(promoChar));
9457         if(gameInfo.variant==VariantBughouse ||
9458            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9459             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9460         board[fromY][fromX] = EmptySquare;
9461     } else if ((fromY < BOARD_HEIGHT>>1)
9462                && (toX != fromX)
9463                && gameInfo.variant != VariantXiangqi
9464                && gameInfo.variant != VariantBerolina
9465                && (board[fromY][fromX] == BlackPawn)
9466                && (board[toY][toX] == EmptySquare)) {
9467         board[fromY][fromX] = EmptySquare;
9468         board[toY][toX] = BlackPawn;
9469         captured = board[toY + 1][toX];
9470         board[toY + 1][toX] = EmptySquare;
9471     } else if ((fromY == 3)
9472                && (toX == fromX)
9473                && gameInfo.variant == VariantBerolina
9474                && (board[fromY][fromX] == BlackPawn)
9475                && (board[toY][toX] == EmptySquare)) {
9476         board[fromY][fromX] = EmptySquare;
9477         board[toY][toX] = BlackPawn;
9478         if(oldEP & EP_BEROLIN_A) {
9479                 captured = board[fromY][fromX-1];
9480                 board[fromY][fromX-1] = EmptySquare;
9481         }else{  captured = board[fromY][fromX+1];
9482                 board[fromY][fromX+1] = EmptySquare;
9483         }
9484     } else {
9485         board[toY][toX] = board[fromY][fromX];
9486         board[fromY][fromX] = EmptySquare;
9487     }
9488   }
9489
9490     if (gameInfo.holdingsWidth != 0) {
9491
9492       /* !!A lot more code needs to be written to support holdings  */
9493       /* [HGM] OK, so I have written it. Holdings are stored in the */
9494       /* penultimate board files, so they are automaticlly stored   */
9495       /* in the game history.                                       */
9496       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9497                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9498         /* Delete from holdings, by decreasing count */
9499         /* and erasing image if necessary            */
9500         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9501         if(p < (int) BlackPawn) { /* white drop */
9502              p -= (int)WhitePawn;
9503                  p = PieceToNumber((ChessSquare)p);
9504              if(p >= gameInfo.holdingsSize) p = 0;
9505              if(--board[p][BOARD_WIDTH-2] <= 0)
9506                   board[p][BOARD_WIDTH-1] = EmptySquare;
9507              if((int)board[p][BOARD_WIDTH-2] < 0)
9508                         board[p][BOARD_WIDTH-2] = 0;
9509         } else {                  /* black drop */
9510              p -= (int)BlackPawn;
9511                  p = PieceToNumber((ChessSquare)p);
9512              if(p >= gameInfo.holdingsSize) p = 0;
9513              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9514                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9515              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9516                         board[BOARD_HEIGHT-1-p][1] = 0;
9517         }
9518       }
9519       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9520           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9521         /* [HGM] holdings: Add to holdings, if holdings exist */
9522         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9523                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9524                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9525         }
9526         p = (int) captured;
9527         if (p >= (int) BlackPawn) {
9528           p -= (int)BlackPawn;
9529           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9530                   /* in Shogi restore piece to its original  first */
9531                   captured = (ChessSquare) (DEMOTED captured);
9532                   p = DEMOTED p;
9533           }
9534           p = PieceToNumber((ChessSquare)p);
9535           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9536           board[p][BOARD_WIDTH-2]++;
9537           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9538         } else {
9539           p -= (int)WhitePawn;
9540           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9541                   captured = (ChessSquare) (DEMOTED captured);
9542                   p = DEMOTED p;
9543           }
9544           p = PieceToNumber((ChessSquare)p);
9545           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9546           board[BOARD_HEIGHT-1-p][1]++;
9547           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9548         }
9549       }
9550     } else if (gameInfo.variant == VariantAtomic) {
9551       if (captured != EmptySquare) {
9552         int y, x;
9553         for (y = toY-1; y <= toY+1; y++) {
9554           for (x = toX-1; x <= toX+1; x++) {
9555             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9556                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9557               board[y][x] = EmptySquare;
9558             }
9559           }
9560         }
9561         board[toY][toX] = EmptySquare;
9562       }
9563     }
9564     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9565         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9566     } else
9567     if(promoChar == '+') {
9568         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
9569         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9570     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9571         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9572         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
9573            && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
9574         board[toY][toX] = newPiece;
9575     }
9576     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9577                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9578         // [HGM] superchess: take promotion piece out of holdings
9579         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9580         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9581             if(!--board[k][BOARD_WIDTH-2])
9582                 board[k][BOARD_WIDTH-1] = EmptySquare;
9583         } else {
9584             if(!--board[BOARD_HEIGHT-1-k][1])
9585                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9586         }
9587     }
9588
9589 }
9590
9591 /* Updates forwardMostMove */
9592 void
9593 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
9594 {
9595 //    forwardMostMove++; // [HGM] bare: moved downstream
9596
9597     (void) CoordsToAlgebraic(boards[forwardMostMove],
9598                              PosFlags(forwardMostMove),
9599                              fromY, fromX, toY, toX, promoChar,
9600                              parseList[forwardMostMove]);
9601
9602     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9603         int timeLeft; static int lastLoadFlag=0; int king, piece;
9604         piece = boards[forwardMostMove][fromY][fromX];
9605         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9606         if(gameInfo.variant == VariantKnightmate)
9607             king += (int) WhiteUnicorn - (int) WhiteKing;
9608         if(forwardMostMove == 0) {
9609             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9610                 fprintf(serverMoves, "%s;", UserName());
9611             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9612                 fprintf(serverMoves, "%s;", second.tidy);
9613             fprintf(serverMoves, "%s;", first.tidy);
9614             if(gameMode == MachinePlaysWhite)
9615                 fprintf(serverMoves, "%s;", UserName());
9616             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9617                 fprintf(serverMoves, "%s;", second.tidy);
9618         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9619         lastLoadFlag = loadFlag;
9620         // print base move
9621         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9622         // print castling suffix
9623         if( toY == fromY && piece == king ) {
9624             if(toX-fromX > 1)
9625                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9626             if(fromX-toX >1)
9627                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9628         }
9629         // e.p. suffix
9630         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9631              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9632              boards[forwardMostMove][toY][toX] == EmptySquare
9633              && fromX != toX && fromY != toY)
9634                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9635         // promotion suffix
9636         if(promoChar != NULLCHAR) {
9637             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
9638                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
9639                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
9640             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
9641         }
9642         if(!loadFlag) {
9643                 char buf[MOVE_LEN*2], *p; int len;
9644             fprintf(serverMoves, "/%d/%d",
9645                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9646             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9647             else                      timeLeft = blackTimeRemaining/1000;
9648             fprintf(serverMoves, "/%d", timeLeft);
9649                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
9650                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
9651                 if(p = strchr(buf, '=')) *p = NULLCHAR;
9652                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
9653             fprintf(serverMoves, "/%s", buf);
9654         }
9655         fflush(serverMoves);
9656     }
9657
9658     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
9659         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
9660       return;
9661     }
9662     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9663     if (commentList[forwardMostMove+1] != NULL) {
9664         free(commentList[forwardMostMove+1]);
9665         commentList[forwardMostMove+1] = NULL;
9666     }
9667     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9668     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9669     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9670     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9671     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9672     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9673     adjustedClock = FALSE;
9674     gameInfo.result = GameUnfinished;
9675     if (gameInfo.resultDetails != NULL) {
9676         free(gameInfo.resultDetails);
9677         gameInfo.resultDetails = NULL;
9678     }
9679     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9680                               moveList[forwardMostMove - 1]);
9681     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9682       case MT_NONE:
9683       case MT_STALEMATE:
9684       default:
9685         break;
9686       case MT_CHECK:
9687         if(gameInfo.variant != VariantShogi)
9688             strcat(parseList[forwardMostMove - 1], "+");
9689         break;
9690       case MT_CHECKMATE:
9691       case MT_STAINMATE:
9692         strcat(parseList[forwardMostMove - 1], "#");
9693         break;
9694     }
9695
9696 }
9697
9698 /* Updates currentMove if not pausing */
9699 void
9700 ShowMove (int fromX, int fromY, int toX, int toY)
9701 {
9702     int instant = (gameMode == PlayFromGameFile) ?
9703         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9704     if(appData.noGUI) return;
9705     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9706         if (!instant) {
9707             if (forwardMostMove == currentMove + 1) {
9708                 AnimateMove(boards[forwardMostMove - 1],
9709                             fromX, fromY, toX, toY);
9710             }
9711             if (appData.highlightLastMove) {
9712                 SetHighlights(fromX, fromY, toX, toY);
9713             }
9714         }
9715         currentMove = forwardMostMove;
9716     }
9717
9718     if (instant) return;
9719
9720     DisplayMove(currentMove - 1);
9721     DrawPosition(FALSE, boards[currentMove]);
9722     DisplayBothClocks();
9723     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9724 }
9725
9726 void
9727 SendEgtPath (ChessProgramState *cps)
9728 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9729         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9730
9731         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9732
9733         while(*p) {
9734             char c, *q = name+1, *r, *s;
9735
9736             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9737             while(*p && *p != ',') *q++ = *p++;
9738             *q++ = ':'; *q = 0;
9739             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9740                 strcmp(name, ",nalimov:") == 0 ) {
9741                 // take nalimov path from the menu-changeable option first, if it is defined
9742               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9743                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9744             } else
9745             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9746                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9747                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9748                 s = r = StrStr(s, ":") + 1; // beginning of path info
9749                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9750                 c = *r; *r = 0;             // temporarily null-terminate path info
9751                     *--q = 0;               // strip of trailig ':' from name
9752                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9753                 *r = c;
9754                 SendToProgram(buf,cps);     // send egtbpath command for this format
9755             }
9756             if(*p == ',') p++; // read away comma to position for next format name
9757         }
9758 }
9759
9760 void
9761 InitChessProgram (ChessProgramState *cps, int setup)
9762 /* setup needed to setup FRC opening position */
9763 {
9764     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9765     if (appData.noChessProgram) return;
9766     hintRequested = FALSE;
9767     bookRequested = FALSE;
9768
9769     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
9770     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9771     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9772     if(cps->memSize) { /* [HGM] memory */
9773       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9774         SendToProgram(buf, cps);
9775     }
9776     SendEgtPath(cps); /* [HGM] EGT */
9777     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9778       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9779         SendToProgram(buf, cps);
9780     }
9781
9782     SendToProgram(cps->initString, cps);
9783     if (gameInfo.variant != VariantNormal &&
9784         gameInfo.variant != VariantLoadable
9785         /* [HGM] also send variant if board size non-standard */
9786         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9787                                             ) {
9788       char *v = VariantName(gameInfo.variant);
9789       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9790         /* [HGM] in protocol 1 we have to assume all variants valid */
9791         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9792         DisplayFatalError(buf, 0, 1);
9793         return;
9794       }
9795
9796       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9797       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9798       if( gameInfo.variant == VariantXiangqi )
9799            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9800       if( gameInfo.variant == VariantShogi )
9801            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9802       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9803            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9804       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9805           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9806            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9807       if( gameInfo.variant == VariantCourier )
9808            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9809       if( gameInfo.variant == VariantSuper )
9810            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9811       if( gameInfo.variant == VariantGreat )
9812            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9813       if( gameInfo.variant == VariantSChess )
9814            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9815       if( gameInfo.variant == VariantGrand )
9816            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
9817
9818       if(overruled) {
9819         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9820                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9821            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9822            if(StrStr(cps->variants, b) == NULL) {
9823                // specific sized variant not known, check if general sizing allowed
9824                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9825                    if(StrStr(cps->variants, "boardsize") == NULL) {
9826                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9827                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9828                        DisplayFatalError(buf, 0, 1);
9829                        return;
9830                    }
9831                    /* [HGM] here we really should compare with the maximum supported board size */
9832                }
9833            }
9834       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9835       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9836       SendToProgram(buf, cps);
9837     }
9838     currentlyInitializedVariant = gameInfo.variant;
9839
9840     /* [HGM] send opening position in FRC to first engine */
9841     if(setup) {
9842           SendToProgram("force\n", cps);
9843           SendBoard(cps, 0);
9844           /* engine is now in force mode! Set flag to wake it up after first move. */
9845           setboardSpoiledMachineBlack = 1;
9846     }
9847
9848     if (cps->sendICS) {
9849       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9850       SendToProgram(buf, cps);
9851     }
9852     cps->maybeThinking = FALSE;
9853     cps->offeredDraw = 0;
9854     if (!appData.icsActive) {
9855         SendTimeControl(cps, movesPerSession, timeControl,
9856                         timeIncrement, appData.searchDepth,
9857                         searchTime);
9858     }
9859     if (appData.showThinking
9860         // [HGM] thinking: four options require thinking output to be sent
9861         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9862                                 ) {
9863         SendToProgram("post\n", cps);
9864     }
9865     SendToProgram("hard\n", cps);
9866     if (!appData.ponderNextMove) {
9867         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9868            it without being sure what state we are in first.  "hard"
9869            is not a toggle, so that one is OK.
9870          */
9871         SendToProgram("easy\n", cps);
9872     }
9873     if (cps->usePing) {
9874       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9875       SendToProgram(buf, cps);
9876     }
9877     cps->initDone = TRUE;
9878     ClearEngineOutputPane(cps == &second);
9879 }
9880
9881
9882 void
9883 StartChessProgram (ChessProgramState *cps)
9884 {
9885     char buf[MSG_SIZ];
9886     int err;
9887
9888     if (appData.noChessProgram) return;
9889     cps->initDone = FALSE;
9890
9891     if (strcmp(cps->host, "localhost") == 0) {
9892         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9893     } else if (*appData.remoteShell == NULLCHAR) {
9894         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9895     } else {
9896         if (*appData.remoteUser == NULLCHAR) {
9897           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9898                     cps->program);
9899         } else {
9900           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9901                     cps->host, appData.remoteUser, cps->program);
9902         }
9903         err = StartChildProcess(buf, "", &cps->pr);
9904     }
9905
9906     if (err != 0) {
9907       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9908         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
9909         if(cps != &first) return;
9910         appData.noChessProgram = TRUE;
9911         ThawUI();
9912         SetNCPMode();
9913 //      DisplayFatalError(buf, err, 1);
9914 //      cps->pr = NoProc;
9915 //      cps->isr = NULL;
9916         return;
9917     }
9918
9919     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9920     if (cps->protocolVersion > 1) {
9921       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9922       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9923       cps->comboCnt = 0;  //                and values of combo boxes
9924       SendToProgram(buf, cps);
9925     } else {
9926       SendToProgram("xboard\n", cps);
9927     }
9928 }
9929
9930 void
9931 TwoMachinesEventIfReady P((void))
9932 {
9933   static int curMess = 0;
9934   if (first.lastPing != first.lastPong) {
9935     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9936     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9937     return;
9938   }
9939   if (second.lastPing != second.lastPong) {
9940     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9941     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9942     return;
9943   }
9944   DisplayMessage("", ""); curMess = 0;
9945   ThawUI();
9946   TwoMachinesEvent();
9947 }
9948
9949 char *
9950 MakeName (char *template)
9951 {
9952     time_t clock;
9953     struct tm *tm;
9954     static char buf[MSG_SIZ];
9955     char *p = buf;
9956     int i;
9957
9958     clock = time((time_t *)NULL);
9959     tm = localtime(&clock);
9960
9961     while(*p++ = *template++) if(p[-1] == '%') {
9962         switch(*template++) {
9963           case 0:   *p = 0; return buf;
9964           case 'Y': i = tm->tm_year+1900; break;
9965           case 'y': i = tm->tm_year-100; break;
9966           case 'M': i = tm->tm_mon+1; break;
9967           case 'd': i = tm->tm_mday; break;
9968           case 'h': i = tm->tm_hour; break;
9969           case 'm': i = tm->tm_min; break;
9970           case 's': i = tm->tm_sec; break;
9971           default:  i = 0;
9972         }
9973         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
9974     }
9975     return buf;
9976 }
9977
9978 int
9979 CountPlayers (char *p)
9980 {
9981     int n = 0;
9982     while(p = strchr(p, '\n')) p++, n++; // count participants
9983     return n;
9984 }
9985
9986 FILE *
9987 WriteTourneyFile (char *results, FILE *f)
9988 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
9989     if(f == NULL) f = fopen(appData.tourneyFile, "w");
9990     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
9991         // create a file with tournament description
9992         fprintf(f, "-participants {%s}\n", appData.participants);
9993         fprintf(f, "-seedBase %d\n", appData.seedBase);
9994         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9995         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9996         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9997         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9998         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9999         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10000         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10001         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10002         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10003         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10004         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10005         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10006         if(searchTime > 0)
10007                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10008         else {
10009                 fprintf(f, "-mps %d\n", appData.movesPerSession);
10010                 fprintf(f, "-tc %s\n", appData.timeControl);
10011                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10012         }
10013         fprintf(f, "-results \"%s\"\n", results);
10014     }
10015     return f;
10016 }
10017
10018 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10019
10020 void
10021 Substitute (char *participants, int expunge)
10022 {
10023     int i, changed, changes=0, nPlayers=0;
10024     char *p, *q, *r, buf[MSG_SIZ];
10025     if(participants == NULL) return;
10026     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10027     r = p = participants; q = appData.participants;
10028     while(*p && *p == *q) {
10029         if(*p == '\n') r = p+1, nPlayers++;
10030         p++; q++;
10031     }
10032     if(*p) { // difference
10033         while(*p && *p++ != '\n');
10034         while(*q && *q++ != '\n');
10035       changed = nPlayers;
10036         changes = 1 + (strcmp(p, q) != 0);
10037     }
10038     if(changes == 1) { // a single engine mnemonic was changed
10039         q = r; while(*q) nPlayers += (*q++ == '\n');
10040         p = buf; while(*r && (*p = *r++) != '\n') p++;
10041         *p = NULLCHAR;
10042         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10043         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10044         if(mnemonic[i]) { // The substitute is valid
10045             FILE *f;
10046             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10047                 flock(fileno(f), LOCK_EX);
10048                 ParseArgsFromFile(f);
10049                 fseek(f, 0, SEEK_SET);
10050                 FREE(appData.participants); appData.participants = participants;
10051                 if(expunge) { // erase results of replaced engine
10052                     int len = strlen(appData.results), w, b, dummy;
10053                     for(i=0; i<len; i++) {
10054                         Pairing(i, nPlayers, &w, &b, &dummy);
10055                         if((w == changed || b == changed) && appData.results[i] == '*') {
10056                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10057                             fclose(f);
10058                             return;
10059                         }
10060                     }
10061                     for(i=0; i<len; i++) {
10062                         Pairing(i, nPlayers, &w, &b, &dummy);
10063                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10064                     }
10065                 }
10066                 WriteTourneyFile(appData.results, f);
10067                 fclose(f); // release lock
10068                 return;
10069             }
10070         } else DisplayError(_("No engine with the name you gave is installed"), 0);
10071     }
10072     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10073     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
10074     free(participants);
10075     return;
10076 }
10077
10078 int
10079 CheckPlayers (char *participants)
10080 {
10081         int i;
10082         char buf[MSG_SIZ], *p;
10083         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10084         while(p = strchr(participants, '\n')) {
10085             *p = NULLCHAR;
10086             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10087             if(!mnemonic[i]) {
10088                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10089                 *p = '\n';
10090                 DisplayError(buf, 0);
10091                 return 1;
10092             }
10093             *p = '\n';
10094             participants = p + 1;
10095         }
10096         return 0;
10097 }
10098
10099 int
10100 CreateTourney (char *name)
10101 {
10102         FILE *f;
10103         if(matchMode && strcmp(name, appData.tourneyFile)) {
10104              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10105         }
10106         if(name[0] == NULLCHAR) {
10107             if(appData.participants[0])
10108                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10109             return 0;
10110         }
10111         f = fopen(name, "r");
10112         if(f) { // file exists
10113             ASSIGN(appData.tourneyFile, name);
10114             ParseArgsFromFile(f); // parse it
10115         } else {
10116             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10117             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10118                 DisplayError(_("Not enough participants"), 0);
10119                 return 0;
10120             }
10121             if(CheckPlayers(appData.participants)) return 0;
10122             ASSIGN(appData.tourneyFile, name);
10123             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10124             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10125         }
10126         fclose(f);
10127         appData.noChessProgram = FALSE;
10128         appData.clockMode = TRUE;
10129         SetGNUMode();
10130         return 1;
10131 }
10132
10133 int
10134 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10135 {
10136     char buf[MSG_SIZ], *p, *q;
10137     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10138     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10139     skip = !all && group[0]; // if group requested, we start in skip mode
10140     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10141         p = names; q = buf; header = 0;
10142         while(*p && *p != '\n') *q++ = *p++;
10143         *q = 0;
10144         if(*p == '\n') p++;
10145         if(buf[0] == '#') {
10146             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10147             depth++; // we must be entering a new group
10148             if(all) continue; // suppress printing group headers when complete list requested
10149             header = 1;
10150             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10151         }
10152         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10153         if(engineList[i]) free(engineList[i]);
10154         engineList[i] = strdup(buf);
10155         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10156         if(engineMnemonic[i]) free(engineMnemonic[i]);
10157         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10158             strcat(buf, " (");
10159             sscanf(q + 8, "%s", buf + strlen(buf));
10160             strcat(buf, ")");
10161         }
10162         engineMnemonic[i] = strdup(buf);
10163         i++;
10164     }
10165     engineList[i] = engineMnemonic[i] = NULL;
10166     return i;
10167 }
10168
10169 // following implemented as macro to avoid type limitations
10170 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10171
10172 void
10173 SwapEngines (int n)
10174 {   // swap settings for first engine and other engine (so far only some selected options)
10175     int h;
10176     char *p;
10177     if(n == 0) return;
10178     SWAP(directory, p)
10179     SWAP(chessProgram, p)
10180     SWAP(isUCI, h)
10181     SWAP(hasOwnBookUCI, h)
10182     SWAP(protocolVersion, h)
10183     SWAP(reuse, h)
10184     SWAP(scoreIsAbsolute, h)
10185     SWAP(timeOdds, h)
10186     SWAP(logo, p)
10187     SWAP(pgnName, p)
10188     SWAP(pvSAN, h)
10189     SWAP(engOptions, p)
10190     SWAP(engInitString, p)
10191     SWAP(computerString, p)
10192     SWAP(features, p)
10193     SWAP(fenOverride, p)
10194     SWAP(NPS, h)
10195     SWAP(accumulateTC, h)
10196     SWAP(host, p)
10197 }
10198
10199 int
10200 GetEngineLine (char *s, int n)
10201 {
10202     int i;
10203     char buf[MSG_SIZ];
10204     extern char *icsNames;
10205     if(!s || !*s) return 0;
10206     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
10207     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
10208     if(!mnemonic[i]) return 0;
10209     if(n == 11) return 1; // just testing if there was a match
10210     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
10211     if(n == 1) SwapEngines(n);
10212     ParseArgsFromString(buf);
10213     if(n == 1) SwapEngines(n);
10214     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
10215         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
10216         ParseArgsFromString(buf);
10217     }
10218     return 1;
10219 }
10220
10221 int
10222 SetPlayer (int player, char *p)
10223 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
10224     int i;
10225     char buf[MSG_SIZ], *engineName;
10226     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10227     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10228     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10229     if(mnemonic[i]) {
10230         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10231         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10232         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10233         ParseArgsFromString(buf);
10234     }
10235     free(engineName);
10236     return i;
10237 }
10238
10239 char *recentEngines;
10240
10241 void
10242 RecentEngineEvent (int nr)
10243 {
10244     int n;
10245 //    SwapEngines(1); // bump first to second
10246 //    ReplaceEngine(&second, 1); // and load it there
10247     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10248     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10249     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10250         ReplaceEngine(&first, 0);
10251         FloatToFront(&appData.recentEngineList, command[n]);
10252     }
10253 }
10254
10255 int
10256 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10257 {   // determine players from game number
10258     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10259
10260     if(appData.tourneyType == 0) {
10261         roundsPerCycle = (nPlayers - 1) | 1;
10262         pairingsPerRound = nPlayers / 2;
10263     } else if(appData.tourneyType > 0) {
10264         roundsPerCycle = nPlayers - appData.tourneyType;
10265         pairingsPerRound = appData.tourneyType;
10266     }
10267     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10268     gamesPerCycle = gamesPerRound * roundsPerCycle;
10269     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10270     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10271     curRound = nr / gamesPerRound; nr %= gamesPerRound;
10272     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10273     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10274     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10275
10276     if(appData.cycleSync) *syncInterval = gamesPerCycle;
10277     if(appData.roundSync) *syncInterval = gamesPerRound;
10278
10279     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10280
10281     if(appData.tourneyType == 0) {
10282         if(curPairing == (nPlayers-1)/2 ) {
10283             *whitePlayer = curRound;
10284             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10285         } else {
10286             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10287             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10288             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10289             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10290         }
10291     } else if(appData.tourneyType > 1) {
10292         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10293         *whitePlayer = curRound + appData.tourneyType;
10294     } else if(appData.tourneyType > 0) {
10295         *whitePlayer = curPairing;
10296         *blackPlayer = curRound + appData.tourneyType;
10297     }
10298
10299     // take care of white/black alternation per round. 
10300     // For cycles and games this is already taken care of by default, derived from matchGame!
10301     return curRound & 1;
10302 }
10303
10304 int
10305 NextTourneyGame (int nr, int *swapColors)
10306 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10307     char *p, *q;
10308     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers;
10309     FILE *tf;
10310     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10311     tf = fopen(appData.tourneyFile, "r");
10312     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10313     ParseArgsFromFile(tf); fclose(tf);
10314     InitTimeControls(); // TC might be altered from tourney file
10315
10316     nPlayers = CountPlayers(appData.participants); // count participants
10317     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10318     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10319
10320     if(syncInterval) {
10321         p = q = appData.results;
10322         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10323         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10324             DisplayMessage(_("Waiting for other game(s)"),"");
10325             waitingForGame = TRUE;
10326             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10327             return 0;
10328         }
10329         waitingForGame = FALSE;
10330     }
10331
10332     if(appData.tourneyType < 0) {
10333         if(nr>=0 && !pairingReceived) {
10334             char buf[1<<16];
10335             if(pairing.pr == NoProc) {
10336                 if(!appData.pairingEngine[0]) {
10337                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10338                     return 0;
10339                 }
10340                 StartChessProgram(&pairing); // starts the pairing engine
10341             }
10342             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10343             SendToProgram(buf, &pairing);
10344             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10345             SendToProgram(buf, &pairing);
10346             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10347         }
10348         pairingReceived = 0;                              // ... so we continue here 
10349         *swapColors = 0;
10350         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10351         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10352         matchGame = 1; roundNr = nr / syncInterval + 1;
10353     }
10354
10355     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10356
10357     // redefine engines, engine dir, etc.
10358     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10359     if(first.pr == NoProc) {
10360       SetPlayer(whitePlayer, appData.participants); // find white player amongst it, and parse its engine line
10361       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10362     }
10363     if(second.pr == NoProc) {
10364       SwapEngines(1);
10365       SetPlayer(blackPlayer, appData.participants); // find black player amongst it, and parse its engine line
10366       SwapEngines(1);         // and make that valid for second engine by swapping
10367       InitEngine(&second, 1);
10368     }
10369     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10370     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10371     return 1;
10372 }
10373
10374 void
10375 NextMatchGame ()
10376 {   // performs game initialization that does not invoke engines, and then tries to start the game
10377     int res, firstWhite, swapColors = 0;
10378     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10379     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
10380         char buf[MSG_SIZ];
10381         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
10382         if(strcmp(buf, currentDebugFile)) { // name has changed
10383             FILE *f = fopen(buf, "w");
10384             if(f) { // if opening the new file failed, just keep using the old one
10385                 ASSIGN(currentDebugFile, buf);
10386                 fclose(debugFP);
10387                 debugFP = f;
10388             }
10389             if(appData.serverFileName) {
10390                 if(serverFP) fclose(serverFP);
10391                 serverFP = fopen(appData.serverFileName, "w");
10392                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
10393                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
10394             }
10395         }
10396     }
10397     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10398     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10399     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
10400     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10401     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10402     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10403     Reset(FALSE, first.pr != NoProc);
10404     res = LoadGameOrPosition(matchGame); // setup game
10405     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10406     if(!res) return; // abort when bad game/pos file
10407     TwoMachinesEvent();
10408 }
10409
10410 void
10411 UserAdjudicationEvent (int result)
10412 {
10413     ChessMove gameResult = GameIsDrawn;
10414
10415     if( result > 0 ) {
10416         gameResult = WhiteWins;
10417     }
10418     else if( result < 0 ) {
10419         gameResult = BlackWins;
10420     }
10421
10422     if( gameMode == TwoMachinesPlay ) {
10423         GameEnds( gameResult, "User adjudication", GE_XBOARD );
10424     }
10425 }
10426
10427
10428 // [HGM] save: calculate checksum of game to make games easily identifiable
10429 int
10430 StringCheckSum (char *s)
10431 {
10432         int i = 0;
10433         if(s==NULL) return 0;
10434         while(*s) i = i*259 + *s++;
10435         return i;
10436 }
10437
10438 int
10439 GameCheckSum ()
10440 {
10441         int i, sum=0;
10442         for(i=backwardMostMove; i<forwardMostMove; i++) {
10443                 sum += pvInfoList[i].depth;
10444                 sum += StringCheckSum(parseList[i]);
10445                 sum += StringCheckSum(commentList[i]);
10446                 sum *= 261;
10447         }
10448         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10449         return sum + StringCheckSum(commentList[i]);
10450 } // end of save patch
10451
10452 void
10453 GameEnds (ChessMove result, char *resultDetails, int whosays)
10454 {
10455     GameMode nextGameMode;
10456     int isIcsGame;
10457     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10458
10459     if(endingGame) return; /* [HGM] crash: forbid recursion */
10460     endingGame = 1;
10461     if(twoBoards) { // [HGM] dual: switch back to one board
10462         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10463         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10464     }
10465     if (appData.debugMode) {
10466       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10467               result, resultDetails ? resultDetails : "(null)", whosays);
10468     }
10469
10470     fromX = fromY = -1; // [HGM] abort any move the user is entering.
10471
10472     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10473         /* If we are playing on ICS, the server decides when the
10474            game is over, but the engine can offer to draw, claim
10475            a draw, or resign.
10476          */
10477 #if ZIPPY
10478         if (appData.zippyPlay && first.initDone) {
10479             if (result == GameIsDrawn) {
10480                 /* In case draw still needs to be claimed */
10481                 SendToICS(ics_prefix);
10482                 SendToICS("draw\n");
10483             } else if (StrCaseStr(resultDetails, "resign")) {
10484                 SendToICS(ics_prefix);
10485                 SendToICS("resign\n");
10486             }
10487         }
10488 #endif
10489         endingGame = 0; /* [HGM] crash */
10490         return;
10491     }
10492
10493     /* If we're loading the game from a file, stop */
10494     if (whosays == GE_FILE) {
10495       (void) StopLoadGameTimer();
10496       gameFileFP = NULL;
10497     }
10498
10499     /* Cancel draw offers */
10500     first.offeredDraw = second.offeredDraw = 0;
10501
10502     /* If this is an ICS game, only ICS can really say it's done;
10503        if not, anyone can. */
10504     isIcsGame = (gameMode == IcsPlayingWhite ||
10505                  gameMode == IcsPlayingBlack ||
10506                  gameMode == IcsObserving    ||
10507                  gameMode == IcsExamining);
10508
10509     if (!isIcsGame || whosays == GE_ICS) {
10510         /* OK -- not an ICS game, or ICS said it was done */
10511         StopClocks();
10512         if (!isIcsGame && !appData.noChessProgram)
10513           SetUserThinkingEnables();
10514
10515         /* [HGM] if a machine claims the game end we verify this claim */
10516         if(gameMode == TwoMachinesPlay && appData.testClaims) {
10517             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10518                 char claimer;
10519                 ChessMove trueResult = (ChessMove) -1;
10520
10521                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
10522                                             first.twoMachinesColor[0] :
10523                                             second.twoMachinesColor[0] ;
10524
10525                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10526                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10527                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10528                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10529                 } else
10530                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10531                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10532                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10533                 } else
10534                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10535                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10536                 }
10537
10538                 // now verify win claims, but not in drop games, as we don't understand those yet
10539                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10540                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10541                     (result == WhiteWins && claimer == 'w' ||
10542                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
10543                       if (appData.debugMode) {
10544                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
10545                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10546                       }
10547                       if(result != trueResult) {
10548                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10549                               result = claimer == 'w' ? BlackWins : WhiteWins;
10550                               resultDetails = buf;
10551                       }
10552                 } else
10553                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10554                     && (forwardMostMove <= backwardMostMove ||
10555                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10556                         (claimer=='b')==(forwardMostMove&1))
10557                                                                                   ) {
10558                       /* [HGM] verify: draws that were not flagged are false claims */
10559                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10560                       result = claimer == 'w' ? BlackWins : WhiteWins;
10561                       resultDetails = buf;
10562                 }
10563                 /* (Claiming a loss is accepted no questions asked!) */
10564             }
10565             /* [HGM] bare: don't allow bare King to win */
10566             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10567                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10568                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10569                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10570                && result != GameIsDrawn)
10571             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10572                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10573                         int p = (signed char)boards[forwardMostMove][i][j] - color;
10574                         if(p >= 0 && p <= (int)WhiteKing) k++;
10575                 }
10576                 if (appData.debugMode) {
10577                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10578                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10579                 }
10580                 if(k <= 1) {
10581                         result = GameIsDrawn;
10582                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10583                         resultDetails = buf;
10584                 }
10585             }
10586         }
10587
10588
10589         if(serverMoves != NULL && !loadFlag) { char c = '=';
10590             if(result==WhiteWins) c = '+';
10591             if(result==BlackWins) c = '-';
10592             if(resultDetails != NULL)
10593                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
10594         }
10595         if (resultDetails != NULL) {
10596             gameInfo.result = result;
10597             gameInfo.resultDetails = StrSave(resultDetails);
10598
10599             /* display last move only if game was not loaded from file */
10600             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10601                 DisplayMove(currentMove - 1);
10602
10603             if (forwardMostMove != 0) {
10604                 if (gameMode != PlayFromGameFile && gameMode != EditGame
10605                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10606                                                                 ) {
10607                     if (*appData.saveGameFile != NULLCHAR) {
10608                         SaveGameToFile(appData.saveGameFile, TRUE);
10609                     } else if (appData.autoSaveGames) {
10610                         AutoSaveGame();
10611                     }
10612                     if (*appData.savePositionFile != NULLCHAR) {
10613                         SavePositionToFile(appData.savePositionFile);
10614                     }
10615                 }
10616             }
10617
10618             /* Tell program how game ended in case it is learning */
10619             /* [HGM] Moved this to after saving the PGN, just in case */
10620             /* engine died and we got here through time loss. In that */
10621             /* case we will get a fatal error writing the pipe, which */
10622             /* would otherwise lose us the PGN.                       */
10623             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
10624             /* output during GameEnds should never be fatal anymore   */
10625             if (gameMode == MachinePlaysWhite ||
10626                 gameMode == MachinePlaysBlack ||
10627                 gameMode == TwoMachinesPlay ||
10628                 gameMode == IcsPlayingWhite ||
10629                 gameMode == IcsPlayingBlack ||
10630                 gameMode == BeginningOfGame) {
10631                 char buf[MSG_SIZ];
10632                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10633                         resultDetails);
10634                 if (first.pr != NoProc) {
10635                     SendToProgram(buf, &first);
10636                 }
10637                 if (second.pr != NoProc &&
10638                     gameMode == TwoMachinesPlay) {
10639                     SendToProgram(buf, &second);
10640                 }
10641             }
10642         }
10643
10644         if (appData.icsActive) {
10645             if (appData.quietPlay &&
10646                 (gameMode == IcsPlayingWhite ||
10647                  gameMode == IcsPlayingBlack)) {
10648                 SendToICS(ics_prefix);
10649                 SendToICS("set shout 1\n");
10650             }
10651             nextGameMode = IcsIdle;
10652             ics_user_moved = FALSE;
10653             /* clean up premove.  It's ugly when the game has ended and the
10654              * premove highlights are still on the board.
10655              */
10656             if (gotPremove) {
10657               gotPremove = FALSE;
10658               ClearPremoveHighlights();
10659               DrawPosition(FALSE, boards[currentMove]);
10660             }
10661             if (whosays == GE_ICS) {
10662                 switch (result) {
10663                 case WhiteWins:
10664                     if (gameMode == IcsPlayingWhite)
10665                         PlayIcsWinSound();
10666                     else if(gameMode == IcsPlayingBlack)
10667                         PlayIcsLossSound();
10668                     break;
10669                 case BlackWins:
10670                     if (gameMode == IcsPlayingBlack)
10671                         PlayIcsWinSound();
10672                     else if(gameMode == IcsPlayingWhite)
10673                         PlayIcsLossSound();
10674                     break;
10675                 case GameIsDrawn:
10676                     PlayIcsDrawSound();
10677                     break;
10678                 default:
10679                     PlayIcsUnfinishedSound();
10680                 }
10681             }
10682         } else if (gameMode == EditGame ||
10683                    gameMode == PlayFromGameFile ||
10684                    gameMode == AnalyzeMode ||
10685                    gameMode == AnalyzeFile) {
10686             nextGameMode = gameMode;
10687         } else {
10688             nextGameMode = EndOfGame;
10689         }
10690         pausing = FALSE;
10691         ModeHighlight();
10692     } else {
10693         nextGameMode = gameMode;
10694     }
10695
10696     if (appData.noChessProgram) {
10697         gameMode = nextGameMode;
10698         ModeHighlight();
10699         endingGame = 0; /* [HGM] crash */
10700         return;
10701     }
10702
10703     if (first.reuse) {
10704         /* Put first chess program into idle state */
10705         if (first.pr != NoProc &&
10706             (gameMode == MachinePlaysWhite ||
10707              gameMode == MachinePlaysBlack ||
10708              gameMode == TwoMachinesPlay ||
10709              gameMode == IcsPlayingWhite ||
10710              gameMode == IcsPlayingBlack ||
10711              gameMode == BeginningOfGame)) {
10712             SendToProgram("force\n", &first);
10713             if (first.usePing) {
10714               char buf[MSG_SIZ];
10715               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10716               SendToProgram(buf, &first);
10717             }
10718         }
10719     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10720         /* Kill off first chess program */
10721         if (first.isr != NULL)
10722           RemoveInputSource(first.isr);
10723         first.isr = NULL;
10724
10725         if (first.pr != NoProc) {
10726             ExitAnalyzeMode();
10727             DoSleep( appData.delayBeforeQuit );
10728             SendToProgram("quit\n", &first);
10729             DoSleep( appData.delayAfterQuit );
10730             DestroyChildProcess(first.pr, first.useSigterm);
10731         }
10732         first.pr = NoProc;
10733     }
10734     if (second.reuse) {
10735         /* Put second chess program into idle state */
10736         if (second.pr != NoProc &&
10737             gameMode == TwoMachinesPlay) {
10738             SendToProgram("force\n", &second);
10739             if (second.usePing) {
10740               char buf[MSG_SIZ];
10741               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10742               SendToProgram(buf, &second);
10743             }
10744         }
10745     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10746         /* Kill off second chess program */
10747         if (second.isr != NULL)
10748           RemoveInputSource(second.isr);
10749         second.isr = NULL;
10750
10751         if (second.pr != NoProc) {
10752             DoSleep( appData.delayBeforeQuit );
10753             SendToProgram("quit\n", &second);
10754             DoSleep( appData.delayAfterQuit );
10755             DestroyChildProcess(second.pr, second.useSigterm);
10756         }
10757         second.pr = NoProc;
10758     }
10759
10760     if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10761         char resChar = '=';
10762         switch (result) {
10763         case WhiteWins:
10764           resChar = '+';
10765           if (first.twoMachinesColor[0] == 'w') {
10766             first.matchWins++;
10767           } else {
10768             second.matchWins++;
10769           }
10770           break;
10771         case BlackWins:
10772           resChar = '-';
10773           if (first.twoMachinesColor[0] == 'b') {
10774             first.matchWins++;
10775           } else {
10776             second.matchWins++;
10777           }
10778           break;
10779         case GameUnfinished:
10780           resChar = ' ';
10781         default:
10782           break;
10783         }
10784
10785         if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10786         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10787             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
10788             ReserveGame(nextGame, resChar); // sets nextGame
10789             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10790             else ranking = strdup("busy"); //suppress popup when aborted but not finished
10791         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10792
10793         if (nextGame <= appData.matchGames && !abortMatch) {
10794             gameMode = nextGameMode;
10795             matchGame = nextGame; // this will be overruled in tourney mode!
10796             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10797             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10798             endingGame = 0; /* [HGM] crash */
10799             return;
10800         } else {
10801             gameMode = nextGameMode;
10802             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10803                      first.tidy, second.tidy,
10804                      first.matchWins, second.matchWins,
10805                      appData.matchGames - (first.matchWins + second.matchWins));
10806             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
10807             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
10808             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10809             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10810                 first.twoMachinesColor = "black\n";
10811                 second.twoMachinesColor = "white\n";
10812             } else {
10813                 first.twoMachinesColor = "white\n";
10814                 second.twoMachinesColor = "black\n";
10815             }
10816         }
10817     }
10818     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10819         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10820       ExitAnalyzeMode();
10821     gameMode = nextGameMode;
10822     ModeHighlight();
10823     endingGame = 0;  /* [HGM] crash */
10824     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10825         if(matchMode == TRUE) { // match through command line: exit with or without popup
10826             if(ranking) {
10827                 ToNrEvent(forwardMostMove);
10828                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10829                 else ExitEvent(0);
10830             } else DisplayFatalError(buf, 0, 0);
10831         } else { // match through menu; just stop, with or without popup
10832             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10833             ModeHighlight();
10834             if(ranking){
10835                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10836             } else DisplayNote(buf);
10837       }
10838       if(ranking) free(ranking);
10839     }
10840 }
10841
10842 /* Assumes program was just initialized (initString sent).
10843    Leaves program in force mode. */
10844 void
10845 FeedMovesToProgram (ChessProgramState *cps, int upto)
10846 {
10847     int i;
10848
10849     if (appData.debugMode)
10850       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10851               startedFromSetupPosition ? "position and " : "",
10852               backwardMostMove, upto, cps->which);
10853     if(currentlyInitializedVariant != gameInfo.variant) {
10854       char buf[MSG_SIZ];
10855         // [HGM] variantswitch: make engine aware of new variant
10856         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10857                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10858         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10859         SendToProgram(buf, cps);
10860         currentlyInitializedVariant = gameInfo.variant;
10861     }
10862     SendToProgram("force\n", cps);
10863     if (startedFromSetupPosition) {
10864         SendBoard(cps, backwardMostMove);
10865     if (appData.debugMode) {
10866         fprintf(debugFP, "feedMoves\n");
10867     }
10868     }
10869     for (i = backwardMostMove; i < upto; i++) {
10870         SendMoveToProgram(i, cps);
10871     }
10872 }
10873
10874
10875 int
10876 ResurrectChessProgram ()
10877 {
10878      /* The chess program may have exited.
10879         If so, restart it and feed it all the moves made so far. */
10880     static int doInit = 0;
10881
10882     if (appData.noChessProgram) return 1;
10883
10884     if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10885         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10886         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10887         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10888     } else {
10889         if (first.pr != NoProc) return 1;
10890         StartChessProgram(&first);
10891     }
10892     InitChessProgram(&first, FALSE);
10893     FeedMovesToProgram(&first, currentMove);
10894
10895     if (!first.sendTime) {
10896         /* can't tell gnuchess what its clock should read,
10897            so we bow to its notion. */
10898         ResetClocks();
10899         timeRemaining[0][currentMove] = whiteTimeRemaining;
10900         timeRemaining[1][currentMove] = blackTimeRemaining;
10901     }
10902
10903     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10904                 appData.icsEngineAnalyze) && first.analysisSupport) {
10905       SendToProgram("analyze\n", &first);
10906       first.analyzing = TRUE;
10907     }
10908     return 1;
10909 }
10910
10911 /*
10912  * Button procedures
10913  */
10914 void
10915 Reset (int redraw, int init)
10916 {
10917     int i;
10918
10919     if (appData.debugMode) {
10920         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10921                 redraw, init, gameMode);
10922     }
10923     CleanupTail(); // [HGM] vari: delete any stored variations
10924     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
10925     pausing = pauseExamInvalid = FALSE;
10926     startedFromSetupPosition = blackPlaysFirst = FALSE;
10927     firstMove = TRUE;
10928     whiteFlag = blackFlag = FALSE;
10929     userOfferedDraw = FALSE;
10930     hintRequested = bookRequested = FALSE;
10931     first.maybeThinking = FALSE;
10932     second.maybeThinking = FALSE;
10933     first.bookSuspend = FALSE; // [HGM] book
10934     second.bookSuspend = FALSE;
10935     thinkOutput[0] = NULLCHAR;
10936     lastHint[0] = NULLCHAR;
10937     ClearGameInfo(&gameInfo);
10938     gameInfo.variant = StringToVariant(appData.variant);
10939     ics_user_moved = ics_clock_paused = FALSE;
10940     ics_getting_history = H_FALSE;
10941     ics_gamenum = -1;
10942     white_holding[0] = black_holding[0] = NULLCHAR;
10943     ClearProgramStats();
10944     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10945
10946     ResetFrontEnd();
10947     ClearHighlights();
10948     flipView = appData.flipView;
10949     ClearPremoveHighlights();
10950     gotPremove = FALSE;
10951     alarmSounded = FALSE;
10952
10953     GameEnds(EndOfFile, NULL, GE_PLAYER);
10954     if(appData.serverMovesName != NULL) {
10955         /* [HGM] prepare to make moves file for broadcasting */
10956         clock_t t = clock();
10957         if(serverMoves != NULL) fclose(serverMoves);
10958         serverMoves = fopen(appData.serverMovesName, "r");
10959         if(serverMoves != NULL) {
10960             fclose(serverMoves);
10961             /* delay 15 sec before overwriting, so all clients can see end */
10962             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10963         }
10964         serverMoves = fopen(appData.serverMovesName, "w");
10965     }
10966
10967     ExitAnalyzeMode();
10968     gameMode = BeginningOfGame;
10969     ModeHighlight();
10970     if(appData.icsActive) gameInfo.variant = VariantNormal;
10971     currentMove = forwardMostMove = backwardMostMove = 0;
10972     MarkTargetSquares(1);
10973     InitPosition(redraw);
10974     for (i = 0; i < MAX_MOVES; i++) {
10975         if (commentList[i] != NULL) {
10976             free(commentList[i]);
10977             commentList[i] = NULL;
10978         }
10979     }
10980     ResetClocks();
10981     timeRemaining[0][0] = whiteTimeRemaining;
10982     timeRemaining[1][0] = blackTimeRemaining;
10983
10984     if (first.pr == NoProc) {
10985         StartChessProgram(&first);
10986     }
10987     if (init) {
10988             InitChessProgram(&first, startedFromSetupPosition);
10989     }
10990     DisplayTitle("");
10991     DisplayMessage("", "");
10992     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10993     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10994     ClearMap();        // [HGM] exclude: invalidate map
10995 }
10996
10997 void
10998 AutoPlayGameLoop ()
10999 {
11000     for (;;) {
11001         if (!AutoPlayOneMove())
11002           return;
11003         if (matchMode || appData.timeDelay == 0)
11004           continue;
11005         if (appData.timeDelay < 0)
11006           return;
11007         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11008         break;
11009     }
11010 }
11011
11012 void
11013 AnalyzeNextGame()
11014 {
11015     ReloadGame(1); // next game
11016 }
11017
11018 int
11019 AutoPlayOneMove ()
11020 {
11021     int fromX, fromY, toX, toY;
11022
11023     if (appData.debugMode) {
11024       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11025     }
11026
11027     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11028       return FALSE;
11029
11030     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
11031       pvInfoList[currentMove].depth = programStats.depth;
11032       pvInfoList[currentMove].score = programStats.score;
11033       pvInfoList[currentMove].time  = 0;
11034       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11035     }
11036
11037     if (currentMove >= forwardMostMove) {
11038       if(gameMode == AnalyzeFile) {
11039           if(appData.loadGameIndex == -1) {
11040             GameEnds(EndOfFile, NULL, GE_FILE);
11041           ScheduleDelayedEvent(AnalyzeNextGame, 10);
11042           } else {
11043           ExitAnalyzeMode(); SendToProgram("force\n", &first);
11044         }
11045       }
11046 //      gameMode = EndOfGame;
11047 //      ModeHighlight();
11048
11049       /* [AS] Clear current move marker at the end of a game */
11050       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11051
11052       return FALSE;
11053     }
11054
11055     toX = moveList[currentMove][2] - AAA;
11056     toY = moveList[currentMove][3] - ONE;
11057
11058     if (moveList[currentMove][1] == '@') {
11059         if (appData.highlightLastMove) {
11060             SetHighlights(-1, -1, toX, toY);
11061         }
11062     } else {
11063         fromX = moveList[currentMove][0] - AAA;
11064         fromY = moveList[currentMove][1] - ONE;
11065
11066         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11067
11068         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11069
11070         if (appData.highlightLastMove) {
11071             SetHighlights(fromX, fromY, toX, toY);
11072         }
11073     }
11074     DisplayMove(currentMove);
11075     SendMoveToProgram(currentMove++, &first);
11076     DisplayBothClocks();
11077     DrawPosition(FALSE, boards[currentMove]);
11078     // [HGM] PV info: always display, routine tests if empty
11079     DisplayComment(currentMove - 1, commentList[currentMove]);
11080     return TRUE;
11081 }
11082
11083
11084 int
11085 LoadGameOneMove (ChessMove readAhead)
11086 {
11087     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11088     char promoChar = NULLCHAR;
11089     ChessMove moveType;
11090     char move[MSG_SIZ];
11091     char *p, *q;
11092
11093     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11094         gameMode != AnalyzeMode && gameMode != Training) {
11095         gameFileFP = NULL;
11096         return FALSE;
11097     }
11098
11099     yyboardindex = forwardMostMove;
11100     if (readAhead != EndOfFile) {
11101       moveType = readAhead;
11102     } else {
11103       if (gameFileFP == NULL)
11104           return FALSE;
11105       moveType = (ChessMove) Myylex();
11106     }
11107
11108     done = FALSE;
11109     switch (moveType) {
11110       case Comment:
11111         if (appData.debugMode)
11112           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11113         p = yy_text;
11114
11115         /* append the comment but don't display it */
11116         AppendComment(currentMove, p, FALSE);
11117         return TRUE;
11118
11119       case WhiteCapturesEnPassant:
11120       case BlackCapturesEnPassant:
11121       case WhitePromotion:
11122       case BlackPromotion:
11123       case WhiteNonPromotion:
11124       case BlackNonPromotion:
11125       case NormalMove:
11126       case WhiteKingSideCastle:
11127       case WhiteQueenSideCastle:
11128       case BlackKingSideCastle:
11129       case BlackQueenSideCastle:
11130       case WhiteKingSideCastleWild:
11131       case WhiteQueenSideCastleWild:
11132       case BlackKingSideCastleWild:
11133       case BlackQueenSideCastleWild:
11134       /* PUSH Fabien */
11135       case WhiteHSideCastleFR:
11136       case WhiteASideCastleFR:
11137       case BlackHSideCastleFR:
11138       case BlackASideCastleFR:
11139       /* POP Fabien */
11140         if (appData.debugMode)
11141           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11142         fromX = currentMoveString[0] - AAA;
11143         fromY = currentMoveString[1] - ONE;
11144         toX = currentMoveString[2] - AAA;
11145         toY = currentMoveString[3] - ONE;
11146         promoChar = currentMoveString[4];
11147         break;
11148
11149       case WhiteDrop:
11150       case BlackDrop:
11151         if (appData.debugMode)
11152           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11153         fromX = moveType == WhiteDrop ?
11154           (int) CharToPiece(ToUpper(currentMoveString[0])) :
11155         (int) CharToPiece(ToLower(currentMoveString[0]));
11156         fromY = DROP_RANK;
11157         toX = currentMoveString[2] - AAA;
11158         toY = currentMoveString[3] - ONE;
11159         break;
11160
11161       case WhiteWins:
11162       case BlackWins:
11163       case GameIsDrawn:
11164       case GameUnfinished:
11165         if (appData.debugMode)
11166           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11167         p = strchr(yy_text, '{');
11168         if (p == NULL) p = strchr(yy_text, '(');
11169         if (p == NULL) {
11170             p = yy_text;
11171             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11172         } else {
11173             q = strchr(p, *p == '{' ? '}' : ')');
11174             if (q != NULL) *q = NULLCHAR;
11175             p++;
11176         }
11177         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11178         GameEnds(moveType, p, GE_FILE);
11179         done = TRUE;
11180         if (cmailMsgLoaded) {
11181             ClearHighlights();
11182             flipView = WhiteOnMove(currentMove);
11183             if (moveType == GameUnfinished) flipView = !flipView;
11184             if (appData.debugMode)
11185               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11186         }
11187         break;
11188
11189       case EndOfFile:
11190         if (appData.debugMode)
11191           fprintf(debugFP, "Parser hit end of file\n");
11192         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11193           case MT_NONE:
11194           case MT_CHECK:
11195             break;
11196           case MT_CHECKMATE:
11197           case MT_STAINMATE:
11198             if (WhiteOnMove(currentMove)) {
11199                 GameEnds(BlackWins, "Black mates", GE_FILE);
11200             } else {
11201                 GameEnds(WhiteWins, "White mates", GE_FILE);
11202             }
11203             break;
11204           case MT_STALEMATE:
11205             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11206             break;
11207         }
11208         done = TRUE;
11209         break;
11210
11211       case MoveNumberOne:
11212         if (lastLoadGameStart == GNUChessGame) {
11213             /* GNUChessGames have numbers, but they aren't move numbers */
11214             if (appData.debugMode)
11215               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11216                       yy_text, (int) moveType);
11217             return LoadGameOneMove(EndOfFile); /* tail recursion */
11218         }
11219         /* else fall thru */
11220
11221       case XBoardGame:
11222       case GNUChessGame:
11223       case PGNTag:
11224         /* Reached start of next game in file */
11225         if (appData.debugMode)
11226           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11227         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11228           case MT_NONE:
11229           case MT_CHECK:
11230             break;
11231           case MT_CHECKMATE:
11232           case MT_STAINMATE:
11233             if (WhiteOnMove(currentMove)) {
11234                 GameEnds(BlackWins, "Black mates", GE_FILE);
11235             } else {
11236                 GameEnds(WhiteWins, "White mates", GE_FILE);
11237             }
11238             break;
11239           case MT_STALEMATE:
11240             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11241             break;
11242         }
11243         done = TRUE;
11244         break;
11245
11246       case PositionDiagram:     /* should not happen; ignore */
11247       case ElapsedTime:         /* ignore */
11248       case NAG:                 /* ignore */
11249         if (appData.debugMode)
11250           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11251                   yy_text, (int) moveType);
11252         return LoadGameOneMove(EndOfFile); /* tail recursion */
11253
11254       case IllegalMove:
11255         if (appData.testLegality) {
11256             if (appData.debugMode)
11257               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11258             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11259                     (forwardMostMove / 2) + 1,
11260                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11261             DisplayError(move, 0);
11262             done = TRUE;
11263         } else {
11264             if (appData.debugMode)
11265               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11266                       yy_text, currentMoveString);
11267             fromX = currentMoveString[0] - AAA;
11268             fromY = currentMoveString[1] - ONE;
11269             toX = currentMoveString[2] - AAA;
11270             toY = currentMoveString[3] - ONE;
11271             promoChar = currentMoveString[4];
11272         }
11273         break;
11274
11275       case AmbiguousMove:
11276         if (appData.debugMode)
11277           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11278         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11279                 (forwardMostMove / 2) + 1,
11280                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11281         DisplayError(move, 0);
11282         done = TRUE;
11283         break;
11284
11285       default:
11286       case ImpossibleMove:
11287         if (appData.debugMode)
11288           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11289         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11290                 (forwardMostMove / 2) + 1,
11291                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11292         DisplayError(move, 0);
11293         done = TRUE;
11294         break;
11295     }
11296
11297     if (done) {
11298         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11299             DrawPosition(FALSE, boards[currentMove]);
11300             DisplayBothClocks();
11301             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11302               DisplayComment(currentMove - 1, commentList[currentMove]);
11303         }
11304         (void) StopLoadGameTimer();
11305         gameFileFP = NULL;
11306         cmailOldMove = forwardMostMove;
11307         return FALSE;
11308     } else {
11309         /* currentMoveString is set as a side-effect of yylex */
11310
11311         thinkOutput[0] = NULLCHAR;
11312         MakeMove(fromX, fromY, toX, toY, promoChar);
11313         currentMove = forwardMostMove;
11314         return TRUE;
11315     }
11316 }
11317
11318 /* Load the nth game from the given file */
11319 int
11320 LoadGameFromFile (char *filename, int n, char *title, int useList)
11321 {
11322     FILE *f;
11323     char buf[MSG_SIZ];
11324
11325     if (strcmp(filename, "-") == 0) {
11326         f = stdin;
11327         title = "stdin";
11328     } else {
11329         f = fopen(filename, "rb");
11330         if (f == NULL) {
11331           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
11332             DisplayError(buf, errno);
11333             return FALSE;
11334         }
11335     }
11336     if (fseek(f, 0, 0) == -1) {
11337         /* f is not seekable; probably a pipe */
11338         useList = FALSE;
11339     }
11340     if (useList && n == 0) {
11341         int error = GameListBuild(f);
11342         if (error) {
11343             DisplayError(_("Cannot build game list"), error);
11344         } else if (!ListEmpty(&gameList) &&
11345                    ((ListGame *) gameList.tailPred)->number > 1) {
11346             GameListPopUp(f, title);
11347             return TRUE;
11348         }
11349         GameListDestroy();
11350         n = 1;
11351     }
11352     if (n == 0) n = 1;
11353     return LoadGame(f, n, title, FALSE);
11354 }
11355
11356
11357 void
11358 MakeRegisteredMove ()
11359 {
11360     int fromX, fromY, toX, toY;
11361     char promoChar;
11362     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11363         switch (cmailMoveType[lastLoadGameNumber - 1]) {
11364           case CMAIL_MOVE:
11365           case CMAIL_DRAW:
11366             if (appData.debugMode)
11367               fprintf(debugFP, "Restoring %s for game %d\n",
11368                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11369
11370             thinkOutput[0] = NULLCHAR;
11371             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11372             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11373             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11374             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11375             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11376             promoChar = cmailMove[lastLoadGameNumber - 1][4];
11377             MakeMove(fromX, fromY, toX, toY, promoChar);
11378             ShowMove(fromX, fromY, toX, toY);
11379
11380             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11381               case MT_NONE:
11382               case MT_CHECK:
11383                 break;
11384
11385               case MT_CHECKMATE:
11386               case MT_STAINMATE:
11387                 if (WhiteOnMove(currentMove)) {
11388                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
11389                 } else {
11390                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
11391                 }
11392                 break;
11393
11394               case MT_STALEMATE:
11395                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11396                 break;
11397             }
11398
11399             break;
11400
11401           case CMAIL_RESIGN:
11402             if (WhiteOnMove(currentMove)) {
11403                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11404             } else {
11405                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11406             }
11407             break;
11408
11409           case CMAIL_ACCEPT:
11410             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11411             break;
11412
11413           default:
11414             break;
11415         }
11416     }
11417
11418     return;
11419 }
11420
11421 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11422 int
11423 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11424 {
11425     int retVal;
11426
11427     if (gameNumber > nCmailGames) {
11428         DisplayError(_("No more games in this message"), 0);
11429         return FALSE;
11430     }
11431     if (f == lastLoadGameFP) {
11432         int offset = gameNumber - lastLoadGameNumber;
11433         if (offset == 0) {
11434             cmailMsg[0] = NULLCHAR;
11435             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11436                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11437                 nCmailMovesRegistered--;
11438             }
11439             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11440             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11441                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11442             }
11443         } else {
11444             if (! RegisterMove()) return FALSE;
11445         }
11446     }
11447
11448     retVal = LoadGame(f, gameNumber, title, useList);
11449
11450     /* Make move registered during previous look at this game, if any */
11451     MakeRegisteredMove();
11452
11453     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11454         commentList[currentMove]
11455           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11456         DisplayComment(currentMove - 1, commentList[currentMove]);
11457     }
11458
11459     return retVal;
11460 }
11461
11462 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11463 int
11464 ReloadGame (int offset)
11465 {
11466     int gameNumber = lastLoadGameNumber + offset;
11467     if (lastLoadGameFP == NULL) {
11468         DisplayError(_("No game has been loaded yet"), 0);
11469         return FALSE;
11470     }
11471     if (gameNumber <= 0) {
11472         DisplayError(_("Can't back up any further"), 0);
11473         return FALSE;
11474     }
11475     if (cmailMsgLoaded) {
11476         return CmailLoadGame(lastLoadGameFP, gameNumber,
11477                              lastLoadGameTitle, lastLoadGameUseList);
11478     } else {
11479         return LoadGame(lastLoadGameFP, gameNumber,
11480                         lastLoadGameTitle, lastLoadGameUseList);
11481     }
11482 }
11483
11484 int keys[EmptySquare+1];
11485
11486 int
11487 PositionMatches (Board b1, Board b2)
11488 {
11489     int r, f, sum=0;
11490     switch(appData.searchMode) {
11491         case 1: return CompareWithRights(b1, b2);
11492         case 2:
11493             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11494                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11495             }
11496             return TRUE;
11497         case 3:
11498             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11499               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11500                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11501             }
11502             return sum==0;
11503         case 4:
11504             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11505                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11506             }
11507             return sum==0;
11508     }
11509     return TRUE;
11510 }
11511
11512 #define Q_PROMO  4
11513 #define Q_EP     3
11514 #define Q_BCASTL 2
11515 #define Q_WCASTL 1
11516
11517 int pieceList[256], quickBoard[256];
11518 ChessSquare pieceType[256] = { EmptySquare };
11519 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11520 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11521 int soughtTotal, turn;
11522 Boolean epOK, flipSearch;
11523
11524 typedef struct {
11525     unsigned char piece, to;
11526 } Move;
11527
11528 #define DSIZE (250000)
11529
11530 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11531 Move *moveDatabase = initialSpace;
11532 unsigned int movePtr, dataSize = DSIZE;
11533
11534 int
11535 MakePieceList (Board board, int *counts)
11536 {
11537     int r, f, n=Q_PROMO, total=0;
11538     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11539     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11540         int sq = f + (r<<4);
11541         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
11542             quickBoard[sq] = ++n;
11543             pieceList[n] = sq;
11544             pieceType[n] = board[r][f];
11545             counts[board[r][f]]++;
11546             if(board[r][f] == WhiteKing) pieceList[1] = n; else
11547             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
11548             total++;
11549         }
11550     }
11551     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
11552     return total;
11553 }
11554
11555 void
11556 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
11557 {
11558     int sq = fromX + (fromY<<4);
11559     int piece = quickBoard[sq];
11560     quickBoard[sq] = 0;
11561     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
11562     if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11563         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
11564         moveDatabase[movePtr++].piece = Q_WCASTL;
11565         quickBoard[sq] = piece;
11566         piece = quickBoard[from]; quickBoard[from] = 0;
11567         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11568     } else
11569     if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11570         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
11571         moveDatabase[movePtr++].piece = Q_BCASTL;
11572         quickBoard[sq] = piece;
11573         piece = quickBoard[from]; quickBoard[from] = 0;
11574         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11575     } else
11576     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
11577         quickBoard[(fromY<<4)+toX] = 0;
11578         moveDatabase[movePtr].piece = Q_EP;
11579         moveDatabase[movePtr++].to = (fromY<<4)+toX;
11580         moveDatabase[movePtr].to = sq;
11581     } else
11582     if(promoPiece != pieceType[piece]) {
11583         moveDatabase[movePtr++].piece = Q_PROMO;
11584         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
11585     }
11586     moveDatabase[movePtr].piece = piece;
11587     quickBoard[sq] = piece;
11588     movePtr++;
11589 }
11590
11591 int
11592 PackGame (Board board)
11593 {
11594     Move *newSpace = NULL;
11595     moveDatabase[movePtr].piece = 0; // terminate previous game
11596     if(movePtr > dataSize) {
11597         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
11598         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
11599         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
11600         if(newSpace) {
11601             int i;
11602             Move *p = moveDatabase, *q = newSpace;
11603             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
11604             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
11605             moveDatabase = newSpace;
11606         } else { // calloc failed, we must be out of memory. Too bad...
11607             dataSize = 0; // prevent calloc events for all subsequent games
11608             return 0;     // and signal this one isn't cached
11609         }
11610     }
11611     movePtr++;
11612     MakePieceList(board, counts);
11613     return movePtr;
11614 }
11615
11616 int
11617 QuickCompare (Board board, int *minCounts, int *maxCounts)
11618 {   // compare according to search mode
11619     int r, f;
11620     switch(appData.searchMode)
11621     {
11622       case 1: // exact position match
11623         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
11624         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11625             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11626         }
11627         break;
11628       case 2: // can have extra material on empty squares
11629         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11630             if(board[r][f] == EmptySquare) continue;
11631             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11632         }
11633         break;
11634       case 3: // material with exact Pawn structure
11635         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11636             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
11637             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11638         } // fall through to material comparison
11639       case 4: // exact material
11640         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
11641         break;
11642       case 6: // material range with given imbalance
11643         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
11644         // fall through to range comparison
11645       case 5: // material range
11646         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
11647     }
11648     return TRUE;
11649 }
11650
11651 int
11652 QuickScan (Board board, Move *move)
11653 {   // reconstruct game,and compare all positions in it
11654     int cnt=0, stretch=0, total = MakePieceList(board, counts);
11655     do {
11656         int piece = move->piece;
11657         int to = move->to, from = pieceList[piece];
11658         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
11659           if(!piece) return -1;
11660           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
11661             piece = (++move)->piece;
11662             from = pieceList[piece];
11663             counts[pieceType[piece]]--;
11664             pieceType[piece] = (ChessSquare) move->to;
11665             counts[move->to]++;
11666           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
11667             counts[pieceType[quickBoard[to]]]--;
11668             quickBoard[to] = 0; total--;
11669             move++;
11670             continue;
11671           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
11672             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
11673             from  = pieceList[piece]; // so this must be King
11674             quickBoard[from] = 0;
11675             pieceList[piece] = to;
11676             from = pieceList[(++move)->piece]; // for FRC this has to be done here
11677             quickBoard[from] = 0; // rook
11678             quickBoard[to] = piece;
11679             to = move->to; piece = move->piece;
11680             goto aftercastle;
11681           }
11682         }
11683         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
11684         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
11685         quickBoard[from] = 0;
11686       aftercastle:
11687         quickBoard[to] = piece;
11688         pieceList[piece] = to;
11689         cnt++; turn ^= 3;
11690         if(QuickCompare(soughtBoard, minSought, maxSought) ||
11691            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
11692            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
11693                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
11694           ) {
11695             static int lastCounts[EmptySquare+1];
11696             int i;
11697             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
11698             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
11699         } else stretch = 0;
11700         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
11701         move++;
11702     } while(1);
11703 }
11704
11705 void
11706 InitSearch ()
11707 {
11708     int r, f;
11709     flipSearch = FALSE;
11710     CopyBoard(soughtBoard, boards[currentMove]);
11711     soughtTotal = MakePieceList(soughtBoard, maxSought);
11712     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
11713     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
11714     CopyBoard(reverseBoard, boards[currentMove]);
11715     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11716         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
11717         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
11718         reverseBoard[r][f] = piece;
11719     }
11720     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3; 
11721     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
11722     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
11723                  || (boards[currentMove][CASTLING][2] == NoRights || 
11724                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
11725                  && (boards[currentMove][CASTLING][5] == NoRights || 
11726                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
11727       ) {
11728         flipSearch = TRUE;
11729         CopyBoard(flipBoard, soughtBoard);
11730         CopyBoard(rotateBoard, reverseBoard);
11731         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11732             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
11733             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
11734         }
11735     }
11736     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
11737     if(appData.searchMode >= 5) {
11738         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
11739         MakePieceList(soughtBoard, minSought);
11740         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
11741     }
11742     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
11743         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
11744 }
11745
11746 GameInfo dummyInfo;
11747
11748 int
11749 GameContainsPosition (FILE *f, ListGame *lg)
11750 {
11751     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
11752     int fromX, fromY, toX, toY;
11753     char promoChar;
11754     static int initDone=FALSE;
11755
11756     // weed out games based on numerical tag comparison
11757     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
11758     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
11759     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
11760     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
11761     if(!initDone) {
11762         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
11763         initDone = TRUE;
11764     }
11765     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen);
11766     else CopyBoard(boards[scratch], initialPosition); // default start position
11767     if(lg->moves) {
11768         turn = btm + 1;
11769         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
11770         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
11771     }
11772     if(btm) plyNr++;
11773     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11774     fseek(f, lg->offset, 0);
11775     yynewfile(f);
11776     while(1) {
11777         yyboardindex = scratch;
11778         quickFlag = plyNr+1;
11779         next = Myylex();
11780         quickFlag = 0;
11781         switch(next) {
11782             case PGNTag:
11783                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
11784             default:
11785                 continue;
11786
11787             case XBoardGame:
11788             case GNUChessGame:
11789                 if(plyNr) return -1; // after we have seen moves, this is for new game
11790               continue;
11791
11792             case AmbiguousMove: // we cannot reconstruct the game beyond these two
11793             case ImpossibleMove:
11794             case WhiteWins: // game ends here with these four
11795             case BlackWins:
11796             case GameIsDrawn:
11797             case GameUnfinished:
11798                 return -1;
11799
11800             case IllegalMove:
11801                 if(appData.testLegality) return -1;
11802             case WhiteCapturesEnPassant:
11803             case BlackCapturesEnPassant:
11804             case WhitePromotion:
11805             case BlackPromotion:
11806             case WhiteNonPromotion:
11807             case BlackNonPromotion:
11808             case NormalMove:
11809             case WhiteKingSideCastle:
11810             case WhiteQueenSideCastle:
11811             case BlackKingSideCastle:
11812             case BlackQueenSideCastle:
11813             case WhiteKingSideCastleWild:
11814             case WhiteQueenSideCastleWild:
11815             case BlackKingSideCastleWild:
11816             case BlackQueenSideCastleWild:
11817             case WhiteHSideCastleFR:
11818             case WhiteASideCastleFR:
11819             case BlackHSideCastleFR:
11820             case BlackASideCastleFR:
11821                 fromX = currentMoveString[0] - AAA;
11822                 fromY = currentMoveString[1] - ONE;
11823                 toX = currentMoveString[2] - AAA;
11824                 toY = currentMoveString[3] - ONE;
11825                 promoChar = currentMoveString[4];
11826                 break;
11827             case WhiteDrop:
11828             case BlackDrop:
11829                 fromX = next == WhiteDrop ?
11830                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
11831                   (int) CharToPiece(ToLower(currentMoveString[0]));
11832                 fromY = DROP_RANK;
11833                 toX = currentMoveString[2] - AAA;
11834                 toY = currentMoveString[3] - ONE;
11835                 promoChar = 0;
11836                 break;
11837         }
11838         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
11839         plyNr++;
11840         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
11841         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11842         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
11843         if(appData.findMirror) {
11844             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
11845             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
11846         }
11847     }
11848 }
11849
11850 /* Load the nth game from open file f */
11851 int
11852 LoadGame (FILE *f, int gameNumber, char *title, int useList)
11853 {
11854     ChessMove cm;
11855     char buf[MSG_SIZ];
11856     int gn = gameNumber;
11857     ListGame *lg = NULL;
11858     int numPGNTags = 0;
11859     int err, pos = -1;
11860     GameMode oldGameMode;
11861     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
11862
11863     if (appData.debugMode)
11864         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
11865
11866     if (gameMode == Training )
11867         SetTrainingModeOff();
11868
11869     oldGameMode = gameMode;
11870     if (gameMode != BeginningOfGame) {
11871       Reset(FALSE, TRUE);
11872     }
11873
11874     gameFileFP = f;
11875     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
11876         fclose(lastLoadGameFP);
11877     }
11878
11879     if (useList) {
11880         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
11881
11882         if (lg) {
11883             fseek(f, lg->offset, 0);
11884             GameListHighlight(gameNumber);
11885             pos = lg->position;
11886             gn = 1;
11887         }
11888         else {
11889             if(gameMode == AnalyzeFile && appData.loadGameIndex == -1)
11890               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
11891             else
11892             DisplayError(_("Game number out of range"), 0);
11893             return FALSE;
11894         }
11895     } else {
11896         GameListDestroy();
11897         if (fseek(f, 0, 0) == -1) {
11898             if (f == lastLoadGameFP ?
11899                 gameNumber == lastLoadGameNumber + 1 :
11900                 gameNumber == 1) {
11901                 gn = 1;
11902             } else {
11903                 DisplayError(_("Can't seek on game file"), 0);
11904                 return FALSE;
11905             }
11906         }
11907     }
11908     lastLoadGameFP = f;
11909     lastLoadGameNumber = gameNumber;
11910     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
11911     lastLoadGameUseList = useList;
11912
11913     yynewfile(f);
11914
11915     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
11916       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
11917                 lg->gameInfo.black);
11918             DisplayTitle(buf);
11919     } else if (*title != NULLCHAR) {
11920         if (gameNumber > 1) {
11921           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
11922             DisplayTitle(buf);
11923         } else {
11924             DisplayTitle(title);
11925         }
11926     }
11927
11928     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
11929         gameMode = PlayFromGameFile;
11930         ModeHighlight();
11931     }
11932
11933     currentMove = forwardMostMove = backwardMostMove = 0;
11934     CopyBoard(boards[0], initialPosition);
11935     StopClocks();
11936
11937     /*
11938      * Skip the first gn-1 games in the file.
11939      * Also skip over anything that precedes an identifiable
11940      * start of game marker, to avoid being confused by
11941      * garbage at the start of the file.  Currently
11942      * recognized start of game markers are the move number "1",
11943      * the pattern "gnuchess .* game", the pattern
11944      * "^[#;%] [^ ]* game file", and a PGN tag block.
11945      * A game that starts with one of the latter two patterns
11946      * will also have a move number 1, possibly
11947      * following a position diagram.
11948      * 5-4-02: Let's try being more lenient and allowing a game to
11949      * start with an unnumbered move.  Does that break anything?
11950      */
11951     cm = lastLoadGameStart = EndOfFile;
11952     while (gn > 0) {
11953         yyboardindex = forwardMostMove;
11954         cm = (ChessMove) Myylex();
11955         switch (cm) {
11956           case EndOfFile:
11957             if (cmailMsgLoaded) {
11958                 nCmailGames = CMAIL_MAX_GAMES - gn;
11959             } else {
11960                 Reset(TRUE, TRUE);
11961                 DisplayError(_("Game not found in file"), 0);
11962             }
11963             return FALSE;
11964
11965           case GNUChessGame:
11966           case XBoardGame:
11967             gn--;
11968             lastLoadGameStart = cm;
11969             break;
11970
11971           case MoveNumberOne:
11972             switch (lastLoadGameStart) {
11973               case GNUChessGame:
11974               case XBoardGame:
11975               case PGNTag:
11976                 break;
11977               case MoveNumberOne:
11978               case EndOfFile:
11979                 gn--;           /* count this game */
11980                 lastLoadGameStart = cm;
11981                 break;
11982               default:
11983                 /* impossible */
11984                 break;
11985             }
11986             break;
11987
11988           case PGNTag:
11989             switch (lastLoadGameStart) {
11990               case GNUChessGame:
11991               case PGNTag:
11992               case MoveNumberOne:
11993               case EndOfFile:
11994                 gn--;           /* count this game */
11995                 lastLoadGameStart = cm;
11996                 break;
11997               case XBoardGame:
11998                 lastLoadGameStart = cm; /* game counted already */
11999                 break;
12000               default:
12001                 /* impossible */
12002                 break;
12003             }
12004             if (gn > 0) {
12005                 do {
12006                     yyboardindex = forwardMostMove;
12007                     cm = (ChessMove) Myylex();
12008                 } while (cm == PGNTag || cm == Comment);
12009             }
12010             break;
12011
12012           case WhiteWins:
12013           case BlackWins:
12014           case GameIsDrawn:
12015             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
12016                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
12017                     != CMAIL_OLD_RESULT) {
12018                     nCmailResults ++ ;
12019                     cmailResult[  CMAIL_MAX_GAMES
12020                                 - gn - 1] = CMAIL_OLD_RESULT;
12021                 }
12022             }
12023             break;
12024
12025           case NormalMove:
12026             /* Only a NormalMove can be at the start of a game
12027              * without a position diagram. */
12028             if (lastLoadGameStart == EndOfFile ) {
12029               gn--;
12030               lastLoadGameStart = MoveNumberOne;
12031             }
12032             break;
12033
12034           default:
12035             break;
12036         }
12037     }
12038
12039     if (appData.debugMode)
12040       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
12041
12042     if (cm == XBoardGame) {
12043         /* Skip any header junk before position diagram and/or move 1 */
12044         for (;;) {
12045             yyboardindex = forwardMostMove;
12046             cm = (ChessMove) Myylex();
12047
12048             if (cm == EndOfFile ||
12049                 cm == GNUChessGame || cm == XBoardGame) {
12050                 /* Empty game; pretend end-of-file and handle later */
12051                 cm = EndOfFile;
12052                 break;
12053             }
12054
12055             if (cm == MoveNumberOne || cm == PositionDiagram ||
12056                 cm == PGNTag || cm == Comment)
12057               break;
12058         }
12059     } else if (cm == GNUChessGame) {
12060         if (gameInfo.event != NULL) {
12061             free(gameInfo.event);
12062         }
12063         gameInfo.event = StrSave(yy_text);
12064     }
12065
12066     startedFromSetupPosition = FALSE;
12067     while (cm == PGNTag) {
12068         if (appData.debugMode)
12069           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
12070         err = ParsePGNTag(yy_text, &gameInfo);
12071         if (!err) numPGNTags++;
12072
12073         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
12074         if(gameInfo.variant != oldVariant) {
12075             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
12076             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
12077             InitPosition(TRUE);
12078             oldVariant = gameInfo.variant;
12079             if (appData.debugMode)
12080               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
12081         }
12082
12083
12084         if (gameInfo.fen != NULL) {
12085           Board initial_position;
12086           startedFromSetupPosition = TRUE;
12087           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
12088             Reset(TRUE, TRUE);
12089             DisplayError(_("Bad FEN position in file"), 0);
12090             return FALSE;
12091           }
12092           CopyBoard(boards[0], initial_position);
12093           if (blackPlaysFirst) {
12094             currentMove = forwardMostMove = backwardMostMove = 1;
12095             CopyBoard(boards[1], initial_position);
12096             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12097             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12098             timeRemaining[0][1] = whiteTimeRemaining;
12099             timeRemaining[1][1] = blackTimeRemaining;
12100             if (commentList[0] != NULL) {
12101               commentList[1] = commentList[0];
12102               commentList[0] = NULL;
12103             }
12104           } else {
12105             currentMove = forwardMostMove = backwardMostMove = 0;
12106           }
12107           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12108           {   int i;
12109               initialRulePlies = FENrulePlies;
12110               for( i=0; i< nrCastlingRights; i++ )
12111                   initialRights[i] = initial_position[CASTLING][i];
12112           }
12113           yyboardindex = forwardMostMove;
12114           free(gameInfo.fen);
12115           gameInfo.fen = NULL;
12116         }
12117
12118         yyboardindex = forwardMostMove;
12119         cm = (ChessMove) Myylex();
12120
12121         /* Handle comments interspersed among the tags */
12122         while (cm == Comment) {
12123             char *p;
12124             if (appData.debugMode)
12125               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12126             p = yy_text;
12127             AppendComment(currentMove, p, FALSE);
12128             yyboardindex = forwardMostMove;
12129             cm = (ChessMove) Myylex();
12130         }
12131     }
12132
12133     /* don't rely on existence of Event tag since if game was
12134      * pasted from clipboard the Event tag may not exist
12135      */
12136     if (numPGNTags > 0){
12137         char *tags;
12138         if (gameInfo.variant == VariantNormal) {
12139           VariantClass v = StringToVariant(gameInfo.event);
12140           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12141           if(v < VariantShogi) gameInfo.variant = v;
12142         }
12143         if (!matchMode) {
12144           if( appData.autoDisplayTags ) {
12145             tags = PGNTags(&gameInfo);
12146             TagsPopUp(tags, CmailMsg());
12147             free(tags);
12148           }
12149         }
12150     } else {
12151         /* Make something up, but don't display it now */
12152         SetGameInfo();
12153         TagsPopDown();
12154     }
12155
12156     if (cm == PositionDiagram) {
12157         int i, j;
12158         char *p;
12159         Board initial_position;
12160
12161         if (appData.debugMode)
12162           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12163
12164         if (!startedFromSetupPosition) {
12165             p = yy_text;
12166             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12167               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12168                 switch (*p) {
12169                   case '{':
12170                   case '[':
12171                   case '-':
12172                   case ' ':
12173                   case '\t':
12174                   case '\n':
12175                   case '\r':
12176                     break;
12177                   default:
12178                     initial_position[i][j++] = CharToPiece(*p);
12179                     break;
12180                 }
12181             while (*p == ' ' || *p == '\t' ||
12182                    *p == '\n' || *p == '\r') p++;
12183
12184             if (strncmp(p, "black", strlen("black"))==0)
12185               blackPlaysFirst = TRUE;
12186             else
12187               blackPlaysFirst = FALSE;
12188             startedFromSetupPosition = TRUE;
12189
12190             CopyBoard(boards[0], initial_position);
12191             if (blackPlaysFirst) {
12192                 currentMove = forwardMostMove = backwardMostMove = 1;
12193                 CopyBoard(boards[1], initial_position);
12194                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12195                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12196                 timeRemaining[0][1] = whiteTimeRemaining;
12197                 timeRemaining[1][1] = blackTimeRemaining;
12198                 if (commentList[0] != NULL) {
12199                     commentList[1] = commentList[0];
12200                     commentList[0] = NULL;
12201                 }
12202             } else {
12203                 currentMove = forwardMostMove = backwardMostMove = 0;
12204             }
12205         }
12206         yyboardindex = forwardMostMove;
12207         cm = (ChessMove) Myylex();
12208     }
12209
12210     if (first.pr == NoProc) {
12211         StartChessProgram(&first);
12212     }
12213     InitChessProgram(&first, FALSE);
12214     SendToProgram("force\n", &first);
12215     if (startedFromSetupPosition) {
12216         SendBoard(&first, forwardMostMove);
12217     if (appData.debugMode) {
12218         fprintf(debugFP, "Load Game\n");
12219     }
12220         DisplayBothClocks();
12221     }
12222
12223     /* [HGM] server: flag to write setup moves in broadcast file as one */
12224     loadFlag = appData.suppressLoadMoves;
12225
12226     while (cm == Comment) {
12227         char *p;
12228         if (appData.debugMode)
12229           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12230         p = yy_text;
12231         AppendComment(currentMove, p, FALSE);
12232         yyboardindex = forwardMostMove;
12233         cm = (ChessMove) Myylex();
12234     }
12235
12236     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12237         cm == WhiteWins || cm == BlackWins ||
12238         cm == GameIsDrawn || cm == GameUnfinished) {
12239         DisplayMessage("", _("No moves in game"));
12240         if (cmailMsgLoaded) {
12241             if (appData.debugMode)
12242               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12243             ClearHighlights();
12244             flipView = FALSE;
12245         }
12246         DrawPosition(FALSE, boards[currentMove]);
12247         DisplayBothClocks();
12248         gameMode = EditGame;
12249         ModeHighlight();
12250         gameFileFP = NULL;
12251         cmailOldMove = 0;
12252         return TRUE;
12253     }
12254
12255     // [HGM] PV info: routine tests if comment empty
12256     if (!matchMode && (pausing || appData.timeDelay != 0)) {
12257         DisplayComment(currentMove - 1, commentList[currentMove]);
12258     }
12259     if (!matchMode && appData.timeDelay != 0)
12260       DrawPosition(FALSE, boards[currentMove]);
12261
12262     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12263       programStats.ok_to_send = 1;
12264     }
12265
12266     /* if the first token after the PGN tags is a move
12267      * and not move number 1, retrieve it from the parser
12268      */
12269     if (cm != MoveNumberOne)
12270         LoadGameOneMove(cm);
12271
12272     /* load the remaining moves from the file */
12273     while (LoadGameOneMove(EndOfFile)) {
12274       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12275       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12276     }
12277
12278     /* rewind to the start of the game */
12279     currentMove = backwardMostMove;
12280
12281     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12282
12283     if (oldGameMode == AnalyzeFile ||
12284         oldGameMode == AnalyzeMode) {
12285       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
12286       keepInfo = 1;
12287       AnalyzeFileEvent();
12288       keepInfo = 0;
12289     }
12290
12291     if (!matchMode && pos > 0) {
12292         ToNrEvent(pos); // [HGM] no autoplay if selected on position
12293     } else
12294     if (matchMode || appData.timeDelay == 0) {
12295       ToEndEvent();
12296     } else if (appData.timeDelay > 0) {
12297       AutoPlayGameLoop();
12298     }
12299
12300     if (appData.debugMode)
12301         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
12302
12303     loadFlag = 0; /* [HGM] true game starts */
12304     return TRUE;
12305 }
12306
12307 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
12308 int
12309 ReloadPosition (int offset)
12310 {
12311     int positionNumber = lastLoadPositionNumber + offset;
12312     if (lastLoadPositionFP == NULL) {
12313         DisplayError(_("No position has been loaded yet"), 0);
12314         return FALSE;
12315     }
12316     if (positionNumber <= 0) {
12317         DisplayError(_("Can't back up any further"), 0);
12318         return FALSE;
12319     }
12320     return LoadPosition(lastLoadPositionFP, positionNumber,
12321                         lastLoadPositionTitle);
12322 }
12323
12324 /* Load the nth position from the given file */
12325 int
12326 LoadPositionFromFile (char *filename, int n, char *title)
12327 {
12328     FILE *f;
12329     char buf[MSG_SIZ];
12330
12331     if (strcmp(filename, "-") == 0) {
12332         return LoadPosition(stdin, n, "stdin");
12333     } else {
12334         f = fopen(filename, "rb");
12335         if (f == NULL) {
12336             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12337             DisplayError(buf, errno);
12338             return FALSE;
12339         } else {
12340             return LoadPosition(f, n, title);
12341         }
12342     }
12343 }
12344
12345 /* Load the nth position from the given open file, and close it */
12346 int
12347 LoadPosition (FILE *f, int positionNumber, char *title)
12348 {
12349     char *p, line[MSG_SIZ];
12350     Board initial_position;
12351     int i, j, fenMode, pn;
12352
12353     if (gameMode == Training )
12354         SetTrainingModeOff();
12355
12356     if (gameMode != BeginningOfGame) {
12357         Reset(FALSE, TRUE);
12358     }
12359     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12360         fclose(lastLoadPositionFP);
12361     }
12362     if (positionNumber == 0) positionNumber = 1;
12363     lastLoadPositionFP = f;
12364     lastLoadPositionNumber = positionNumber;
12365     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12366     if (first.pr == NoProc && !appData.noChessProgram) {
12367       StartChessProgram(&first);
12368       InitChessProgram(&first, FALSE);
12369     }
12370     pn = positionNumber;
12371     if (positionNumber < 0) {
12372         /* Negative position number means to seek to that byte offset */
12373         if (fseek(f, -positionNumber, 0) == -1) {
12374             DisplayError(_("Can't seek on position file"), 0);
12375             return FALSE;
12376         };
12377         pn = 1;
12378     } else {
12379         if (fseek(f, 0, 0) == -1) {
12380             if (f == lastLoadPositionFP ?
12381                 positionNumber == lastLoadPositionNumber + 1 :
12382                 positionNumber == 1) {
12383                 pn = 1;
12384             } else {
12385                 DisplayError(_("Can't seek on position file"), 0);
12386                 return FALSE;
12387             }
12388         }
12389     }
12390     /* See if this file is FEN or old-style xboard */
12391     if (fgets(line, MSG_SIZ, f) == NULL) {
12392         DisplayError(_("Position not found in file"), 0);
12393         return FALSE;
12394     }
12395     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12396     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12397
12398     if (pn >= 2) {
12399         if (fenMode || line[0] == '#') pn--;
12400         while (pn > 0) {
12401             /* skip positions before number pn */
12402             if (fgets(line, MSG_SIZ, f) == NULL) {
12403                 Reset(TRUE, TRUE);
12404                 DisplayError(_("Position not found in file"), 0);
12405                 return FALSE;
12406             }
12407             if (fenMode || line[0] == '#') pn--;
12408         }
12409     }
12410
12411     if (fenMode) {
12412         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
12413             DisplayError(_("Bad FEN position in file"), 0);
12414             return FALSE;
12415         }
12416     } else {
12417         (void) fgets(line, MSG_SIZ, f);
12418         (void) fgets(line, MSG_SIZ, f);
12419
12420         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12421             (void) fgets(line, MSG_SIZ, f);
12422             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12423                 if (*p == ' ')
12424                   continue;
12425                 initial_position[i][j++] = CharToPiece(*p);
12426             }
12427         }
12428
12429         blackPlaysFirst = FALSE;
12430         if (!feof(f)) {
12431             (void) fgets(line, MSG_SIZ, f);
12432             if (strncmp(line, "black", strlen("black"))==0)
12433               blackPlaysFirst = TRUE;
12434         }
12435     }
12436     startedFromSetupPosition = TRUE;
12437
12438     CopyBoard(boards[0], initial_position);
12439     if (blackPlaysFirst) {
12440         currentMove = forwardMostMove = backwardMostMove = 1;
12441         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12442         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12443         CopyBoard(boards[1], initial_position);
12444         DisplayMessage("", _("Black to play"));
12445     } else {
12446         currentMove = forwardMostMove = backwardMostMove = 0;
12447         DisplayMessage("", _("White to play"));
12448     }
12449     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12450     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12451         SendToProgram("force\n", &first);
12452         SendBoard(&first, forwardMostMove);
12453     }
12454     if (appData.debugMode) {
12455 int i, j;
12456   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12457   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12458         fprintf(debugFP, "Load Position\n");
12459     }
12460
12461     if (positionNumber > 1) {
12462       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12463         DisplayTitle(line);
12464     } else {
12465         DisplayTitle(title);
12466     }
12467     gameMode = EditGame;
12468     ModeHighlight();
12469     ResetClocks();
12470     timeRemaining[0][1] = whiteTimeRemaining;
12471     timeRemaining[1][1] = blackTimeRemaining;
12472     DrawPosition(FALSE, boards[currentMove]);
12473
12474     return TRUE;
12475 }
12476
12477
12478 void
12479 CopyPlayerNameIntoFileName (char **dest, char *src)
12480 {
12481     while (*src != NULLCHAR && *src != ',') {
12482         if (*src == ' ') {
12483             *(*dest)++ = '_';
12484             src++;
12485         } else {
12486             *(*dest)++ = *src++;
12487         }
12488     }
12489 }
12490
12491 char *
12492 DefaultFileName (char *ext)
12493 {
12494     static char def[MSG_SIZ];
12495     char *p;
12496
12497     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12498         p = def;
12499         CopyPlayerNameIntoFileName(&p, gameInfo.white);
12500         *p++ = '-';
12501         CopyPlayerNameIntoFileName(&p, gameInfo.black);
12502         *p++ = '.';
12503         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12504     } else {
12505         def[0] = NULLCHAR;
12506     }
12507     return def;
12508 }
12509
12510 /* Save the current game to the given file */
12511 int
12512 SaveGameToFile (char *filename, int append)
12513 {
12514     FILE *f;
12515     char buf[MSG_SIZ];
12516     int result, i, t,tot=0;
12517
12518     if (strcmp(filename, "-") == 0) {
12519         return SaveGame(stdout, 0, NULL);
12520     } else {
12521         for(i=0; i<10; i++) { // upto 10 tries
12522              f = fopen(filename, append ? "a" : "w");
12523              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12524              if(f || errno != 13) break;
12525              DoSleep(t = 5 + random()%11); // wait 5-15 msec
12526              tot += t;
12527         }
12528         if (f == NULL) {
12529             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12530             DisplayError(buf, errno);
12531             return FALSE;
12532         } else {
12533             safeStrCpy(buf, lastMsg, MSG_SIZ);
12534             DisplayMessage(_("Waiting for access to save file"), "");
12535             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
12536             DisplayMessage(_("Saving game"), "");
12537             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
12538             result = SaveGame(f, 0, NULL);
12539             DisplayMessage(buf, "");
12540             return result;
12541         }
12542     }
12543 }
12544
12545 char *
12546 SavePart (char *str)
12547 {
12548     static char buf[MSG_SIZ];
12549     char *p;
12550
12551     p = strchr(str, ' ');
12552     if (p == NULL) return str;
12553     strncpy(buf, str, p - str);
12554     buf[p - str] = NULLCHAR;
12555     return buf;
12556 }
12557
12558 #define PGN_MAX_LINE 75
12559
12560 #define PGN_SIDE_WHITE  0
12561 #define PGN_SIDE_BLACK  1
12562
12563 static int
12564 FindFirstMoveOutOfBook (int side)
12565 {
12566     int result = -1;
12567
12568     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12569         int index = backwardMostMove;
12570         int has_book_hit = 0;
12571
12572         if( (index % 2) != side ) {
12573             index++;
12574         }
12575
12576         while( index < forwardMostMove ) {
12577             /* Check to see if engine is in book */
12578             int depth = pvInfoList[index].depth;
12579             int score = pvInfoList[index].score;
12580             int in_book = 0;
12581
12582             if( depth <= 2 ) {
12583                 in_book = 1;
12584             }
12585             else if( score == 0 && depth == 63 ) {
12586                 in_book = 1; /* Zappa */
12587             }
12588             else if( score == 2 && depth == 99 ) {
12589                 in_book = 1; /* Abrok */
12590             }
12591
12592             has_book_hit += in_book;
12593
12594             if( ! in_book ) {
12595                 result = index;
12596
12597                 break;
12598             }
12599
12600             index += 2;
12601         }
12602     }
12603
12604     return result;
12605 }
12606
12607 void
12608 GetOutOfBookInfo (char * buf)
12609 {
12610     int oob[2];
12611     int i;
12612     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12613
12614     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12615     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12616
12617     *buf = '\0';
12618
12619     if( oob[0] >= 0 || oob[1] >= 0 ) {
12620         for( i=0; i<2; i++ ) {
12621             int idx = oob[i];
12622
12623             if( idx >= 0 ) {
12624                 if( i > 0 && oob[0] >= 0 ) {
12625                     strcat( buf, "   " );
12626                 }
12627
12628                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12629                 sprintf( buf+strlen(buf), "%s%.2f",
12630                     pvInfoList[idx].score >= 0 ? "+" : "",
12631                     pvInfoList[idx].score / 100.0 );
12632             }
12633         }
12634     }
12635 }
12636
12637 /* Save game in PGN style and close the file */
12638 int
12639 SaveGamePGN (FILE *f)
12640 {
12641     int i, offset, linelen, newblock;
12642 //    char *movetext;
12643     char numtext[32];
12644     int movelen, numlen, blank;
12645     char move_buffer[100]; /* [AS] Buffer for move+PV info */
12646
12647     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12648
12649     PrintPGNTags(f, &gameInfo);
12650
12651     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
12652
12653     if (backwardMostMove > 0 || startedFromSetupPosition) {
12654         char *fen = PositionToFEN(backwardMostMove, NULL);
12655         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12656         fprintf(f, "\n{--------------\n");
12657         PrintPosition(f, backwardMostMove);
12658         fprintf(f, "--------------}\n");
12659         free(fen);
12660     }
12661     else {
12662         /* [AS] Out of book annotation */
12663         if( appData.saveOutOfBookInfo ) {
12664             char buf[64];
12665
12666             GetOutOfBookInfo( buf );
12667
12668             if( buf[0] != '\0' ) {
12669                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12670             }
12671         }
12672
12673         fprintf(f, "\n");
12674     }
12675
12676     i = backwardMostMove;
12677     linelen = 0;
12678     newblock = TRUE;
12679
12680     while (i < forwardMostMove) {
12681         /* Print comments preceding this move */
12682         if (commentList[i] != NULL) {
12683             if (linelen > 0) fprintf(f, "\n");
12684             fprintf(f, "%s", commentList[i]);
12685             linelen = 0;
12686             newblock = TRUE;
12687         }
12688
12689         /* Format move number */
12690         if ((i % 2) == 0)
12691           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
12692         else
12693           if (newblock)
12694             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
12695           else
12696             numtext[0] = NULLCHAR;
12697
12698         numlen = strlen(numtext);
12699         newblock = FALSE;
12700
12701         /* Print move number */
12702         blank = linelen > 0 && numlen > 0;
12703         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
12704             fprintf(f, "\n");
12705             linelen = 0;
12706             blank = 0;
12707         }
12708         if (blank) {
12709             fprintf(f, " ");
12710             linelen++;
12711         }
12712         fprintf(f, "%s", numtext);
12713         linelen += numlen;
12714
12715         /* Get move */
12716         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
12717         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
12718
12719         /* Print move */
12720         blank = linelen > 0 && movelen > 0;
12721         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12722             fprintf(f, "\n");
12723             linelen = 0;
12724             blank = 0;
12725         }
12726         if (blank) {
12727             fprintf(f, " ");
12728             linelen++;
12729         }
12730         fprintf(f, "%s", move_buffer);
12731         linelen += movelen;
12732
12733         /* [AS] Add PV info if present */
12734         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
12735             /* [HGM] add time */
12736             char buf[MSG_SIZ]; int seconds;
12737
12738             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
12739
12740             if( seconds <= 0)
12741               buf[0] = 0;
12742             else
12743               if( seconds < 30 )
12744                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
12745               else
12746                 {
12747                   seconds = (seconds + 4)/10; // round to full seconds
12748                   if( seconds < 60 )
12749                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
12750                   else
12751                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
12752                 }
12753
12754             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
12755                       pvInfoList[i].score >= 0 ? "+" : "",
12756                       pvInfoList[i].score / 100.0,
12757                       pvInfoList[i].depth,
12758                       buf );
12759
12760             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
12761
12762             /* Print score/depth */
12763             blank = linelen > 0 && movelen > 0;
12764             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12765                 fprintf(f, "\n");
12766                 linelen = 0;
12767                 blank = 0;
12768             }
12769             if (blank) {
12770                 fprintf(f, " ");
12771                 linelen++;
12772             }
12773             fprintf(f, "%s", move_buffer);
12774             linelen += movelen;
12775         }
12776
12777         i++;
12778     }
12779
12780     /* Start a new line */
12781     if (linelen > 0) fprintf(f, "\n");
12782
12783     /* Print comments after last move */
12784     if (commentList[i] != NULL) {
12785         fprintf(f, "%s\n", commentList[i]);
12786     }
12787
12788     /* Print result */
12789     if (gameInfo.resultDetails != NULL &&
12790         gameInfo.resultDetails[0] != NULLCHAR) {
12791         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
12792                 PGNResult(gameInfo.result));
12793     } else {
12794         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12795     }
12796
12797     fclose(f);
12798     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12799     return TRUE;
12800 }
12801
12802 /* Save game in old style and close the file */
12803 int
12804 SaveGameOldStyle (FILE *f)
12805 {
12806     int i, offset;
12807     time_t tm;
12808
12809     tm = time((time_t *) NULL);
12810
12811     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
12812     PrintOpponents(f);
12813
12814     if (backwardMostMove > 0 || startedFromSetupPosition) {
12815         fprintf(f, "\n[--------------\n");
12816         PrintPosition(f, backwardMostMove);
12817         fprintf(f, "--------------]\n");
12818     } else {
12819         fprintf(f, "\n");
12820     }
12821
12822     i = backwardMostMove;
12823     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12824
12825     while (i < forwardMostMove) {
12826         if (commentList[i] != NULL) {
12827             fprintf(f, "[%s]\n", commentList[i]);
12828         }
12829
12830         if ((i % 2) == 1) {
12831             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
12832             i++;
12833         } else {
12834             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
12835             i++;
12836             if (commentList[i] != NULL) {
12837                 fprintf(f, "\n");
12838                 continue;
12839             }
12840             if (i >= forwardMostMove) {
12841                 fprintf(f, "\n");
12842                 break;
12843             }
12844             fprintf(f, "%s\n", parseList[i]);
12845             i++;
12846         }
12847     }
12848
12849     if (commentList[i] != NULL) {
12850         fprintf(f, "[%s]\n", commentList[i]);
12851     }
12852
12853     /* This isn't really the old style, but it's close enough */
12854     if (gameInfo.resultDetails != NULL &&
12855         gameInfo.resultDetails[0] != NULLCHAR) {
12856         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
12857                 gameInfo.resultDetails);
12858     } else {
12859         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12860     }
12861
12862     fclose(f);
12863     return TRUE;
12864 }
12865
12866 /* Save the current game to open file f and close the file */
12867 int
12868 SaveGame (FILE *f, int dummy, char *dummy2)
12869 {
12870     if (gameMode == EditPosition) EditPositionDone(TRUE);
12871     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12872     if (appData.oldSaveStyle)
12873       return SaveGameOldStyle(f);
12874     else
12875       return SaveGamePGN(f);
12876 }
12877
12878 /* Save the current position to the given file */
12879 int
12880 SavePositionToFile (char *filename)
12881 {
12882     FILE *f;
12883     char buf[MSG_SIZ];
12884
12885     if (strcmp(filename, "-") == 0) {
12886         return SavePosition(stdout, 0, NULL);
12887     } else {
12888         f = fopen(filename, "a");
12889         if (f == NULL) {
12890             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12891             DisplayError(buf, errno);
12892             return FALSE;
12893         } else {
12894             safeStrCpy(buf, lastMsg, MSG_SIZ);
12895             DisplayMessage(_("Waiting for access to save file"), "");
12896             flock(fileno(f), LOCK_EX); // [HGM] lock
12897             DisplayMessage(_("Saving position"), "");
12898             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
12899             SavePosition(f, 0, NULL);
12900             DisplayMessage(buf, "");
12901             return TRUE;
12902         }
12903     }
12904 }
12905
12906 /* Save the current position to the given open file and close the file */
12907 int
12908 SavePosition (FILE *f, int dummy, char *dummy2)
12909 {
12910     time_t tm;
12911     char *fen;
12912
12913     if (gameMode == EditPosition) EditPositionDone(TRUE);
12914     if (appData.oldSaveStyle) {
12915         tm = time((time_t *) NULL);
12916
12917         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
12918         PrintOpponents(f);
12919         fprintf(f, "[--------------\n");
12920         PrintPosition(f, currentMove);
12921         fprintf(f, "--------------]\n");
12922     } else {
12923         fen = PositionToFEN(currentMove, NULL);
12924         fprintf(f, "%s\n", fen);
12925         free(fen);
12926     }
12927     fclose(f);
12928     return TRUE;
12929 }
12930
12931 void
12932 ReloadCmailMsgEvent (int unregister)
12933 {
12934 #if !WIN32
12935     static char *inFilename = NULL;
12936     static char *outFilename;
12937     int i;
12938     struct stat inbuf, outbuf;
12939     int status;
12940
12941     /* Any registered moves are unregistered if unregister is set, */
12942     /* i.e. invoked by the signal handler */
12943     if (unregister) {
12944         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12945             cmailMoveRegistered[i] = FALSE;
12946             if (cmailCommentList[i] != NULL) {
12947                 free(cmailCommentList[i]);
12948                 cmailCommentList[i] = NULL;
12949             }
12950         }
12951         nCmailMovesRegistered = 0;
12952     }
12953
12954     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12955         cmailResult[i] = CMAIL_NOT_RESULT;
12956     }
12957     nCmailResults = 0;
12958
12959     if (inFilename == NULL) {
12960         /* Because the filenames are static they only get malloced once  */
12961         /* and they never get freed                                      */
12962         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
12963         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
12964
12965         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
12966         sprintf(outFilename, "%s.out", appData.cmailGameName);
12967     }
12968
12969     status = stat(outFilename, &outbuf);
12970     if (status < 0) {
12971         cmailMailedMove = FALSE;
12972     } else {
12973         status = stat(inFilename, &inbuf);
12974         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
12975     }
12976
12977     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
12978        counts the games, notes how each one terminated, etc.
12979
12980        It would be nice to remove this kludge and instead gather all
12981        the information while building the game list.  (And to keep it
12982        in the game list nodes instead of having a bunch of fixed-size
12983        parallel arrays.)  Note this will require getting each game's
12984        termination from the PGN tags, as the game list builder does
12985        not process the game moves.  --mann
12986        */
12987     cmailMsgLoaded = TRUE;
12988     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
12989
12990     /* Load first game in the file or popup game menu */
12991     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
12992
12993 #endif /* !WIN32 */
12994     return;
12995 }
12996
12997 int
12998 RegisterMove ()
12999 {
13000     FILE *f;
13001     char string[MSG_SIZ];
13002
13003     if (   cmailMailedMove
13004         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
13005         return TRUE;            /* Allow free viewing  */
13006     }
13007
13008     /* Unregister move to ensure that we don't leave RegisterMove        */
13009     /* with the move registered when the conditions for registering no   */
13010     /* longer hold                                                       */
13011     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
13012         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
13013         nCmailMovesRegistered --;
13014
13015         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
13016           {
13017               free(cmailCommentList[lastLoadGameNumber - 1]);
13018               cmailCommentList[lastLoadGameNumber - 1] = NULL;
13019           }
13020     }
13021
13022     if (cmailOldMove == -1) {
13023         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
13024         return FALSE;
13025     }
13026
13027     if (currentMove > cmailOldMove + 1) {
13028         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
13029         return FALSE;
13030     }
13031
13032     if (currentMove < cmailOldMove) {
13033         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
13034         return FALSE;
13035     }
13036
13037     if (forwardMostMove > currentMove) {
13038         /* Silently truncate extra moves */
13039         TruncateGame();
13040     }
13041
13042     if (   (currentMove == cmailOldMove + 1)
13043         || (   (currentMove == cmailOldMove)
13044             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
13045                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
13046         if (gameInfo.result != GameUnfinished) {
13047             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
13048         }
13049
13050         if (commentList[currentMove] != NULL) {
13051             cmailCommentList[lastLoadGameNumber - 1]
13052               = StrSave(commentList[currentMove]);
13053         }
13054         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
13055
13056         if (appData.debugMode)
13057           fprintf(debugFP, "Saving %s for game %d\n",
13058                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
13059
13060         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
13061
13062         f = fopen(string, "w");
13063         if (appData.oldSaveStyle) {
13064             SaveGameOldStyle(f); /* also closes the file */
13065
13066             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
13067             f = fopen(string, "w");
13068             SavePosition(f, 0, NULL); /* also closes the file */
13069         } else {
13070             fprintf(f, "{--------------\n");
13071             PrintPosition(f, currentMove);
13072             fprintf(f, "--------------}\n\n");
13073
13074             SaveGame(f, 0, NULL); /* also closes the file*/
13075         }
13076
13077         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
13078         nCmailMovesRegistered ++;
13079     } else if (nCmailGames == 1) {
13080         DisplayError(_("You have not made a move yet"), 0);
13081         return FALSE;
13082     }
13083
13084     return TRUE;
13085 }
13086
13087 void
13088 MailMoveEvent ()
13089 {
13090 #if !WIN32
13091     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
13092     FILE *commandOutput;
13093     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
13094     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
13095     int nBuffers;
13096     int i;
13097     int archived;
13098     char *arcDir;
13099
13100     if (! cmailMsgLoaded) {
13101         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
13102         return;
13103     }
13104
13105     if (nCmailGames == nCmailResults) {
13106         DisplayError(_("No unfinished games"), 0);
13107         return;
13108     }
13109
13110 #if CMAIL_PROHIBIT_REMAIL
13111     if (cmailMailedMove) {
13112       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);
13113         DisplayError(msg, 0);
13114         return;
13115     }
13116 #endif
13117
13118     if (! (cmailMailedMove || RegisterMove())) return;
13119
13120     if (   cmailMailedMove
13121         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13122       snprintf(string, MSG_SIZ, partCommandString,
13123                appData.debugMode ? " -v" : "", appData.cmailGameName);
13124         commandOutput = popen(string, "r");
13125
13126         if (commandOutput == NULL) {
13127             DisplayError(_("Failed to invoke cmail"), 0);
13128         } else {
13129             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13130                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13131             }
13132             if (nBuffers > 1) {
13133                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13134                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13135                 nBytes = MSG_SIZ - 1;
13136             } else {
13137                 (void) memcpy(msg, buffer, nBytes);
13138             }
13139             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13140
13141             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13142                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
13143
13144                 archived = TRUE;
13145                 for (i = 0; i < nCmailGames; i ++) {
13146                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
13147                         archived = FALSE;
13148                     }
13149                 }
13150                 if (   archived
13151                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13152                         != NULL)) {
13153                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13154                            arcDir,
13155                            appData.cmailGameName,
13156                            gameInfo.date);
13157                     LoadGameFromFile(buffer, 1, buffer, FALSE);
13158                     cmailMsgLoaded = FALSE;
13159                 }
13160             }
13161
13162             DisplayInformation(msg);
13163             pclose(commandOutput);
13164         }
13165     } else {
13166         if ((*cmailMsg) != '\0') {
13167             DisplayInformation(cmailMsg);
13168         }
13169     }
13170
13171     return;
13172 #endif /* !WIN32 */
13173 }
13174
13175 char *
13176 CmailMsg ()
13177 {
13178 #if WIN32
13179     return NULL;
13180 #else
13181     int  prependComma = 0;
13182     char number[5];
13183     char string[MSG_SIZ];       /* Space for game-list */
13184     int  i;
13185
13186     if (!cmailMsgLoaded) return "";
13187
13188     if (cmailMailedMove) {
13189       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13190     } else {
13191         /* Create a list of games left */
13192       snprintf(string, MSG_SIZ, "[");
13193         for (i = 0; i < nCmailGames; i ++) {
13194             if (! (   cmailMoveRegistered[i]
13195                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13196                 if (prependComma) {
13197                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13198                 } else {
13199                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13200                     prependComma = 1;
13201                 }
13202
13203                 strcat(string, number);
13204             }
13205         }
13206         strcat(string, "]");
13207
13208         if (nCmailMovesRegistered + nCmailResults == 0) {
13209             switch (nCmailGames) {
13210               case 1:
13211                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13212                 break;
13213
13214               case 2:
13215                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13216                 break;
13217
13218               default:
13219                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13220                          nCmailGames);
13221                 break;
13222             }
13223         } else {
13224             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13225               case 1:
13226                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13227                          string);
13228                 break;
13229
13230               case 0:
13231                 if (nCmailResults == nCmailGames) {
13232                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13233                 } else {
13234                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13235                 }
13236                 break;
13237
13238               default:
13239                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13240                          string);
13241             }
13242         }
13243     }
13244     return cmailMsg;
13245 #endif /* WIN32 */
13246 }
13247
13248 void
13249 ResetGameEvent ()
13250 {
13251     if (gameMode == Training)
13252       SetTrainingModeOff();
13253
13254     Reset(TRUE, TRUE);
13255     cmailMsgLoaded = FALSE;
13256     if (appData.icsActive) {
13257       SendToICS(ics_prefix);
13258       SendToICS("refresh\n");
13259     }
13260 }
13261
13262 void
13263 ExitEvent (int status)
13264 {
13265     exiting++;
13266     if (exiting > 2) {
13267       /* Give up on clean exit */
13268       exit(status);
13269     }
13270     if (exiting > 1) {
13271       /* Keep trying for clean exit */
13272       return;
13273     }
13274
13275     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
13276
13277     if (telnetISR != NULL) {
13278       RemoveInputSource(telnetISR);
13279     }
13280     if (icsPR != NoProc) {
13281       DestroyChildProcess(icsPR, TRUE);
13282     }
13283
13284     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
13285     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
13286
13287     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
13288     /* make sure this other one finishes before killing it!                  */
13289     if(endingGame) { int count = 0;
13290         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
13291         while(endingGame && count++ < 10) DoSleep(1);
13292         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
13293     }
13294
13295     /* Kill off chess programs */
13296     if (first.pr != NoProc) {
13297         ExitAnalyzeMode();
13298
13299         DoSleep( appData.delayBeforeQuit );
13300         SendToProgram("quit\n", &first);
13301         DoSleep( appData.delayAfterQuit );
13302         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
13303     }
13304     if (second.pr != NoProc) {
13305         DoSleep( appData.delayBeforeQuit );
13306         SendToProgram("quit\n", &second);
13307         DoSleep( appData.delayAfterQuit );
13308         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
13309     }
13310     if (first.isr != NULL) {
13311         RemoveInputSource(first.isr);
13312     }
13313     if (second.isr != NULL) {
13314         RemoveInputSource(second.isr);
13315     }
13316
13317     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13318     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13319
13320     ShutDownFrontEnd();
13321     exit(status);
13322 }
13323
13324 void
13325 PauseEvent ()
13326 {
13327     if (appData.debugMode)
13328         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13329     if (pausing) {
13330         pausing = FALSE;
13331         ModeHighlight();
13332         if (gameMode == MachinePlaysWhite ||
13333             gameMode == MachinePlaysBlack) {
13334             StartClocks();
13335         } else {
13336             DisplayBothClocks();
13337         }
13338         if (gameMode == PlayFromGameFile) {
13339             if (appData.timeDelay >= 0)
13340                 AutoPlayGameLoop();
13341         } else if (gameMode == IcsExamining && pauseExamInvalid) {
13342             Reset(FALSE, TRUE);
13343             SendToICS(ics_prefix);
13344             SendToICS("refresh\n");
13345         } else if (currentMove < forwardMostMove) {
13346             ForwardInner(forwardMostMove);
13347         }
13348         pauseExamInvalid = FALSE;
13349     } else {
13350         switch (gameMode) {
13351           default:
13352             return;
13353           case IcsExamining:
13354             pauseExamForwardMostMove = forwardMostMove;
13355             pauseExamInvalid = FALSE;
13356             /* fall through */
13357           case IcsObserving:
13358           case IcsPlayingWhite:
13359           case IcsPlayingBlack:
13360             pausing = TRUE;
13361             ModeHighlight();
13362             return;
13363           case PlayFromGameFile:
13364             (void) StopLoadGameTimer();
13365             pausing = TRUE;
13366             ModeHighlight();
13367             break;
13368           case BeginningOfGame:
13369             if (appData.icsActive) return;
13370             /* else fall through */
13371           case MachinePlaysWhite:
13372           case MachinePlaysBlack:
13373           case TwoMachinesPlay:
13374             if (forwardMostMove == 0)
13375               return;           /* don't pause if no one has moved */
13376             if ((gameMode == MachinePlaysWhite &&
13377                  !WhiteOnMove(forwardMostMove)) ||
13378                 (gameMode == MachinePlaysBlack &&
13379                  WhiteOnMove(forwardMostMove))) {
13380                 StopClocks();
13381             }
13382             pausing = TRUE;
13383             ModeHighlight();
13384             break;
13385         }
13386     }
13387 }
13388
13389 void
13390 EditCommentEvent ()
13391 {
13392     char title[MSG_SIZ];
13393
13394     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13395       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13396     } else {
13397       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13398                WhiteOnMove(currentMove - 1) ? " " : ".. ",
13399                parseList[currentMove - 1]);
13400     }
13401
13402     EditCommentPopUp(currentMove, title, commentList[currentMove]);
13403 }
13404
13405
13406 void
13407 EditTagsEvent ()
13408 {
13409     char *tags = PGNTags(&gameInfo);
13410     bookUp = FALSE;
13411     EditTagsPopUp(tags, NULL);
13412     free(tags);
13413 }
13414
13415 void
13416 ToggleSecond ()
13417 {
13418   if(second.analyzing) {
13419     SendToProgram("exit\n", &second);
13420     second.analyzing = FALSE;
13421   } else {
13422     if (second.pr == NoProc) StartChessProgram(&second);
13423     InitChessProgram(&second, FALSE);
13424     FeedMovesToProgram(&second, currentMove);
13425
13426     SendToProgram("analyze\n", &second);
13427     second.analyzing = TRUE;
13428   }
13429 }
13430
13431 void
13432 AnalyzeModeEvent ()
13433 {
13434     if (gameMode == AnalyzeMode) { ToggleSecond(); return; }
13435     if (appData.noChessProgram || gameMode == AnalyzeMode)
13436       return;
13437
13438     if (gameMode != AnalyzeFile) {
13439         if (!appData.icsEngineAnalyze) {
13440                EditGameEvent();
13441                if (gameMode != EditGame) return;
13442         }
13443         ResurrectChessProgram();
13444         SendToProgram("analyze\n", &first);
13445         first.analyzing = TRUE;
13446         /*first.maybeThinking = TRUE;*/
13447         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13448         EngineOutputPopUp();
13449     }
13450     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
13451     pausing = FALSE;
13452     ModeHighlight();
13453     SetGameInfo();
13454
13455     StartAnalysisClock();
13456     GetTimeMark(&lastNodeCountTime);
13457     lastNodeCount = 0;
13458 }
13459
13460 void
13461 AnalyzeFileEvent ()
13462 {
13463     if (appData.noChessProgram || gameMode == AnalyzeFile)
13464       return;
13465
13466     if (gameMode != AnalyzeMode) {
13467         EditGameEvent();
13468         if (gameMode != EditGame) return;
13469         ResurrectChessProgram();
13470         SendToProgram("analyze\n", &first);
13471         first.analyzing = TRUE;
13472         /*first.maybeThinking = TRUE;*/
13473         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13474         EngineOutputPopUp();
13475     }
13476     gameMode = AnalyzeFile;
13477     pausing = FALSE;
13478     ModeHighlight();
13479     SetGameInfo();
13480
13481     StartAnalysisClock();
13482     GetTimeMark(&lastNodeCountTime);
13483     lastNodeCount = 0;
13484     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
13485 }
13486
13487 void
13488 MachineWhiteEvent ()
13489 {
13490     char buf[MSG_SIZ];
13491     char *bookHit = NULL;
13492
13493     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
13494       return;
13495
13496
13497     if (gameMode == PlayFromGameFile ||
13498         gameMode == TwoMachinesPlay  ||
13499         gameMode == Training         ||
13500         gameMode == AnalyzeMode      ||
13501         gameMode == EndOfGame)
13502         EditGameEvent();
13503
13504     if (gameMode == EditPosition)
13505         EditPositionDone(TRUE);
13506
13507     if (!WhiteOnMove(currentMove)) {
13508         DisplayError(_("It is not White's turn"), 0);
13509         return;
13510     }
13511
13512     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13513       ExitAnalyzeMode();
13514
13515     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13516         gameMode == AnalyzeFile)
13517         TruncateGame();
13518
13519     ResurrectChessProgram();    /* in case it isn't running */
13520     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
13521         gameMode = MachinePlaysWhite;
13522         ResetClocks();
13523     } else
13524     gameMode = MachinePlaysWhite;
13525     pausing = FALSE;
13526     ModeHighlight();
13527     SetGameInfo();
13528     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13529     DisplayTitle(buf);
13530     if (first.sendName) {
13531       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
13532       SendToProgram(buf, &first);
13533     }
13534     if (first.sendTime) {
13535       if (first.useColors) {
13536         SendToProgram("black\n", &first); /*gnu kludge*/
13537       }
13538       SendTimeRemaining(&first, TRUE);
13539     }
13540     if (first.useColors) {
13541       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
13542     }
13543     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13544     SetMachineThinkingEnables();
13545     first.maybeThinking = TRUE;
13546     StartClocks();
13547     firstMove = FALSE;
13548
13549     if (appData.autoFlipView && !flipView) {
13550       flipView = !flipView;
13551       DrawPosition(FALSE, NULL);
13552       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13553     }
13554
13555     if(bookHit) { // [HGM] book: simulate book reply
13556         static char bookMove[MSG_SIZ]; // a bit generous?
13557
13558         programStats.nodes = programStats.depth = programStats.time =
13559         programStats.score = programStats.got_only_move = 0;
13560         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13561
13562         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13563         strcat(bookMove, bookHit);
13564         HandleMachineMove(bookMove, &first);
13565     }
13566 }
13567
13568 void
13569 MachineBlackEvent ()
13570 {
13571   char buf[MSG_SIZ];
13572   char *bookHit = NULL;
13573
13574     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
13575         return;
13576
13577
13578     if (gameMode == PlayFromGameFile ||
13579         gameMode == TwoMachinesPlay  ||
13580         gameMode == Training         ||
13581         gameMode == AnalyzeMode      ||
13582         gameMode == EndOfGame)
13583         EditGameEvent();
13584
13585     if (gameMode == EditPosition)
13586         EditPositionDone(TRUE);
13587
13588     if (WhiteOnMove(currentMove)) {
13589         DisplayError(_("It is not Black's turn"), 0);
13590         return;
13591     }
13592
13593     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13594       ExitAnalyzeMode();
13595
13596     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13597         gameMode == AnalyzeFile)
13598         TruncateGame();
13599
13600     ResurrectChessProgram();    /* in case it isn't running */
13601     gameMode = MachinePlaysBlack;
13602     pausing = FALSE;
13603     ModeHighlight();
13604     SetGameInfo();
13605     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13606     DisplayTitle(buf);
13607     if (first.sendName) {
13608       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
13609       SendToProgram(buf, &first);
13610     }
13611     if (first.sendTime) {
13612       if (first.useColors) {
13613         SendToProgram("white\n", &first); /*gnu kludge*/
13614       }
13615       SendTimeRemaining(&first, FALSE);
13616     }
13617     if (first.useColors) {
13618       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
13619     }
13620     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13621     SetMachineThinkingEnables();
13622     first.maybeThinking = TRUE;
13623     StartClocks();
13624
13625     if (appData.autoFlipView && flipView) {
13626       flipView = !flipView;
13627       DrawPosition(FALSE, NULL);
13628       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13629     }
13630     if(bookHit) { // [HGM] book: simulate book reply
13631         static char bookMove[MSG_SIZ]; // a bit generous?
13632
13633         programStats.nodes = programStats.depth = programStats.time =
13634         programStats.score = programStats.got_only_move = 0;
13635         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13636
13637         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13638         strcat(bookMove, bookHit);
13639         HandleMachineMove(bookMove, &first);
13640     }
13641 }
13642
13643
13644 void
13645 DisplayTwoMachinesTitle ()
13646 {
13647     char buf[MSG_SIZ];
13648     if (appData.matchGames > 0) {
13649         if(appData.tourneyFile[0]) {
13650           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
13651                    gameInfo.white, _("vs."), gameInfo.black,
13652                    nextGame+1, appData.matchGames+1,
13653                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
13654         } else 
13655         if (first.twoMachinesColor[0] == 'w') {
13656           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13657                    gameInfo.white, _("vs."),  gameInfo.black,
13658                    first.matchWins, second.matchWins,
13659                    matchGame - 1 - (first.matchWins + second.matchWins));
13660         } else {
13661           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13662                    gameInfo.white, _("vs."), gameInfo.black,
13663                    second.matchWins, first.matchWins,
13664                    matchGame - 1 - (first.matchWins + second.matchWins));
13665         }
13666     } else {
13667       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13668     }
13669     DisplayTitle(buf);
13670 }
13671
13672 void
13673 SettingsMenuIfReady ()
13674 {
13675   if (second.lastPing != second.lastPong) {
13676     DisplayMessage("", _("Waiting for second chess program"));
13677     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
13678     return;
13679   }
13680   ThawUI();
13681   DisplayMessage("", "");
13682   SettingsPopUp(&second);
13683 }
13684
13685 int
13686 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
13687 {
13688     char buf[MSG_SIZ];
13689     if (cps->pr == NoProc) {
13690         StartChessProgram(cps);
13691         if (cps->protocolVersion == 1) {
13692           retry();
13693         } else {
13694           /* kludge: allow timeout for initial "feature" command */
13695           FreezeUI();
13696           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
13697           DisplayMessage("", buf);
13698           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
13699         }
13700         return 1;
13701     }
13702     return 0;
13703 }
13704
13705 void
13706 TwoMachinesEvent P((void))
13707 {
13708     int i;
13709     char buf[MSG_SIZ];
13710     ChessProgramState *onmove;
13711     char *bookHit = NULL;
13712     static int stalling = 0;
13713     TimeMark now;
13714     long wait;
13715
13716     if (appData.noChessProgram) return;
13717
13718     switch (gameMode) {
13719       case TwoMachinesPlay:
13720         return;
13721       case MachinePlaysWhite:
13722       case MachinePlaysBlack:
13723         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13724             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13725             return;
13726         }
13727         /* fall through */
13728       case BeginningOfGame:
13729       case PlayFromGameFile:
13730       case EndOfGame:
13731         EditGameEvent();
13732         if (gameMode != EditGame) return;
13733         break;
13734       case EditPosition:
13735         EditPositionDone(TRUE);
13736         break;
13737       case AnalyzeMode:
13738       case AnalyzeFile:
13739         ExitAnalyzeMode();
13740         break;
13741       case EditGame:
13742       default:
13743         break;
13744     }
13745
13746 //    forwardMostMove = currentMove;
13747     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
13748
13749     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
13750
13751     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
13752     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
13753       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13754       return;
13755     }
13756
13757     if(second.protocolVersion >= 2 && !strstr(second.variants, VariantName(gameInfo.variant))) {
13758         DisplayError("second engine does not play this", 0);
13759         return;
13760     }
13761
13762     if(!stalling) {
13763       InitChessProgram(&second, FALSE); // unbalances ping of second engine
13764       SendToProgram("force\n", &second);
13765       stalling = 1;
13766       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13767       return;
13768     }
13769     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
13770     if(appData.matchPause>10000 || appData.matchPause<10)
13771                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
13772     wait = SubtractTimeMarks(&now, &pauseStart);
13773     if(wait < appData.matchPause) {
13774         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
13775         return;
13776     }
13777     // we are now committed to starting the game
13778     stalling = 0;
13779     DisplayMessage("", "");
13780     if (startedFromSetupPosition) {
13781         SendBoard(&second, backwardMostMove);
13782     if (appData.debugMode) {
13783         fprintf(debugFP, "Two Machines\n");
13784     }
13785     }
13786     for (i = backwardMostMove; i < forwardMostMove; i++) {
13787         SendMoveToProgram(i, &second);
13788     }
13789
13790     gameMode = TwoMachinesPlay;
13791     pausing = FALSE;
13792     ModeHighlight(); // [HGM] logo: this triggers display update of logos
13793     SetGameInfo();
13794     DisplayTwoMachinesTitle();
13795     firstMove = TRUE;
13796     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
13797         onmove = &first;
13798     } else {
13799         onmove = &second;
13800     }
13801     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
13802     SendToProgram(first.computerString, &first);
13803     if (first.sendName) {
13804       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
13805       SendToProgram(buf, &first);
13806     }
13807     SendToProgram(second.computerString, &second);
13808     if (second.sendName) {
13809       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
13810       SendToProgram(buf, &second);
13811     }
13812
13813     ResetClocks();
13814     if (!first.sendTime || !second.sendTime) {
13815         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13816         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13817     }
13818     if (onmove->sendTime) {
13819       if (onmove->useColors) {
13820         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
13821       }
13822       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
13823     }
13824     if (onmove->useColors) {
13825       SendToProgram(onmove->twoMachinesColor, onmove);
13826     }
13827     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
13828 //    SendToProgram("go\n", onmove);
13829     onmove->maybeThinking = TRUE;
13830     SetMachineThinkingEnables();
13831
13832     StartClocks();
13833
13834     if(bookHit) { // [HGM] book: simulate book reply
13835         static char bookMove[MSG_SIZ]; // a bit generous?
13836
13837         programStats.nodes = programStats.depth = programStats.time =
13838         programStats.score = programStats.got_only_move = 0;
13839         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13840
13841         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13842         strcat(bookMove, bookHit);
13843         savedMessage = bookMove; // args for deferred call
13844         savedState = onmove;
13845         ScheduleDelayedEvent(DeferredBookMove, 1);
13846     }
13847 }
13848
13849 void
13850 TrainingEvent ()
13851 {
13852     if (gameMode == Training) {
13853       SetTrainingModeOff();
13854       gameMode = PlayFromGameFile;
13855       DisplayMessage("", _("Training mode off"));
13856     } else {
13857       gameMode = Training;
13858       animateTraining = appData.animate;
13859
13860       /* make sure we are not already at the end of the game */
13861       if (currentMove < forwardMostMove) {
13862         SetTrainingModeOn();
13863         DisplayMessage("", _("Training mode on"));
13864       } else {
13865         gameMode = PlayFromGameFile;
13866         DisplayError(_("Already at end of game"), 0);
13867       }
13868     }
13869     ModeHighlight();
13870 }
13871
13872 void
13873 IcsClientEvent ()
13874 {
13875     if (!appData.icsActive) return;
13876     switch (gameMode) {
13877       case IcsPlayingWhite:
13878       case IcsPlayingBlack:
13879       case IcsObserving:
13880       case IcsIdle:
13881       case BeginningOfGame:
13882       case IcsExamining:
13883         return;
13884
13885       case EditGame:
13886         break;
13887
13888       case EditPosition:
13889         EditPositionDone(TRUE);
13890         break;
13891
13892       case AnalyzeMode:
13893       case AnalyzeFile:
13894         ExitAnalyzeMode();
13895         break;
13896
13897       default:
13898         EditGameEvent();
13899         break;
13900     }
13901
13902     gameMode = IcsIdle;
13903     ModeHighlight();
13904     return;
13905 }
13906
13907 void
13908 EditGameEvent ()
13909 {
13910     int i;
13911
13912     switch (gameMode) {
13913       case Training:
13914         SetTrainingModeOff();
13915         break;
13916       case MachinePlaysWhite:
13917       case MachinePlaysBlack:
13918       case BeginningOfGame:
13919         SendToProgram("force\n", &first);
13920         SetUserThinkingEnables();
13921         break;
13922       case PlayFromGameFile:
13923         (void) StopLoadGameTimer();
13924         if (gameFileFP != NULL) {
13925             gameFileFP = NULL;
13926         }
13927         break;
13928       case EditPosition:
13929         EditPositionDone(TRUE);
13930         break;
13931       case AnalyzeMode:
13932       case AnalyzeFile:
13933         ExitAnalyzeMode();
13934         SendToProgram("force\n", &first);
13935         break;
13936       case TwoMachinesPlay:
13937         GameEnds(EndOfFile, NULL, GE_PLAYER);
13938         ResurrectChessProgram();
13939         SetUserThinkingEnables();
13940         break;
13941       case EndOfGame:
13942         ResurrectChessProgram();
13943         break;
13944       case IcsPlayingBlack:
13945       case IcsPlayingWhite:
13946         DisplayError(_("Warning: You are still playing a game"), 0);
13947         break;
13948       case IcsObserving:
13949         DisplayError(_("Warning: You are still observing a game"), 0);
13950         break;
13951       case IcsExamining:
13952         DisplayError(_("Warning: You are still examining a game"), 0);
13953         break;
13954       case IcsIdle:
13955         break;
13956       case EditGame:
13957       default:
13958         return;
13959     }
13960
13961     pausing = FALSE;
13962     StopClocks();
13963     first.offeredDraw = second.offeredDraw = 0;
13964
13965     if (gameMode == PlayFromGameFile) {
13966         whiteTimeRemaining = timeRemaining[0][currentMove];
13967         blackTimeRemaining = timeRemaining[1][currentMove];
13968         DisplayTitle("");
13969     }
13970
13971     if (gameMode == MachinePlaysWhite ||
13972         gameMode == MachinePlaysBlack ||
13973         gameMode == TwoMachinesPlay ||
13974         gameMode == EndOfGame) {
13975         i = forwardMostMove;
13976         while (i > currentMove) {
13977             SendToProgram("undo\n", &first);
13978             i--;
13979         }
13980         if(!adjustedClock) {
13981         whiteTimeRemaining = timeRemaining[0][currentMove];
13982         blackTimeRemaining = timeRemaining[1][currentMove];
13983         DisplayBothClocks();
13984         }
13985         if (whiteFlag || blackFlag) {
13986             whiteFlag = blackFlag = 0;
13987         }
13988         DisplayTitle("");
13989     }
13990
13991     gameMode = EditGame;
13992     ModeHighlight();
13993     SetGameInfo();
13994 }
13995
13996
13997 void
13998 EditPositionEvent ()
13999 {
14000     if (gameMode == EditPosition) {
14001         EditGameEvent();
14002         return;
14003     }
14004
14005     EditGameEvent();
14006     if (gameMode != EditGame) return;
14007
14008     gameMode = EditPosition;
14009     ModeHighlight();
14010     SetGameInfo();
14011     if (currentMove > 0)
14012       CopyBoard(boards[0], boards[currentMove]);
14013
14014     blackPlaysFirst = !WhiteOnMove(currentMove);
14015     ResetClocks();
14016     currentMove = forwardMostMove = backwardMostMove = 0;
14017     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14018     DisplayMove(-1);
14019     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
14020 }
14021
14022 void
14023 ExitAnalyzeMode ()
14024 {
14025     /* [DM] icsEngineAnalyze - possible call from other functions */
14026     if (appData.icsEngineAnalyze) {
14027         appData.icsEngineAnalyze = FALSE;
14028
14029         DisplayMessage("",_("Close ICS engine analyze..."));
14030     }
14031     if (first.analysisSupport && first.analyzing) {
14032       SendToBoth("exit\n");
14033       first.analyzing = second.analyzing = FALSE;
14034     }
14035     thinkOutput[0] = NULLCHAR;
14036 }
14037
14038 void
14039 EditPositionDone (Boolean fakeRights)
14040 {
14041     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
14042
14043     startedFromSetupPosition = TRUE;
14044     InitChessProgram(&first, FALSE);
14045     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
14046       boards[0][EP_STATUS] = EP_NONE;
14047       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
14048       if(boards[0][0][BOARD_WIDTH>>1] == king) {
14049         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
14050         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
14051       } else boards[0][CASTLING][2] = NoRights;
14052       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
14053         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
14054         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
14055       } else boards[0][CASTLING][5] = NoRights;
14056       if(gameInfo.variant == VariantSChess) {
14057         int i;
14058         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
14059           boards[0][VIRGIN][i] = 0;
14060           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
14061           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
14062         }
14063       }
14064     }
14065     SendToProgram("force\n", &first);
14066     if (blackPlaysFirst) {
14067         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
14068         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
14069         currentMove = forwardMostMove = backwardMostMove = 1;
14070         CopyBoard(boards[1], boards[0]);
14071     } else {
14072         currentMove = forwardMostMove = backwardMostMove = 0;
14073     }
14074     SendBoard(&first, forwardMostMove);
14075     if (appData.debugMode) {
14076         fprintf(debugFP, "EditPosDone\n");
14077     }
14078     DisplayTitle("");
14079     DisplayMessage("", "");
14080     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14081     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14082     gameMode = EditGame;
14083     ModeHighlight();
14084     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14085     ClearHighlights(); /* [AS] */
14086 }
14087
14088 /* Pause for `ms' milliseconds */
14089 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14090 void
14091 TimeDelay (long ms)
14092 {
14093     TimeMark m1, m2;
14094
14095     GetTimeMark(&m1);
14096     do {
14097         GetTimeMark(&m2);
14098     } while (SubtractTimeMarks(&m2, &m1) < ms);
14099 }
14100
14101 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14102 void
14103 SendMultiLineToICS (char *buf)
14104 {
14105     char temp[MSG_SIZ+1], *p;
14106     int len;
14107
14108     len = strlen(buf);
14109     if (len > MSG_SIZ)
14110       len = MSG_SIZ;
14111
14112     strncpy(temp, buf, len);
14113     temp[len] = 0;
14114
14115     p = temp;
14116     while (*p) {
14117         if (*p == '\n' || *p == '\r')
14118           *p = ' ';
14119         ++p;
14120     }
14121
14122     strcat(temp, "\n");
14123     SendToICS(temp);
14124     SendToPlayer(temp, strlen(temp));
14125 }
14126
14127 void
14128 SetWhiteToPlayEvent ()
14129 {
14130     if (gameMode == EditPosition) {
14131         blackPlaysFirst = FALSE;
14132         DisplayBothClocks();    /* works because currentMove is 0 */
14133     } else if (gameMode == IcsExamining) {
14134         SendToICS(ics_prefix);
14135         SendToICS("tomove white\n");
14136     }
14137 }
14138
14139 void
14140 SetBlackToPlayEvent ()
14141 {
14142     if (gameMode == EditPosition) {
14143         blackPlaysFirst = TRUE;
14144         currentMove = 1;        /* kludge */
14145         DisplayBothClocks();
14146         currentMove = 0;
14147     } else if (gameMode == IcsExamining) {
14148         SendToICS(ics_prefix);
14149         SendToICS("tomove black\n");
14150     }
14151 }
14152
14153 void
14154 EditPositionMenuEvent (ChessSquare selection, int x, int y)
14155 {
14156     char buf[MSG_SIZ];
14157     ChessSquare piece = boards[0][y][x];
14158
14159     if (gameMode != EditPosition && gameMode != IcsExamining) return;
14160
14161     switch (selection) {
14162       case ClearBoard:
14163         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
14164             SendToICS(ics_prefix);
14165             SendToICS("bsetup clear\n");
14166         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
14167             SendToICS(ics_prefix);
14168             SendToICS("clearboard\n");
14169         } else {
14170             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
14171                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
14172                 for (y = 0; y < BOARD_HEIGHT; y++) {
14173                     if (gameMode == IcsExamining) {
14174                         if (boards[currentMove][y][x] != EmptySquare) {
14175                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
14176                                     AAA + x, ONE + y);
14177                             SendToICS(buf);
14178                         }
14179                     } else {
14180                         boards[0][y][x] = p;
14181                     }
14182                 }
14183             }
14184         }
14185         if (gameMode == EditPosition) {
14186             DrawPosition(FALSE, boards[0]);
14187         }
14188         break;
14189
14190       case WhitePlay:
14191         SetWhiteToPlayEvent();
14192         break;
14193
14194       case BlackPlay:
14195         SetBlackToPlayEvent();
14196         break;
14197
14198       case EmptySquare:
14199         if (gameMode == IcsExamining) {
14200             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14201             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
14202             SendToICS(buf);
14203         } else {
14204             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14205                 if(x == BOARD_LEFT-2) {
14206                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
14207                     boards[0][y][1] = 0;
14208                 } else
14209                 if(x == BOARD_RGHT+1) {
14210                     if(y >= gameInfo.holdingsSize) break;
14211                     boards[0][y][BOARD_WIDTH-2] = 0;
14212                 } else break;
14213             }
14214             boards[0][y][x] = EmptySquare;
14215             DrawPosition(FALSE, boards[0]);
14216         }
14217         break;
14218
14219       case PromotePiece:
14220         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
14221            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
14222             selection = (ChessSquare) (PROMOTED piece);
14223         } else if(piece == EmptySquare) selection = WhiteSilver;
14224         else selection = (ChessSquare)((int)piece - 1);
14225         goto defaultlabel;
14226
14227       case DemotePiece:
14228         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
14229            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
14230             selection = (ChessSquare) (DEMOTED piece);
14231         } else if(piece == EmptySquare) selection = BlackSilver;
14232         else selection = (ChessSquare)((int)piece + 1);
14233         goto defaultlabel;
14234
14235       case WhiteQueen:
14236       case BlackQueen:
14237         if(gameInfo.variant == VariantShatranj ||
14238            gameInfo.variant == VariantXiangqi  ||
14239            gameInfo.variant == VariantCourier  ||
14240            gameInfo.variant == VariantMakruk     )
14241             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
14242         goto defaultlabel;
14243
14244       case WhiteKing:
14245       case BlackKing:
14246         if(gameInfo.variant == VariantXiangqi)
14247             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
14248         if(gameInfo.variant == VariantKnightmate)
14249             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
14250       default:
14251         defaultlabel:
14252         if (gameMode == IcsExamining) {
14253             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14254             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
14255                      PieceToChar(selection), AAA + x, ONE + y);
14256             SendToICS(buf);
14257         } else {
14258             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14259                 int n;
14260                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
14261                     n = PieceToNumber(selection - BlackPawn);
14262                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
14263                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
14264                     boards[0][BOARD_HEIGHT-1-n][1]++;
14265                 } else
14266                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
14267                     n = PieceToNumber(selection);
14268                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
14269                     boards[0][n][BOARD_WIDTH-1] = selection;
14270                     boards[0][n][BOARD_WIDTH-2]++;
14271                 }
14272             } else
14273             boards[0][y][x] = selection;
14274             DrawPosition(TRUE, boards[0]);
14275             ClearHighlights();
14276             fromX = fromY = -1;
14277         }
14278         break;
14279     }
14280 }
14281
14282
14283 void
14284 DropMenuEvent (ChessSquare selection, int x, int y)
14285 {
14286     ChessMove moveType;
14287
14288     switch (gameMode) {
14289       case IcsPlayingWhite:
14290       case MachinePlaysBlack:
14291         if (!WhiteOnMove(currentMove)) {
14292             DisplayMoveError(_("It is Black's turn"));
14293             return;
14294         }
14295         moveType = WhiteDrop;
14296         break;
14297       case IcsPlayingBlack:
14298       case MachinePlaysWhite:
14299         if (WhiteOnMove(currentMove)) {
14300             DisplayMoveError(_("It is White's turn"));
14301             return;
14302         }
14303         moveType = BlackDrop;
14304         break;
14305       case EditGame:
14306         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
14307         break;
14308       default:
14309         return;
14310     }
14311
14312     if (moveType == BlackDrop && selection < BlackPawn) {
14313       selection = (ChessSquare) ((int) selection
14314                                  + (int) BlackPawn - (int) WhitePawn);
14315     }
14316     if (boards[currentMove][y][x] != EmptySquare) {
14317         DisplayMoveError(_("That square is occupied"));
14318         return;
14319     }
14320
14321     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
14322 }
14323
14324 void
14325 AcceptEvent ()
14326 {
14327     /* Accept a pending offer of any kind from opponent */
14328
14329     if (appData.icsActive) {
14330         SendToICS(ics_prefix);
14331         SendToICS("accept\n");
14332     } else if (cmailMsgLoaded) {
14333         if (currentMove == cmailOldMove &&
14334             commentList[cmailOldMove] != NULL &&
14335             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14336                    "Black offers a draw" : "White offers a draw")) {
14337             TruncateGame();
14338             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14339             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14340         } else {
14341             DisplayError(_("There is no pending offer on this move"), 0);
14342             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14343         }
14344     } else {
14345         /* Not used for offers from chess program */
14346     }
14347 }
14348
14349 void
14350 DeclineEvent ()
14351 {
14352     /* Decline a pending offer of any kind from opponent */
14353
14354     if (appData.icsActive) {
14355         SendToICS(ics_prefix);
14356         SendToICS("decline\n");
14357     } else if (cmailMsgLoaded) {
14358         if (currentMove == cmailOldMove &&
14359             commentList[cmailOldMove] != NULL &&
14360             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14361                    "Black offers a draw" : "White offers a draw")) {
14362 #ifdef NOTDEF
14363             AppendComment(cmailOldMove, "Draw declined", TRUE);
14364             DisplayComment(cmailOldMove - 1, "Draw declined");
14365 #endif /*NOTDEF*/
14366         } else {
14367             DisplayError(_("There is no pending offer on this move"), 0);
14368         }
14369     } else {
14370         /* Not used for offers from chess program */
14371     }
14372 }
14373
14374 void
14375 RematchEvent ()
14376 {
14377     /* Issue ICS rematch command */
14378     if (appData.icsActive) {
14379         SendToICS(ics_prefix);
14380         SendToICS("rematch\n");
14381     }
14382 }
14383
14384 void
14385 CallFlagEvent ()
14386 {
14387     /* Call your opponent's flag (claim a win on time) */
14388     if (appData.icsActive) {
14389         SendToICS(ics_prefix);
14390         SendToICS("flag\n");
14391     } else {
14392         switch (gameMode) {
14393           default:
14394             return;
14395           case MachinePlaysWhite:
14396             if (whiteFlag) {
14397                 if (blackFlag)
14398                   GameEnds(GameIsDrawn, "Both players ran out of time",
14399                            GE_PLAYER);
14400                 else
14401                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
14402             } else {
14403                 DisplayError(_("Your opponent is not out of time"), 0);
14404             }
14405             break;
14406           case MachinePlaysBlack:
14407             if (blackFlag) {
14408                 if (whiteFlag)
14409                   GameEnds(GameIsDrawn, "Both players ran out of time",
14410                            GE_PLAYER);
14411                 else
14412                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
14413             } else {
14414                 DisplayError(_("Your opponent is not out of time"), 0);
14415             }
14416             break;
14417         }
14418     }
14419 }
14420
14421 void
14422 ClockClick (int which)
14423 {       // [HGM] code moved to back-end from winboard.c
14424         if(which) { // black clock
14425           if (gameMode == EditPosition || gameMode == IcsExamining) {
14426             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14427             SetBlackToPlayEvent();
14428           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
14429           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
14430           } else if (shiftKey) {
14431             AdjustClock(which, -1);
14432           } else if (gameMode == IcsPlayingWhite ||
14433                      gameMode == MachinePlaysBlack) {
14434             CallFlagEvent();
14435           }
14436         } else { // white clock
14437           if (gameMode == EditPosition || gameMode == IcsExamining) {
14438             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14439             SetWhiteToPlayEvent();
14440           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
14441           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
14442           } else if (shiftKey) {
14443             AdjustClock(which, -1);
14444           } else if (gameMode == IcsPlayingBlack ||
14445                    gameMode == MachinePlaysWhite) {
14446             CallFlagEvent();
14447           }
14448         }
14449 }
14450
14451 void
14452 DrawEvent ()
14453 {
14454     /* Offer draw or accept pending draw offer from opponent */
14455
14456     if (appData.icsActive) {
14457         /* Note: tournament rules require draw offers to be
14458            made after you make your move but before you punch
14459            your clock.  Currently ICS doesn't let you do that;
14460            instead, you immediately punch your clock after making
14461            a move, but you can offer a draw at any time. */
14462
14463         SendToICS(ics_prefix);
14464         SendToICS("draw\n");
14465         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
14466     } else if (cmailMsgLoaded) {
14467         if (currentMove == cmailOldMove &&
14468             commentList[cmailOldMove] != NULL &&
14469             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14470                    "Black offers a draw" : "White offers a draw")) {
14471             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14472             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14473         } else if (currentMove == cmailOldMove + 1) {
14474             char *offer = WhiteOnMove(cmailOldMove) ?
14475               "White offers a draw" : "Black offers a draw";
14476             AppendComment(currentMove, offer, TRUE);
14477             DisplayComment(currentMove - 1, offer);
14478             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
14479         } else {
14480             DisplayError(_("You must make your move before offering a draw"), 0);
14481             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14482         }
14483     } else if (first.offeredDraw) {
14484         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
14485     } else {
14486         if (first.sendDrawOffers) {
14487             SendToProgram("draw\n", &first);
14488             userOfferedDraw = TRUE;
14489         }
14490     }
14491 }
14492
14493 void
14494 AdjournEvent ()
14495 {
14496     /* Offer Adjourn or accept pending Adjourn offer from opponent */
14497
14498     if (appData.icsActive) {
14499         SendToICS(ics_prefix);
14500         SendToICS("adjourn\n");
14501     } else {
14502         /* Currently GNU Chess doesn't offer or accept Adjourns */
14503     }
14504 }
14505
14506
14507 void
14508 AbortEvent ()
14509 {
14510     /* Offer Abort or accept pending Abort offer from opponent */
14511
14512     if (appData.icsActive) {
14513         SendToICS(ics_prefix);
14514         SendToICS("abort\n");
14515     } else {
14516         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
14517     }
14518 }
14519
14520 void
14521 ResignEvent ()
14522 {
14523     /* Resign.  You can do this even if it's not your turn. */
14524
14525     if (appData.icsActive) {
14526         SendToICS(ics_prefix);
14527         SendToICS("resign\n");
14528     } else {
14529         switch (gameMode) {
14530           case MachinePlaysWhite:
14531             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14532             break;
14533           case MachinePlaysBlack:
14534             GameEnds(BlackWins, "White resigns", GE_PLAYER);
14535             break;
14536           case EditGame:
14537             if (cmailMsgLoaded) {
14538                 TruncateGame();
14539                 if (WhiteOnMove(cmailOldMove)) {
14540                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
14541                 } else {
14542                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14543                 }
14544                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
14545             }
14546             break;
14547           default:
14548             break;
14549         }
14550     }
14551 }
14552
14553
14554 void
14555 StopObservingEvent ()
14556 {
14557     /* Stop observing current games */
14558     SendToICS(ics_prefix);
14559     SendToICS("unobserve\n");
14560 }
14561
14562 void
14563 StopExaminingEvent ()
14564 {
14565     /* Stop observing current game */
14566     SendToICS(ics_prefix);
14567     SendToICS("unexamine\n");
14568 }
14569
14570 void
14571 ForwardInner (int target)
14572 {
14573     int limit; int oldSeekGraphUp = seekGraphUp;
14574
14575     if (appData.debugMode)
14576         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
14577                 target, currentMove, forwardMostMove);
14578
14579     if (gameMode == EditPosition)
14580       return;
14581
14582     seekGraphUp = FALSE;
14583     MarkTargetSquares(1);
14584
14585     if (gameMode == PlayFromGameFile && !pausing)
14586       PauseEvent();
14587
14588     if (gameMode == IcsExamining && pausing)
14589       limit = pauseExamForwardMostMove;
14590     else
14591       limit = forwardMostMove;
14592
14593     if (target > limit) target = limit;
14594
14595     if (target > 0 && moveList[target - 1][0]) {
14596         int fromX, fromY, toX, toY;
14597         toX = moveList[target - 1][2] - AAA;
14598         toY = moveList[target - 1][3] - ONE;
14599         if (moveList[target - 1][1] == '@') {
14600             if (appData.highlightLastMove) {
14601                 SetHighlights(-1, -1, toX, toY);
14602             }
14603         } else {
14604             fromX = moveList[target - 1][0] - AAA;
14605             fromY = moveList[target - 1][1] - ONE;
14606             if (target == currentMove + 1) {
14607                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
14608             }
14609             if (appData.highlightLastMove) {
14610                 SetHighlights(fromX, fromY, toX, toY);
14611             }
14612         }
14613     }
14614     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14615         gameMode == Training || gameMode == PlayFromGameFile ||
14616         gameMode == AnalyzeFile) {
14617         while (currentMove < target) {
14618             if(second.analyzing) SendMoveToProgram(currentMove, &second);
14619             SendMoveToProgram(currentMove++, &first);
14620         }
14621     } else {
14622         currentMove = target;
14623     }
14624
14625     if (gameMode == EditGame || gameMode == EndOfGame) {
14626         whiteTimeRemaining = timeRemaining[0][currentMove];
14627         blackTimeRemaining = timeRemaining[1][currentMove];
14628     }
14629     DisplayBothClocks();
14630     DisplayMove(currentMove - 1);
14631     DrawPosition(oldSeekGraphUp, boards[currentMove]);
14632     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14633     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
14634         DisplayComment(currentMove - 1, commentList[currentMove]);
14635     }
14636     ClearMap(); // [HGM] exclude: invalidate map
14637 }
14638
14639
14640 void
14641 ForwardEvent ()
14642 {
14643     if (gameMode == IcsExamining && !pausing) {
14644         SendToICS(ics_prefix);
14645         SendToICS("forward\n");
14646     } else {
14647         ForwardInner(currentMove + 1);
14648     }
14649 }
14650
14651 void
14652 ToEndEvent ()
14653 {
14654     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14655         /* to optimze, we temporarily turn off analysis mode while we feed
14656          * the remaining moves to the engine. Otherwise we get analysis output
14657          * after each move.
14658          */
14659         if (first.analysisSupport) {
14660           SendToProgram("exit\nforce\n", &first);
14661           first.analyzing = FALSE;
14662         }
14663     }
14664
14665     if (gameMode == IcsExamining && !pausing) {
14666         SendToICS(ics_prefix);
14667         SendToICS("forward 999999\n");
14668     } else {
14669         ForwardInner(forwardMostMove);
14670     }
14671
14672     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14673         /* we have fed all the moves, so reactivate analysis mode */
14674         SendToProgram("analyze\n", &first);
14675         first.analyzing = TRUE;
14676         /*first.maybeThinking = TRUE;*/
14677         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14678     }
14679 }
14680
14681 void
14682 BackwardInner (int target)
14683 {
14684     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
14685
14686     if (appData.debugMode)
14687         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
14688                 target, currentMove, forwardMostMove);
14689
14690     if (gameMode == EditPosition) return;
14691     seekGraphUp = FALSE;
14692     MarkTargetSquares(1);
14693     if (currentMove <= backwardMostMove) {
14694         ClearHighlights();
14695         DrawPosition(full_redraw, boards[currentMove]);
14696         return;
14697     }
14698     if (gameMode == PlayFromGameFile && !pausing)
14699       PauseEvent();
14700
14701     if (moveList[target][0]) {
14702         int fromX, fromY, toX, toY;
14703         toX = moveList[target][2] - AAA;
14704         toY = moveList[target][3] - ONE;
14705         if (moveList[target][1] == '@') {
14706             if (appData.highlightLastMove) {
14707                 SetHighlights(-1, -1, toX, toY);
14708             }
14709         } else {
14710             fromX = moveList[target][0] - AAA;
14711             fromY = moveList[target][1] - ONE;
14712             if (target == currentMove - 1) {
14713                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
14714             }
14715             if (appData.highlightLastMove) {
14716                 SetHighlights(fromX, fromY, toX, toY);
14717             }
14718         }
14719     }
14720     if (gameMode == EditGame || gameMode==AnalyzeMode ||
14721         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
14722         while (currentMove > target) {
14723             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
14724                 // null move cannot be undone. Reload program with move history before it.
14725                 int i;
14726                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
14727                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
14728                 }
14729                 SendBoard(&first, i); 
14730               if(second.analyzing) SendBoard(&second, i);
14731                 for(currentMove=i; currentMove<target; currentMove++) {
14732                     SendMoveToProgram(currentMove, &first);
14733                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
14734                 }
14735                 break;
14736             }
14737             SendToBoth("undo\n");
14738             currentMove--;
14739         }
14740     } else {
14741         currentMove = target;
14742     }
14743
14744     if (gameMode == EditGame || gameMode == EndOfGame) {
14745         whiteTimeRemaining = timeRemaining[0][currentMove];
14746         blackTimeRemaining = timeRemaining[1][currentMove];
14747     }
14748     DisplayBothClocks();
14749     DisplayMove(currentMove - 1);
14750     DrawPosition(full_redraw, boards[currentMove]);
14751     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14752     // [HGM] PV info: routine tests if comment empty
14753     DisplayComment(currentMove - 1, commentList[currentMove]);
14754     ClearMap(); // [HGM] exclude: invalidate map
14755 }
14756
14757 void
14758 BackwardEvent ()
14759 {
14760     if (gameMode == IcsExamining && !pausing) {
14761         SendToICS(ics_prefix);
14762         SendToICS("backward\n");
14763     } else {
14764         BackwardInner(currentMove - 1);
14765     }
14766 }
14767
14768 void
14769 ToStartEvent ()
14770 {
14771     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14772         /* to optimize, we temporarily turn off analysis mode while we undo
14773          * all the moves. Otherwise we get analysis output after each undo.
14774          */
14775         if (first.analysisSupport) {
14776           SendToProgram("exit\nforce\n", &first);
14777           first.analyzing = FALSE;
14778         }
14779     }
14780
14781     if (gameMode == IcsExamining && !pausing) {
14782         SendToICS(ics_prefix);
14783         SendToICS("backward 999999\n");
14784     } else {
14785         BackwardInner(backwardMostMove);
14786     }
14787
14788     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14789         /* we have fed all the moves, so reactivate analysis mode */
14790         SendToProgram("analyze\n", &first);
14791         first.analyzing = TRUE;
14792         /*first.maybeThinking = TRUE;*/
14793         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14794     }
14795 }
14796
14797 void
14798 ToNrEvent (int to)
14799 {
14800   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
14801   if (to >= forwardMostMove) to = forwardMostMove;
14802   if (to <= backwardMostMove) to = backwardMostMove;
14803   if (to < currentMove) {
14804     BackwardInner(to);
14805   } else {
14806     ForwardInner(to);
14807   }
14808 }
14809
14810 void
14811 RevertEvent (Boolean annotate)
14812 {
14813     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
14814         return;
14815     }
14816     if (gameMode != IcsExamining) {
14817         DisplayError(_("You are not examining a game"), 0);
14818         return;
14819     }
14820     if (pausing) {
14821         DisplayError(_("You can't revert while pausing"), 0);
14822         return;
14823     }
14824     SendToICS(ics_prefix);
14825     SendToICS("revert\n");
14826 }
14827
14828 void
14829 RetractMoveEvent ()
14830 {
14831     switch (gameMode) {
14832       case MachinePlaysWhite:
14833       case MachinePlaysBlack:
14834         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14835             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
14836             return;
14837         }
14838         if (forwardMostMove < 2) return;
14839         currentMove = forwardMostMove = forwardMostMove - 2;
14840         whiteTimeRemaining = timeRemaining[0][currentMove];
14841         blackTimeRemaining = timeRemaining[1][currentMove];
14842         DisplayBothClocks();
14843         DisplayMove(currentMove - 1);
14844         ClearHighlights();/*!! could figure this out*/
14845         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
14846         SendToProgram("remove\n", &first);
14847         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
14848         break;
14849
14850       case BeginningOfGame:
14851       default:
14852         break;
14853
14854       case IcsPlayingWhite:
14855       case IcsPlayingBlack:
14856         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
14857             SendToICS(ics_prefix);
14858             SendToICS("takeback 2\n");
14859         } else {
14860             SendToICS(ics_prefix);
14861             SendToICS("takeback 1\n");
14862         }
14863         break;
14864     }
14865 }
14866
14867 void
14868 MoveNowEvent ()
14869 {
14870     ChessProgramState *cps;
14871
14872     switch (gameMode) {
14873       case MachinePlaysWhite:
14874         if (!WhiteOnMove(forwardMostMove)) {
14875             DisplayError(_("It is your turn"), 0);
14876             return;
14877         }
14878         cps = &first;
14879         break;
14880       case MachinePlaysBlack:
14881         if (WhiteOnMove(forwardMostMove)) {
14882             DisplayError(_("It is your turn"), 0);
14883             return;
14884         }
14885         cps = &first;
14886         break;
14887       case TwoMachinesPlay:
14888         if (WhiteOnMove(forwardMostMove) ==
14889             (first.twoMachinesColor[0] == 'w')) {
14890             cps = &first;
14891         } else {
14892             cps = &second;
14893         }
14894         break;
14895       case BeginningOfGame:
14896       default:
14897         return;
14898     }
14899     SendToProgram("?\n", cps);
14900 }
14901
14902 void
14903 TruncateGameEvent ()
14904 {
14905     EditGameEvent();
14906     if (gameMode != EditGame) return;
14907     TruncateGame();
14908 }
14909
14910 void
14911 TruncateGame ()
14912 {
14913     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
14914     if (forwardMostMove > currentMove) {
14915         if (gameInfo.resultDetails != NULL) {
14916             free(gameInfo.resultDetails);
14917             gameInfo.resultDetails = NULL;
14918             gameInfo.result = GameUnfinished;
14919         }
14920         forwardMostMove = currentMove;
14921         HistorySet(parseList, backwardMostMove, forwardMostMove,
14922                    currentMove-1);
14923     }
14924 }
14925
14926 void
14927 HintEvent ()
14928 {
14929     if (appData.noChessProgram) return;
14930     switch (gameMode) {
14931       case MachinePlaysWhite:
14932         if (WhiteOnMove(forwardMostMove)) {
14933             DisplayError(_("Wait until your turn"), 0);
14934             return;
14935         }
14936         break;
14937       case BeginningOfGame:
14938       case MachinePlaysBlack:
14939         if (!WhiteOnMove(forwardMostMove)) {
14940             DisplayError(_("Wait until your turn"), 0);
14941             return;
14942         }
14943         break;
14944       default:
14945         DisplayError(_("No hint available"), 0);
14946         return;
14947     }
14948     SendToProgram("hint\n", &first);
14949     hintRequested = TRUE;
14950 }
14951
14952 void
14953 BookEvent ()
14954 {
14955     if (appData.noChessProgram) return;
14956     switch (gameMode) {
14957       case MachinePlaysWhite:
14958         if (WhiteOnMove(forwardMostMove)) {
14959             DisplayError(_("Wait until your turn"), 0);
14960             return;
14961         }
14962         break;
14963       case BeginningOfGame:
14964       case MachinePlaysBlack:
14965         if (!WhiteOnMove(forwardMostMove)) {
14966             DisplayError(_("Wait until your turn"), 0);
14967             return;
14968         }
14969         break;
14970       case EditPosition:
14971         EditPositionDone(TRUE);
14972         break;
14973       case TwoMachinesPlay:
14974         return;
14975       default:
14976         break;
14977     }
14978     SendToProgram("bk\n", &first);
14979     bookOutput[0] = NULLCHAR;
14980     bookRequested = TRUE;
14981 }
14982
14983 void
14984 AboutGameEvent ()
14985 {
14986     char *tags = PGNTags(&gameInfo);
14987     TagsPopUp(tags, CmailMsg());
14988     free(tags);
14989 }
14990
14991 /* end button procedures */
14992
14993 void
14994 PrintPosition (FILE *fp, int move)
14995 {
14996     int i, j;
14997
14998     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14999         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15000             char c = PieceToChar(boards[move][i][j]);
15001             fputc(c == 'x' ? '.' : c, fp);
15002             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
15003         }
15004     }
15005     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
15006       fprintf(fp, "white to play\n");
15007     else
15008       fprintf(fp, "black to play\n");
15009 }
15010
15011 void
15012 PrintOpponents (FILE *fp)
15013 {
15014     if (gameInfo.white != NULL) {
15015         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
15016     } else {
15017         fprintf(fp, "\n");
15018     }
15019 }
15020
15021 /* Find last component of program's own name, using some heuristics */
15022 void
15023 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
15024 {
15025     char *p, *q, c;
15026     int local = (strcmp(host, "localhost") == 0);
15027     while (!local && (p = strchr(prog, ';')) != NULL) {
15028         p++;
15029         while (*p == ' ') p++;
15030         prog = p;
15031     }
15032     if (*prog == '"' || *prog == '\'') {
15033         q = strchr(prog + 1, *prog);
15034     } else {
15035         q = strchr(prog, ' ');
15036     }
15037     if (q == NULL) q = prog + strlen(prog);
15038     p = q;
15039     while (p >= prog && *p != '/' && *p != '\\') p--;
15040     p++;
15041     if(p == prog && *p == '"') p++;
15042     c = *q; *q = 0;
15043     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
15044     memcpy(buf, p, q - p);
15045     buf[q - p] = NULLCHAR;
15046     if (!local) {
15047         strcat(buf, "@");
15048         strcat(buf, host);
15049     }
15050 }
15051
15052 char *
15053 TimeControlTagValue ()
15054 {
15055     char buf[MSG_SIZ];
15056     if (!appData.clockMode) {
15057       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
15058     } else if (movesPerSession > 0) {
15059       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
15060     } else if (timeIncrement == 0) {
15061       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
15062     } else {
15063       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
15064     }
15065     return StrSave(buf);
15066 }
15067
15068 void
15069 SetGameInfo ()
15070 {
15071     /* This routine is used only for certain modes */
15072     VariantClass v = gameInfo.variant;
15073     ChessMove r = GameUnfinished;
15074     char *p = NULL;
15075
15076     if(keepInfo) return;
15077
15078     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
15079         r = gameInfo.result;
15080         p = gameInfo.resultDetails;
15081         gameInfo.resultDetails = NULL;
15082     }
15083     ClearGameInfo(&gameInfo);
15084     gameInfo.variant = v;
15085
15086     switch (gameMode) {
15087       case MachinePlaysWhite:
15088         gameInfo.event = StrSave( appData.pgnEventHeader );
15089         gameInfo.site = StrSave(HostName());
15090         gameInfo.date = PGNDate();
15091         gameInfo.round = StrSave("-");
15092         gameInfo.white = StrSave(first.tidy);
15093         gameInfo.black = StrSave(UserName());
15094         gameInfo.timeControl = TimeControlTagValue();
15095         break;
15096
15097       case MachinePlaysBlack:
15098         gameInfo.event = StrSave( appData.pgnEventHeader );
15099         gameInfo.site = StrSave(HostName());
15100         gameInfo.date = PGNDate();
15101         gameInfo.round = StrSave("-");
15102         gameInfo.white = StrSave(UserName());
15103         gameInfo.black = StrSave(first.tidy);
15104         gameInfo.timeControl = TimeControlTagValue();
15105         break;
15106
15107       case TwoMachinesPlay:
15108         gameInfo.event = StrSave( appData.pgnEventHeader );
15109         gameInfo.site = StrSave(HostName());
15110         gameInfo.date = PGNDate();
15111         if (roundNr > 0) {
15112             char buf[MSG_SIZ];
15113             snprintf(buf, MSG_SIZ, "%d", roundNr);
15114             gameInfo.round = StrSave(buf);
15115         } else {
15116             gameInfo.round = StrSave("-");
15117         }
15118         if (first.twoMachinesColor[0] == 'w') {
15119             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15120             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15121         } else {
15122             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15123             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15124         }
15125         gameInfo.timeControl = TimeControlTagValue();
15126         break;
15127
15128       case EditGame:
15129         gameInfo.event = StrSave("Edited game");
15130         gameInfo.site = StrSave(HostName());
15131         gameInfo.date = PGNDate();
15132         gameInfo.round = StrSave("-");
15133         gameInfo.white = StrSave("-");
15134         gameInfo.black = StrSave("-");
15135         gameInfo.result = r;
15136         gameInfo.resultDetails = p;
15137         break;
15138
15139       case EditPosition:
15140         gameInfo.event = StrSave("Edited position");
15141         gameInfo.site = StrSave(HostName());
15142         gameInfo.date = PGNDate();
15143         gameInfo.round = StrSave("-");
15144         gameInfo.white = StrSave("-");
15145         gameInfo.black = StrSave("-");
15146         break;
15147
15148       case IcsPlayingWhite:
15149       case IcsPlayingBlack:
15150       case IcsObserving:
15151       case IcsExamining:
15152         break;
15153
15154       case PlayFromGameFile:
15155         gameInfo.event = StrSave("Game from non-PGN file");
15156         gameInfo.site = StrSave(HostName());
15157         gameInfo.date = PGNDate();
15158         gameInfo.round = StrSave("-");
15159         gameInfo.white = StrSave("?");
15160         gameInfo.black = StrSave("?");
15161         break;
15162
15163       default:
15164         break;
15165     }
15166 }
15167
15168 void
15169 ReplaceComment (int index, char *text)
15170 {
15171     int len;
15172     char *p;
15173     float score;
15174
15175     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
15176        pvInfoList[index-1].depth == len &&
15177        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
15178        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
15179     while (*text == '\n') text++;
15180     len = strlen(text);
15181     while (len > 0 && text[len - 1] == '\n') len--;
15182
15183     if (commentList[index] != NULL)
15184       free(commentList[index]);
15185
15186     if (len == 0) {
15187         commentList[index] = NULL;
15188         return;
15189     }
15190   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
15191       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
15192       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
15193     commentList[index] = (char *) malloc(len + 2);
15194     strncpy(commentList[index], text, len);
15195     commentList[index][len] = '\n';
15196     commentList[index][len + 1] = NULLCHAR;
15197   } else {
15198     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
15199     char *p;
15200     commentList[index] = (char *) malloc(len + 7);
15201     safeStrCpy(commentList[index], "{\n", 3);
15202     safeStrCpy(commentList[index]+2, text, len+1);
15203     commentList[index][len+2] = NULLCHAR;
15204     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
15205     strcat(commentList[index], "\n}\n");
15206   }
15207 }
15208
15209 void
15210 CrushCRs (char *text)
15211 {
15212   char *p = text;
15213   char *q = text;
15214   char ch;
15215
15216   do {
15217     ch = *p++;
15218     if (ch == '\r') continue;
15219     *q++ = ch;
15220   } while (ch != '\0');
15221 }
15222
15223 void
15224 AppendComment (int index, char *text, Boolean addBraces)
15225 /* addBraces  tells if we should add {} */
15226 {
15227     int oldlen, len;
15228     char *old;
15229
15230 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
15231     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
15232
15233     CrushCRs(text);
15234     while (*text == '\n') text++;
15235     len = strlen(text);
15236     while (len > 0 && text[len - 1] == '\n') len--;
15237     text[len] = NULLCHAR;
15238
15239     if (len == 0) return;
15240
15241     if (commentList[index] != NULL) {
15242       Boolean addClosingBrace = addBraces;
15243         old = commentList[index];
15244         oldlen = strlen(old);
15245         while(commentList[index][oldlen-1] ==  '\n')
15246           commentList[index][--oldlen] = NULLCHAR;
15247         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
15248         safeStrCpy(commentList[index], old, oldlen + len + 6);
15249         free(old);
15250         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
15251         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
15252           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
15253           while (*text == '\n') { text++; len--; }
15254           commentList[index][--oldlen] = NULLCHAR;
15255       }
15256         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
15257         else          strcat(commentList[index], "\n");
15258         strcat(commentList[index], text);
15259         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
15260         else          strcat(commentList[index], "\n");
15261     } else {
15262         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
15263         if(addBraces)
15264           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
15265         else commentList[index][0] = NULLCHAR;
15266         strcat(commentList[index], text);
15267         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
15268         if(addBraces == TRUE) strcat(commentList[index], "}\n");
15269     }
15270 }
15271
15272 static char *
15273 FindStr (char * text, char * sub_text)
15274 {
15275     char * result = strstr( text, sub_text );
15276
15277     if( result != NULL ) {
15278         result += strlen( sub_text );
15279     }
15280
15281     return result;
15282 }
15283
15284 /* [AS] Try to extract PV info from PGN comment */
15285 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
15286 char *
15287 GetInfoFromComment (int index, char * text)
15288 {
15289     char * sep = text, *p;
15290
15291     if( text != NULL && index > 0 ) {
15292         int score = 0;
15293         int depth = 0;
15294         int time = -1, sec = 0, deci;
15295         char * s_eval = FindStr( text, "[%eval " );
15296         char * s_emt = FindStr( text, "[%emt " );
15297
15298         if( s_eval != NULL || s_emt != NULL ) {
15299             /* New style */
15300             char delim;
15301
15302             if( s_eval != NULL ) {
15303                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
15304                     return text;
15305                 }
15306
15307                 if( delim != ']' ) {
15308                     return text;
15309                 }
15310             }
15311
15312             if( s_emt != NULL ) {
15313             }
15314                 return text;
15315         }
15316         else {
15317             /* We expect something like: [+|-]nnn.nn/dd */
15318             int score_lo = 0;
15319
15320             if(*text != '{') return text; // [HGM] braces: must be normal comment
15321
15322             sep = strchr( text, '/' );
15323             if( sep == NULL || sep < (text+4) ) {
15324                 return text;
15325             }
15326
15327             p = text;
15328             if(p[1] == '(') { // comment starts with PV
15329                p = strchr(p, ')'); // locate end of PV
15330                if(p == NULL || sep < p+5) return text;
15331                // at this point we have something like "{(.*) +0.23/6 ..."
15332                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
15333                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
15334                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
15335             }
15336             time = -1; sec = -1; deci = -1;
15337             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
15338                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
15339                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
15340                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
15341                 return text;
15342             }
15343
15344             if( score_lo < 0 || score_lo >= 100 ) {
15345                 return text;
15346             }
15347
15348             if(sec >= 0) time = 600*time + 10*sec; else
15349             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
15350
15351             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
15352
15353             /* [HGM] PV time: now locate end of PV info */
15354             while( *++sep >= '0' && *sep <= '9'); // strip depth
15355             if(time >= 0)
15356             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
15357             if(sec >= 0)
15358             while( *++sep >= '0' && *sep <= '9'); // strip seconds
15359             if(deci >= 0)
15360             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
15361             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
15362         }
15363
15364         if( depth <= 0 ) {
15365             return text;
15366         }
15367
15368         if( time < 0 ) {
15369             time = -1;
15370         }
15371
15372         pvInfoList[index-1].depth = depth;
15373         pvInfoList[index-1].score = score;
15374         pvInfoList[index-1].time  = 10*time; // centi-sec
15375         if(*sep == '}') *sep = 0; else *--sep = '{';
15376         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
15377     }
15378     return sep;
15379 }
15380
15381 void
15382 SendToProgram (char *message, ChessProgramState *cps)
15383 {
15384     int count, outCount, error;
15385     char buf[MSG_SIZ];
15386
15387     if (cps->pr == NoProc) return;
15388     Attention(cps);
15389
15390     if (appData.debugMode) {
15391         TimeMark now;
15392         GetTimeMark(&now);
15393         fprintf(debugFP, "%ld >%-6s: %s",
15394                 SubtractTimeMarks(&now, &programStartTime),
15395                 cps->which, message);
15396         if(serverFP)
15397             fprintf(serverFP, "%ld >%-6s: %s",
15398                 SubtractTimeMarks(&now, &programStartTime),
15399                 cps->which, message), fflush(serverFP);
15400     }
15401
15402     count = strlen(message);
15403     outCount = OutputToProcess(cps->pr, message, count, &error);
15404     if (outCount < count && !exiting
15405                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
15406       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15407       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
15408         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15409             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15410                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15411                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15412                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15413             } else {
15414                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15415                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15416                 gameInfo.result = res;
15417             }
15418             gameInfo.resultDetails = StrSave(buf);
15419         }
15420         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15421         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15422     }
15423 }
15424
15425 void
15426 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
15427 {
15428     char *end_str;
15429     char buf[MSG_SIZ];
15430     ChessProgramState *cps = (ChessProgramState *)closure;
15431
15432     if (isr != cps->isr) return; /* Killed intentionally */
15433     if (count <= 0) {
15434         if (count == 0) {
15435             RemoveInputSource(cps->isr);
15436             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
15437                     _(cps->which), cps->program);
15438             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
15439             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15440                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15441                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15442                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15443                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15444                 } else {
15445                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15446                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15447                     gameInfo.result = res;
15448                 }
15449                 gameInfo.resultDetails = StrSave(buf);
15450             }
15451             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15452             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
15453         } else {
15454             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
15455                     _(cps->which), cps->program);
15456             RemoveInputSource(cps->isr);
15457
15458             /* [AS] Program is misbehaving badly... kill it */
15459             if( count == -2 ) {
15460                 DestroyChildProcess( cps->pr, 9 );
15461                 cps->pr = NoProc;
15462             }
15463
15464             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15465         }
15466         return;
15467     }
15468
15469     if ((end_str = strchr(message, '\r')) != NULL)
15470       *end_str = NULLCHAR;
15471     if ((end_str = strchr(message, '\n')) != NULL)
15472       *end_str = NULLCHAR;
15473
15474     if (appData.debugMode) {
15475         TimeMark now; int print = 1;
15476         char *quote = ""; char c; int i;
15477
15478         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
15479                 char start = message[0];
15480                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
15481                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
15482                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
15483                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
15484                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
15485                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
15486                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
15487                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
15488                    sscanf(message, "hint: %c", &c)!=1 && 
15489                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
15490                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
15491                     print = (appData.engineComments >= 2);
15492                 }
15493                 message[0] = start; // restore original message
15494         }
15495         if(print) {
15496                 GetTimeMark(&now);
15497                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
15498                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15499                         quote,
15500                         message);
15501                 if(serverFP)
15502                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
15503                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15504                         quote,
15505                         message), fflush(serverFP);
15506         }
15507     }
15508
15509     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
15510     if (appData.icsEngineAnalyze) {
15511         if (strstr(message, "whisper") != NULL ||
15512              strstr(message, "kibitz") != NULL ||
15513             strstr(message, "tellics") != NULL) return;
15514     }
15515
15516     HandleMachineMove(message, cps);
15517 }
15518
15519
15520 void
15521 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
15522 {
15523     char buf[MSG_SIZ];
15524     int seconds;
15525
15526     if( timeControl_2 > 0 ) {
15527         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
15528             tc = timeControl_2;
15529         }
15530     }
15531     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
15532     inc /= cps->timeOdds;
15533     st  /= cps->timeOdds;
15534
15535     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
15536
15537     if (st > 0) {
15538       /* Set exact time per move, normally using st command */
15539       if (cps->stKludge) {
15540         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
15541         seconds = st % 60;
15542         if (seconds == 0) {
15543           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
15544         } else {
15545           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
15546         }
15547       } else {
15548         snprintf(buf, MSG_SIZ, "st %d\n", st);
15549       }
15550     } else {
15551       /* Set conventional or incremental time control, using level command */
15552       if (seconds == 0) {
15553         /* Note old gnuchess bug -- minutes:seconds used to not work.
15554            Fixed in later versions, but still avoid :seconds
15555            when seconds is 0. */
15556         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
15557       } else {
15558         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
15559                  seconds, inc/1000.);
15560       }
15561     }
15562     SendToProgram(buf, cps);
15563
15564     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
15565     /* Orthogonally, limit search to given depth */
15566     if (sd > 0) {
15567       if (cps->sdKludge) {
15568         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
15569       } else {
15570         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
15571       }
15572       SendToProgram(buf, cps);
15573     }
15574
15575     if(cps->nps >= 0) { /* [HGM] nps */
15576         if(cps->supportsNPS == FALSE)
15577           cps->nps = -1; // don't use if engine explicitly says not supported!
15578         else {
15579           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
15580           SendToProgram(buf, cps);
15581         }
15582     }
15583 }
15584
15585 ChessProgramState *
15586 WhitePlayer ()
15587 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
15588 {
15589     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
15590        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
15591         return &second;
15592     return &first;
15593 }
15594
15595 void
15596 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
15597 {
15598     char message[MSG_SIZ];
15599     long time, otime;
15600
15601     /* Note: this routine must be called when the clocks are stopped
15602        or when they have *just* been set or switched; otherwise
15603        it will be off by the time since the current tick started.
15604     */
15605     if (machineWhite) {
15606         time = whiteTimeRemaining / 10;
15607         otime = blackTimeRemaining / 10;
15608     } else {
15609         time = blackTimeRemaining / 10;
15610         otime = whiteTimeRemaining / 10;
15611     }
15612     /* [HGM] translate opponent's time by time-odds factor */
15613     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
15614
15615     if (time <= 0) time = 1;
15616     if (otime <= 0) otime = 1;
15617
15618     snprintf(message, MSG_SIZ, "time %ld\n", time);
15619     SendToProgram(message, cps);
15620
15621     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
15622     SendToProgram(message, cps);
15623 }
15624
15625 int
15626 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15627 {
15628   char buf[MSG_SIZ];
15629   int len = strlen(name);
15630   int val;
15631
15632   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15633     (*p) += len + 1;
15634     sscanf(*p, "%d", &val);
15635     *loc = (val != 0);
15636     while (**p && **p != ' ')
15637       (*p)++;
15638     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15639     SendToProgram(buf, cps);
15640     return TRUE;
15641   }
15642   return FALSE;
15643 }
15644
15645 int
15646 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15647 {
15648   char buf[MSG_SIZ];
15649   int len = strlen(name);
15650   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15651     (*p) += len + 1;
15652     sscanf(*p, "%d", loc);
15653     while (**p && **p != ' ') (*p)++;
15654     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15655     SendToProgram(buf, cps);
15656     return TRUE;
15657   }
15658   return FALSE;
15659 }
15660
15661 int
15662 StringFeature (char **p, char *name, char loc[], ChessProgramState *cps)
15663 {
15664   char buf[MSG_SIZ];
15665   int len = strlen(name);
15666   if (strncmp((*p), name, len) == 0
15667       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
15668     (*p) += len + 2;
15669     sscanf(*p, "%[^\"]", loc);
15670     while (**p && **p != '\"') (*p)++;
15671     if (**p == '\"') (*p)++;
15672     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15673     SendToProgram(buf, cps);
15674     return TRUE;
15675   }
15676   return FALSE;
15677 }
15678
15679 int
15680 ParseOption (Option *opt, ChessProgramState *cps)
15681 // [HGM] options: process the string that defines an engine option, and determine
15682 // name, type, default value, and allowed value range
15683 {
15684         char *p, *q, buf[MSG_SIZ];
15685         int n, min = (-1)<<31, max = 1<<31, def;
15686
15687         if(p = strstr(opt->name, " -spin ")) {
15688             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15689             if(max < min) max = min; // enforce consistency
15690             if(def < min) def = min;
15691             if(def > max) def = max;
15692             opt->value = def;
15693             opt->min = min;
15694             opt->max = max;
15695             opt->type = Spin;
15696         } else if((p = strstr(opt->name, " -slider "))) {
15697             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
15698             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15699             if(max < min) max = min; // enforce consistency
15700             if(def < min) def = min;
15701             if(def > max) def = max;
15702             opt->value = def;
15703             opt->min = min;
15704             opt->max = max;
15705             opt->type = Spin; // Slider;
15706         } else if((p = strstr(opt->name, " -string "))) {
15707             opt->textValue = p+9;
15708             opt->type = TextBox;
15709         } else if((p = strstr(opt->name, " -file "))) {
15710             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15711             opt->textValue = p+7;
15712             opt->type = FileName; // FileName;
15713         } else if((p = strstr(opt->name, " -path "))) {
15714             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15715             opt->textValue = p+7;
15716             opt->type = PathName; // PathName;
15717         } else if(p = strstr(opt->name, " -check ")) {
15718             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
15719             opt->value = (def != 0);
15720             opt->type = CheckBox;
15721         } else if(p = strstr(opt->name, " -combo ")) {
15722             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
15723             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
15724             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
15725             opt->value = n = 0;
15726             while(q = StrStr(q, " /// ")) {
15727                 n++; *q = 0;    // count choices, and null-terminate each of them
15728                 q += 5;
15729                 if(*q == '*') { // remember default, which is marked with * prefix
15730                     q++;
15731                     opt->value = n;
15732                 }
15733                 cps->comboList[cps->comboCnt++] = q;
15734             }
15735             cps->comboList[cps->comboCnt++] = NULL;
15736             opt->max = n + 1;
15737             opt->type = ComboBox;
15738         } else if(p = strstr(opt->name, " -button")) {
15739             opt->type = Button;
15740         } else if(p = strstr(opt->name, " -save")) {
15741             opt->type = SaveButton;
15742         } else return FALSE;
15743         *p = 0; // terminate option name
15744         // now look if the command-line options define a setting for this engine option.
15745         if(cps->optionSettings && cps->optionSettings[0])
15746             p = strstr(cps->optionSettings, opt->name); else p = NULL;
15747         if(p && (p == cps->optionSettings || p[-1] == ',')) {
15748           snprintf(buf, MSG_SIZ, "option %s", p);
15749                 if(p = strstr(buf, ",")) *p = 0;
15750                 if(q = strchr(buf, '=')) switch(opt->type) {
15751                     case ComboBox:
15752                         for(n=0; n<opt->max; n++)
15753                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
15754                         break;
15755                     case TextBox:
15756                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
15757                         break;
15758                     case Spin:
15759                     case CheckBox:
15760                         opt->value = atoi(q+1);
15761                     default:
15762                         break;
15763                 }
15764                 strcat(buf, "\n");
15765                 SendToProgram(buf, cps);
15766         }
15767         return TRUE;
15768 }
15769
15770 void
15771 FeatureDone (ChessProgramState *cps, int val)
15772 {
15773   DelayedEventCallback cb = GetDelayedEvent();
15774   if ((cb == InitBackEnd3 && cps == &first) ||
15775       (cb == SettingsMenuIfReady && cps == &second) ||
15776       (cb == LoadEngine) ||
15777       (cb == TwoMachinesEventIfReady)) {
15778     CancelDelayedEvent();
15779     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
15780   }
15781   cps->initDone = val;
15782 }
15783
15784 /* Parse feature command from engine */
15785 void
15786 ParseFeatures (char *args, ChessProgramState *cps)
15787 {
15788   char *p = args;
15789   char *q;
15790   int val;
15791   char buf[MSG_SIZ];
15792
15793   for (;;) {
15794     while (*p == ' ') p++;
15795     if (*p == NULLCHAR) return;
15796
15797     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
15798     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
15799     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
15800     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
15801     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
15802     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
15803     if (BoolFeature(&p, "reuse", &val, cps)) {
15804       /* Engine can disable reuse, but can't enable it if user said no */
15805       if (!val) cps->reuse = FALSE;
15806       continue;
15807     }
15808     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
15809     if (StringFeature(&p, "myname", cps->tidy, cps)) {
15810       if (gameMode == TwoMachinesPlay) {
15811         DisplayTwoMachinesTitle();
15812       } else {
15813         DisplayTitle("");
15814       }
15815       continue;
15816     }
15817     if (StringFeature(&p, "variants", cps->variants, cps)) continue;
15818     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
15819     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
15820     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
15821     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
15822     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
15823     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
15824     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
15825     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
15826     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
15827     if (IntFeature(&p, "done", &val, cps)) {
15828       FeatureDone(cps, val);
15829       continue;
15830     }
15831     /* Added by Tord: */
15832     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
15833     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
15834     /* End of additions by Tord */
15835
15836     /* [HGM] added features: */
15837     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
15838     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
15839     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
15840     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
15841     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
15842     if (StringFeature(&p, "egt", cps->egtFormats, cps)) continue;
15843     if (StringFeature(&p, "option", buf, cps)) {
15844         FREE(cps->option[cps->nrOptions].name);
15845         cps->option[cps->nrOptions].name = malloc(MSG_SIZ);
15846         safeStrCpy(cps->option[cps->nrOptions].name, buf, MSG_SIZ);
15847         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
15848           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
15849             SendToProgram(buf, cps);
15850             continue;
15851         }
15852         if(cps->nrOptions >= MAX_OPTIONS) {
15853             cps->nrOptions--;
15854             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
15855             DisplayError(buf, 0);
15856         }
15857         continue;
15858     }
15859     /* End of additions by HGM */
15860
15861     /* unknown feature: complain and skip */
15862     q = p;
15863     while (*q && *q != '=') q++;
15864     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
15865     SendToProgram(buf, cps);
15866     p = q;
15867     if (*p == '=') {
15868       p++;
15869       if (*p == '\"') {
15870         p++;
15871         while (*p && *p != '\"') p++;
15872         if (*p == '\"') p++;
15873       } else {
15874         while (*p && *p != ' ') p++;
15875       }
15876     }
15877   }
15878
15879 }
15880
15881 void
15882 PeriodicUpdatesEvent (int newState)
15883 {
15884     if (newState == appData.periodicUpdates)
15885       return;
15886
15887     appData.periodicUpdates=newState;
15888
15889     /* Display type changes, so update it now */
15890 //    DisplayAnalysis();
15891
15892     /* Get the ball rolling again... */
15893     if (newState) {
15894         AnalysisPeriodicEvent(1);
15895         StartAnalysisClock();
15896     }
15897 }
15898
15899 void
15900 PonderNextMoveEvent (int newState)
15901 {
15902     if (newState == appData.ponderNextMove) return;
15903     if (gameMode == EditPosition) EditPositionDone(TRUE);
15904     if (newState) {
15905         SendToProgram("hard\n", &first);
15906         if (gameMode == TwoMachinesPlay) {
15907             SendToProgram("hard\n", &second);
15908         }
15909     } else {
15910         SendToProgram("easy\n", &first);
15911         thinkOutput[0] = NULLCHAR;
15912         if (gameMode == TwoMachinesPlay) {
15913             SendToProgram("easy\n", &second);
15914         }
15915     }
15916     appData.ponderNextMove = newState;
15917 }
15918
15919 void
15920 NewSettingEvent (int option, int *feature, char *command, int value)
15921 {
15922     char buf[MSG_SIZ];
15923
15924     if (gameMode == EditPosition) EditPositionDone(TRUE);
15925     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
15926     if(feature == NULL || *feature) SendToProgram(buf, &first);
15927     if (gameMode == TwoMachinesPlay) {
15928         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
15929     }
15930 }
15931
15932 void
15933 ShowThinkingEvent ()
15934 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
15935 {
15936     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
15937     int newState = appData.showThinking
15938         // [HGM] thinking: other features now need thinking output as well
15939         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
15940
15941     if (oldState == newState) return;
15942     oldState = newState;
15943     if (gameMode == EditPosition) EditPositionDone(TRUE);
15944     if (oldState) {
15945         SendToProgram("post\n", &first);
15946         if (gameMode == TwoMachinesPlay) {
15947             SendToProgram("post\n", &second);
15948         }
15949     } else {
15950         SendToProgram("nopost\n", &first);
15951         thinkOutput[0] = NULLCHAR;
15952         if (gameMode == TwoMachinesPlay) {
15953             SendToProgram("nopost\n", &second);
15954         }
15955     }
15956 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
15957 }
15958
15959 void
15960 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
15961 {
15962   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
15963   if (pr == NoProc) return;
15964   AskQuestion(title, question, replyPrefix, pr);
15965 }
15966
15967 void
15968 TypeInEvent (char firstChar)
15969 {
15970     if ((gameMode == BeginningOfGame && !appData.icsActive) || 
15971         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
15972         gameMode == AnalyzeMode || gameMode == EditGame || 
15973         gameMode == EditPosition || gameMode == IcsExamining ||
15974         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
15975         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
15976                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
15977                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
15978         gameMode == Training) PopUpMoveDialog(firstChar);
15979 }
15980
15981 void
15982 TypeInDoneEvent (char *move)
15983 {
15984         Board board;
15985         int n, fromX, fromY, toX, toY;
15986         char promoChar;
15987         ChessMove moveType;
15988
15989         // [HGM] FENedit
15990         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
15991                 EditPositionPasteFEN(move);
15992                 return;
15993         }
15994         // [HGM] movenum: allow move number to be typed in any mode
15995         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
15996           ToNrEvent(2*n-1);
15997           return;
15998         }
15999         // undocumented kludge: allow command-line option to be typed in!
16000         // (potentially fatal, and does not implement the effect of the option.)
16001         // should only be used for options that are values on which future decisions will be made,
16002         // and definitely not on options that would be used during initialization.
16003         if(strstr(move, "!!! -") == move) {
16004             ParseArgsFromString(move+4);
16005             return;
16006         }
16007
16008       if (gameMode != EditGame && currentMove != forwardMostMove && 
16009         gameMode != Training) {
16010         DisplayMoveError(_("Displayed move is not current"));
16011       } else {
16012         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
16013           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
16014         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
16015         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
16016           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
16017           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     
16018         } else {
16019           DisplayMoveError(_("Could not parse move"));
16020         }
16021       }
16022 }
16023
16024 void
16025 DisplayMove (int moveNumber)
16026 {
16027     char message[MSG_SIZ];
16028     char res[MSG_SIZ];
16029     char cpThinkOutput[MSG_SIZ];
16030
16031     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
16032
16033     if (moveNumber == forwardMostMove - 1 ||
16034         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16035
16036         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
16037
16038         if (strchr(cpThinkOutput, '\n')) {
16039             *strchr(cpThinkOutput, '\n') = NULLCHAR;
16040         }
16041     } else {
16042         *cpThinkOutput = NULLCHAR;
16043     }
16044
16045     /* [AS] Hide thinking from human user */
16046     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
16047         *cpThinkOutput = NULLCHAR;
16048         if( thinkOutput[0] != NULLCHAR ) {
16049             int i;
16050
16051             for( i=0; i<=hiddenThinkOutputState; i++ ) {
16052                 cpThinkOutput[i] = '.';
16053             }
16054             cpThinkOutput[i] = NULLCHAR;
16055             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
16056         }
16057     }
16058
16059     if (moveNumber == forwardMostMove - 1 &&
16060         gameInfo.resultDetails != NULL) {
16061         if (gameInfo.resultDetails[0] == NULLCHAR) {
16062           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
16063         } else {
16064           snprintf(res, MSG_SIZ, " {%s} %s",
16065                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
16066         }
16067     } else {
16068         res[0] = NULLCHAR;
16069     }
16070
16071     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16072         DisplayMessage(res, cpThinkOutput);
16073     } else {
16074       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
16075                 WhiteOnMove(moveNumber) ? " " : ".. ",
16076                 parseList[moveNumber], res);
16077         DisplayMessage(message, cpThinkOutput);
16078     }
16079 }
16080
16081 void
16082 DisplayComment (int moveNumber, char *text)
16083 {
16084     char title[MSG_SIZ];
16085
16086     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16087       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
16088     } else {
16089       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
16090               WhiteOnMove(moveNumber) ? " " : ".. ",
16091               parseList[moveNumber]);
16092     }
16093     if (text != NULL && (appData.autoDisplayComment || commentUp))
16094         CommentPopUp(title, text);
16095 }
16096
16097 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
16098  * might be busy thinking or pondering.  It can be omitted if your
16099  * gnuchess is configured to stop thinking immediately on any user
16100  * input.  However, that gnuchess feature depends on the FIONREAD
16101  * ioctl, which does not work properly on some flavors of Unix.
16102  */
16103 void
16104 Attention (ChessProgramState *cps)
16105 {
16106 #if ATTENTION
16107     if (!cps->useSigint) return;
16108     if (appData.noChessProgram || (cps->pr == NoProc)) return;
16109     switch (gameMode) {
16110       case MachinePlaysWhite:
16111       case MachinePlaysBlack:
16112       case TwoMachinesPlay:
16113       case IcsPlayingWhite:
16114       case IcsPlayingBlack:
16115       case AnalyzeMode:
16116       case AnalyzeFile:
16117         /* Skip if we know it isn't thinking */
16118         if (!cps->maybeThinking) return;
16119         if (appData.debugMode)
16120           fprintf(debugFP, "Interrupting %s\n", cps->which);
16121         InterruptChildProcess(cps->pr);
16122         cps->maybeThinking = FALSE;
16123         break;
16124       default:
16125         break;
16126     }
16127 #endif /*ATTENTION*/
16128 }
16129
16130 int
16131 CheckFlags ()
16132 {
16133     if (whiteTimeRemaining <= 0) {
16134         if (!whiteFlag) {
16135             whiteFlag = TRUE;
16136             if (appData.icsActive) {
16137                 if (appData.autoCallFlag &&
16138                     gameMode == IcsPlayingBlack && !blackFlag) {
16139                   SendToICS(ics_prefix);
16140                   SendToICS("flag\n");
16141                 }
16142             } else {
16143                 if (blackFlag) {
16144                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16145                 } else {
16146                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
16147                     if (appData.autoCallFlag) {
16148                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
16149                         return TRUE;
16150                     }
16151                 }
16152             }
16153         }
16154     }
16155     if (blackTimeRemaining <= 0) {
16156         if (!blackFlag) {
16157             blackFlag = TRUE;
16158             if (appData.icsActive) {
16159                 if (appData.autoCallFlag &&
16160                     gameMode == IcsPlayingWhite && !whiteFlag) {
16161                   SendToICS(ics_prefix);
16162                   SendToICS("flag\n");
16163                 }
16164             } else {
16165                 if (whiteFlag) {
16166                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16167                 } else {
16168                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
16169                     if (appData.autoCallFlag) {
16170                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
16171                         return TRUE;
16172                     }
16173                 }
16174             }
16175         }
16176     }
16177     return FALSE;
16178 }
16179
16180 void
16181 CheckTimeControl ()
16182 {
16183     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
16184         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
16185
16186     /*
16187      * add time to clocks when time control is achieved ([HGM] now also used for increment)
16188      */
16189     if ( !WhiteOnMove(forwardMostMove) ) {
16190         /* White made time control */
16191         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
16192         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
16193         /* [HGM] time odds: correct new time quota for time odds! */
16194                                             / WhitePlayer()->timeOdds;
16195         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
16196     } else {
16197         lastBlack -= blackTimeRemaining;
16198         /* Black made time control */
16199         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
16200                                             / WhitePlayer()->other->timeOdds;
16201         lastWhite = whiteTimeRemaining;
16202     }
16203 }
16204
16205 void
16206 DisplayBothClocks ()
16207 {
16208     int wom = gameMode == EditPosition ?
16209       !blackPlaysFirst : WhiteOnMove(currentMove);
16210     DisplayWhiteClock(whiteTimeRemaining, wom);
16211     DisplayBlackClock(blackTimeRemaining, !wom);
16212 }
16213
16214
16215 /* Timekeeping seems to be a portability nightmare.  I think everyone
16216    has ftime(), but I'm really not sure, so I'm including some ifdefs
16217    to use other calls if you don't.  Clocks will be less accurate if
16218    you have neither ftime nor gettimeofday.
16219 */
16220
16221 /* VS 2008 requires the #include outside of the function */
16222 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
16223 #include <sys/timeb.h>
16224 #endif
16225
16226 /* Get the current time as a TimeMark */
16227 void
16228 GetTimeMark (TimeMark *tm)
16229 {
16230 #if HAVE_GETTIMEOFDAY
16231
16232     struct timeval timeVal;
16233     struct timezone timeZone;
16234
16235     gettimeofday(&timeVal, &timeZone);
16236     tm->sec = (long) timeVal.tv_sec;
16237     tm->ms = (int) (timeVal.tv_usec / 1000L);
16238
16239 #else /*!HAVE_GETTIMEOFDAY*/
16240 #if HAVE_FTIME
16241
16242 // include <sys/timeb.h> / moved to just above start of function
16243     struct timeb timeB;
16244
16245     ftime(&timeB);
16246     tm->sec = (long) timeB.time;
16247     tm->ms = (int) timeB.millitm;
16248
16249 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
16250     tm->sec = (long) time(NULL);
16251     tm->ms = 0;
16252 #endif
16253 #endif
16254 }
16255
16256 /* Return the difference in milliseconds between two
16257    time marks.  We assume the difference will fit in a long!
16258 */
16259 long
16260 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
16261 {
16262     return 1000L*(tm2->sec - tm1->sec) +
16263            (long) (tm2->ms - tm1->ms);
16264 }
16265
16266
16267 /*
16268  * Code to manage the game clocks.
16269  *
16270  * In tournament play, black starts the clock and then white makes a move.
16271  * We give the human user a slight advantage if he is playing white---the
16272  * clocks don't run until he makes his first move, so it takes zero time.
16273  * Also, we don't account for network lag, so we could get out of sync
16274  * with GNU Chess's clock -- but then, referees are always right.
16275  */
16276
16277 static TimeMark tickStartTM;
16278 static long intendedTickLength;
16279
16280 long
16281 NextTickLength (long timeRemaining)
16282 {
16283     long nominalTickLength, nextTickLength;
16284
16285     if (timeRemaining > 0L && timeRemaining <= 10000L)
16286       nominalTickLength = 100L;
16287     else
16288       nominalTickLength = 1000L;
16289     nextTickLength = timeRemaining % nominalTickLength;
16290     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
16291
16292     return nextTickLength;
16293 }
16294
16295 /* Adjust clock one minute up or down */
16296 void
16297 AdjustClock (Boolean which, int dir)
16298 {
16299     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
16300     if(which) blackTimeRemaining += 60000*dir;
16301     else      whiteTimeRemaining += 60000*dir;
16302     DisplayBothClocks();
16303     adjustedClock = TRUE;
16304 }
16305
16306 /* Stop clocks and reset to a fresh time control */
16307 void
16308 ResetClocks ()
16309 {
16310     (void) StopClockTimer();
16311     if (appData.icsActive) {
16312         whiteTimeRemaining = blackTimeRemaining = 0;
16313     } else if (searchTime) {
16314         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16315         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16316     } else { /* [HGM] correct new time quote for time odds */
16317         whiteTC = blackTC = fullTimeControlString;
16318         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
16319         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
16320     }
16321     if (whiteFlag || blackFlag) {
16322         DisplayTitle("");
16323         whiteFlag = blackFlag = FALSE;
16324     }
16325     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
16326     DisplayBothClocks();
16327     adjustedClock = FALSE;
16328 }
16329
16330 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
16331
16332 /* Decrement running clock by amount of time that has passed */
16333 void
16334 DecrementClocks ()
16335 {
16336     long timeRemaining;
16337     long lastTickLength, fudge;
16338     TimeMark now;
16339
16340     if (!appData.clockMode) return;
16341     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
16342
16343     GetTimeMark(&now);
16344
16345     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16346
16347     /* Fudge if we woke up a little too soon */
16348     fudge = intendedTickLength - lastTickLength;
16349     if (fudge < 0 || fudge > FUDGE) fudge = 0;
16350
16351     if (WhiteOnMove(forwardMostMove)) {
16352         if(whiteNPS >= 0) lastTickLength = 0;
16353         timeRemaining = whiteTimeRemaining -= lastTickLength;
16354         if(timeRemaining < 0 && !appData.icsActive) {
16355             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
16356             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
16357                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
16358                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
16359             }
16360         }
16361         DisplayWhiteClock(whiteTimeRemaining - fudge,
16362                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16363     } else {
16364         if(blackNPS >= 0) lastTickLength = 0;
16365         timeRemaining = blackTimeRemaining -= lastTickLength;
16366         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
16367             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
16368             if(suddenDeath) {
16369                 blackStartMove = forwardMostMove;
16370                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
16371             }
16372         }
16373         DisplayBlackClock(blackTimeRemaining - fudge,
16374                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16375     }
16376     if (CheckFlags()) return;
16377
16378     if(twoBoards) { // count down secondary board's clocks as well
16379         activePartnerTime -= lastTickLength;
16380         partnerUp = 1;
16381         if(activePartner == 'W')
16382             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
16383         else
16384             DisplayBlackClock(activePartnerTime, TRUE);
16385         partnerUp = 0;
16386     }
16387
16388     tickStartTM = now;
16389     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
16390     StartClockTimer(intendedTickLength);
16391
16392     /* if the time remaining has fallen below the alarm threshold, sound the
16393      * alarm. if the alarm has sounded and (due to a takeback or time control
16394      * with increment) the time remaining has increased to a level above the
16395      * threshold, reset the alarm so it can sound again.
16396      */
16397
16398     if (appData.icsActive && appData.icsAlarm) {
16399
16400         /* make sure we are dealing with the user's clock */
16401         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
16402                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
16403            )) return;
16404
16405         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
16406             alarmSounded = FALSE;
16407         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
16408             PlayAlarmSound();
16409             alarmSounded = TRUE;
16410         }
16411     }
16412 }
16413
16414
16415 /* A player has just moved, so stop the previously running
16416    clock and (if in clock mode) start the other one.
16417    We redisplay both clocks in case we're in ICS mode, because
16418    ICS gives us an update to both clocks after every move.
16419    Note that this routine is called *after* forwardMostMove
16420    is updated, so the last fractional tick must be subtracted
16421    from the color that is *not* on move now.
16422 */
16423 void
16424 SwitchClocks (int newMoveNr)
16425 {
16426     long lastTickLength;
16427     TimeMark now;
16428     int flagged = FALSE;
16429
16430     GetTimeMark(&now);
16431
16432     if (StopClockTimer() && appData.clockMode) {
16433         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16434         if (!WhiteOnMove(forwardMostMove)) {
16435             if(blackNPS >= 0) lastTickLength = 0;
16436             blackTimeRemaining -= lastTickLength;
16437            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16438 //         if(pvInfoList[forwardMostMove].time == -1)
16439                  pvInfoList[forwardMostMove].time =               // use GUI time
16440                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
16441         } else {
16442            if(whiteNPS >= 0) lastTickLength = 0;
16443            whiteTimeRemaining -= lastTickLength;
16444            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16445 //         if(pvInfoList[forwardMostMove].time == -1)
16446                  pvInfoList[forwardMostMove].time =
16447                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
16448         }
16449         flagged = CheckFlags();
16450     }
16451     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
16452     CheckTimeControl();
16453
16454     if (flagged || !appData.clockMode) return;
16455
16456     switch (gameMode) {
16457       case MachinePlaysBlack:
16458       case MachinePlaysWhite:
16459       case BeginningOfGame:
16460         if (pausing) return;
16461         break;
16462
16463       case EditGame:
16464       case PlayFromGameFile:
16465       case IcsExamining:
16466         return;
16467
16468       default:
16469         break;
16470     }
16471
16472     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
16473         if(WhiteOnMove(forwardMostMove))
16474              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16475         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16476     }
16477
16478     tickStartTM = now;
16479     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16480       whiteTimeRemaining : blackTimeRemaining);
16481     StartClockTimer(intendedTickLength);
16482 }
16483
16484
16485 /* Stop both clocks */
16486 void
16487 StopClocks ()
16488 {
16489     long lastTickLength;
16490     TimeMark now;
16491
16492     if (!StopClockTimer()) return;
16493     if (!appData.clockMode) return;
16494
16495     GetTimeMark(&now);
16496
16497     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16498     if (WhiteOnMove(forwardMostMove)) {
16499         if(whiteNPS >= 0) lastTickLength = 0;
16500         whiteTimeRemaining -= lastTickLength;
16501         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
16502     } else {
16503         if(blackNPS >= 0) lastTickLength = 0;
16504         blackTimeRemaining -= lastTickLength;
16505         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
16506     }
16507     CheckFlags();
16508 }
16509
16510 /* Start clock of player on move.  Time may have been reset, so
16511    if clock is already running, stop and restart it. */
16512 void
16513 StartClocks ()
16514 {
16515     (void) StopClockTimer(); /* in case it was running already */
16516     DisplayBothClocks();
16517     if (CheckFlags()) return;
16518
16519     if (!appData.clockMode) return;
16520     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
16521
16522     GetTimeMark(&tickStartTM);
16523     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16524       whiteTimeRemaining : blackTimeRemaining);
16525
16526    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
16527     whiteNPS = blackNPS = -1;
16528     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
16529        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
16530         whiteNPS = first.nps;
16531     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
16532        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
16533         blackNPS = first.nps;
16534     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
16535         whiteNPS = second.nps;
16536     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
16537         blackNPS = second.nps;
16538     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
16539
16540     StartClockTimer(intendedTickLength);
16541 }
16542
16543 char *
16544 TimeString (long ms)
16545 {
16546     long second, minute, hour, day;
16547     char *sign = "";
16548     static char buf[32];
16549
16550     if (ms > 0 && ms <= 9900) {
16551       /* convert milliseconds to tenths, rounding up */
16552       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
16553
16554       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
16555       return buf;
16556     }
16557
16558     /* convert milliseconds to seconds, rounding up */
16559     /* use floating point to avoid strangeness of integer division
16560        with negative dividends on many machines */
16561     second = (long) floor(((double) (ms + 999L)) / 1000.0);
16562
16563     if (second < 0) {
16564         sign = "-";
16565         second = -second;
16566     }
16567
16568     day = second / (60 * 60 * 24);
16569     second = second % (60 * 60 * 24);
16570     hour = second / (60 * 60);
16571     second = second % (60 * 60);
16572     minute = second / 60;
16573     second = second % 60;
16574
16575     if (day > 0)
16576       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
16577               sign, day, hour, minute, second);
16578     else if (hour > 0)
16579       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
16580     else
16581       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
16582
16583     return buf;
16584 }
16585
16586
16587 /*
16588  * This is necessary because some C libraries aren't ANSI C compliant yet.
16589  */
16590 char *
16591 StrStr (char *string, char *match)
16592 {
16593     int i, length;
16594
16595     length = strlen(match);
16596
16597     for (i = strlen(string) - length; i >= 0; i--, string++)
16598       if (!strncmp(match, string, length))
16599         return string;
16600
16601     return NULL;
16602 }
16603
16604 char *
16605 StrCaseStr (char *string, char *match)
16606 {
16607     int i, j, length;
16608
16609     length = strlen(match);
16610
16611     for (i = strlen(string) - length; i >= 0; i--, string++) {
16612         for (j = 0; j < length; j++) {
16613             if (ToLower(match[j]) != ToLower(string[j]))
16614               break;
16615         }
16616         if (j == length) return string;
16617     }
16618
16619     return NULL;
16620 }
16621
16622 #ifndef _amigados
16623 int
16624 StrCaseCmp (char *s1, char *s2)
16625 {
16626     char c1, c2;
16627
16628     for (;;) {
16629         c1 = ToLower(*s1++);
16630         c2 = ToLower(*s2++);
16631         if (c1 > c2) return 1;
16632         if (c1 < c2) return -1;
16633         if (c1 == NULLCHAR) return 0;
16634     }
16635 }
16636
16637
16638 int
16639 ToLower (int c)
16640 {
16641     return isupper(c) ? tolower(c) : c;
16642 }
16643
16644
16645 int
16646 ToUpper (int c)
16647 {
16648     return islower(c) ? toupper(c) : c;
16649 }
16650 #endif /* !_amigados    */
16651
16652 char *
16653 StrSave (char *s)
16654 {
16655   char *ret;
16656
16657   if ((ret = (char *) malloc(strlen(s) + 1)))
16658     {
16659       safeStrCpy(ret, s, strlen(s)+1);
16660     }
16661   return ret;
16662 }
16663
16664 char *
16665 StrSavePtr (char *s, char **savePtr)
16666 {
16667     if (*savePtr) {
16668         free(*savePtr);
16669     }
16670     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
16671       safeStrCpy(*savePtr, s, strlen(s)+1);
16672     }
16673     return(*savePtr);
16674 }
16675
16676 char *
16677 PGNDate ()
16678 {
16679     time_t clock;
16680     struct tm *tm;
16681     char buf[MSG_SIZ];
16682
16683     clock = time((time_t *)NULL);
16684     tm = localtime(&clock);
16685     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
16686             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
16687     return StrSave(buf);
16688 }
16689
16690
16691 char *
16692 PositionToFEN (int move, char *overrideCastling)
16693 {
16694     int i, j, fromX, fromY, toX, toY;
16695     int whiteToPlay;
16696     char buf[MSG_SIZ];
16697     char *p, *q;
16698     int emptycount;
16699     ChessSquare piece;
16700
16701     whiteToPlay = (gameMode == EditPosition) ?
16702       !blackPlaysFirst : (move % 2 == 0);
16703     p = buf;
16704
16705     /* Piece placement data */
16706     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16707         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
16708         emptycount = 0;
16709         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16710             if (boards[move][i][j] == EmptySquare) {
16711                 emptycount++;
16712             } else { ChessSquare piece = boards[move][i][j];
16713                 if (emptycount > 0) {
16714                     if(emptycount<10) /* [HGM] can be >= 10 */
16715                         *p++ = '0' + emptycount;
16716                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16717                     emptycount = 0;
16718                 }
16719                 if(PieceToChar(piece) == '+') {
16720                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
16721                     *p++ = '+';
16722                     piece = (ChessSquare)(DEMOTED piece);
16723                 }
16724                 *p++ = PieceToChar(piece);
16725                 if(p[-1] == '~') {
16726                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
16727                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
16728                     *p++ = '~';
16729                 }
16730             }
16731         }
16732         if (emptycount > 0) {
16733             if(emptycount<10) /* [HGM] can be >= 10 */
16734                 *p++ = '0' + emptycount;
16735             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16736             emptycount = 0;
16737         }
16738         *p++ = '/';
16739     }
16740     *(p - 1) = ' ';
16741
16742     /* [HGM] print Crazyhouse or Shogi holdings */
16743     if( gameInfo.holdingsWidth ) {
16744         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
16745         q = p;
16746         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
16747             piece = boards[move][i][BOARD_WIDTH-1];
16748             if( piece != EmptySquare )
16749               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
16750                   *p++ = PieceToChar(piece);
16751         }
16752         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
16753             piece = boards[move][BOARD_HEIGHT-i-1][0];
16754             if( piece != EmptySquare )
16755               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
16756                   *p++ = PieceToChar(piece);
16757         }
16758
16759         if( q == p ) *p++ = '-';
16760         *p++ = ']';
16761         *p++ = ' ';
16762     }
16763
16764     /* Active color */
16765     *p++ = whiteToPlay ? 'w' : 'b';
16766     *p++ = ' ';
16767
16768   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
16769     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
16770   } else {
16771   if(nrCastlingRights) {
16772      q = p;
16773      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
16774        /* [HGM] write directly from rights */
16775            if(boards[move][CASTLING][2] != NoRights &&
16776               boards[move][CASTLING][0] != NoRights   )
16777                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
16778            if(boards[move][CASTLING][2] != NoRights &&
16779               boards[move][CASTLING][1] != NoRights   )
16780                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
16781            if(boards[move][CASTLING][5] != NoRights &&
16782               boards[move][CASTLING][3] != NoRights   )
16783                 *p++ = boards[move][CASTLING][3] + AAA;
16784            if(boards[move][CASTLING][5] != NoRights &&
16785               boards[move][CASTLING][4] != NoRights   )
16786                 *p++ = boards[move][CASTLING][4] + AAA;
16787      } else {
16788
16789         /* [HGM] write true castling rights */
16790         if( nrCastlingRights == 6 ) {
16791             int q, k=0;
16792             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
16793                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
16794             q = (boards[move][CASTLING][1] == BOARD_LEFT &&
16795                  boards[move][CASTLING][2] != NoRights  );
16796             if(gameInfo.variant == VariantSChess) { // for S-Chess, indicate all vrgin backrank pieces
16797                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_RGHT]; // count white held pieces
16798                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
16799                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
16800                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
16801             }
16802             if(q) *p++ = 'Q';
16803             k = 0;
16804             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
16805                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
16806             q = (boards[move][CASTLING][4] == BOARD_LEFT &&
16807                  boards[move][CASTLING][5] != NoRights  );
16808             if(gameInfo.variant == VariantSChess) {
16809                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_LEFT-1]; // count black held pieces
16810                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
16811                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
16812                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
16813             }
16814             if(q) *p++ = 'q';
16815         }
16816      }
16817      if (q == p) *p++ = '-'; /* No castling rights */
16818      *p++ = ' ';
16819   }
16820
16821   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16822      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16823     /* En passant target square */
16824     if (move > backwardMostMove) {
16825         fromX = moveList[move - 1][0] - AAA;
16826         fromY = moveList[move - 1][1] - ONE;
16827         toX = moveList[move - 1][2] - AAA;
16828         toY = moveList[move - 1][3] - ONE;
16829         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
16830             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
16831             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
16832             fromX == toX) {
16833             /* 2-square pawn move just happened */
16834             *p++ = toX + AAA;
16835             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16836         } else {
16837             *p++ = '-';
16838         }
16839     } else if(move == backwardMostMove) {
16840         // [HGM] perhaps we should always do it like this, and forget the above?
16841         if((signed char)boards[move][EP_STATUS] >= 0) {
16842             *p++ = boards[move][EP_STATUS] + AAA;
16843             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16844         } else {
16845             *p++ = '-';
16846         }
16847     } else {
16848         *p++ = '-';
16849     }
16850     *p++ = ' ';
16851   }
16852   }
16853
16854     /* [HGM] find reversible plies */
16855     {   int i = 0, j=move;
16856
16857         if (appData.debugMode) { int k;
16858             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
16859             for(k=backwardMostMove; k<=forwardMostMove; k++)
16860                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
16861
16862         }
16863
16864         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
16865         if( j == backwardMostMove ) i += initialRulePlies;
16866         sprintf(p, "%d ", i);
16867         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
16868     }
16869     /* Fullmove number */
16870     sprintf(p, "%d", (move / 2) + 1);
16871
16872     return StrSave(buf);
16873 }
16874
16875 Boolean
16876 ParseFEN (Board board, int *blackPlaysFirst, char *fen)
16877 {
16878     int i, j;
16879     char *p, c;
16880     int emptycount, virgin[BOARD_FILES];
16881     ChessSquare piece;
16882
16883     p = fen;
16884
16885     /* [HGM] by default clear Crazyhouse holdings, if present */
16886     if(gameInfo.holdingsWidth) {
16887        for(i=0; i<BOARD_HEIGHT; i++) {
16888            board[i][0]             = EmptySquare; /* black holdings */
16889            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
16890            board[i][1]             = (ChessSquare) 0; /* black counts */
16891            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
16892        }
16893     }
16894
16895     /* Piece placement data */
16896     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16897         j = 0;
16898         for (;;) {
16899             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
16900                 if (*p == '/') p++;
16901                 emptycount = gameInfo.boardWidth - j;
16902                 while (emptycount--)
16903                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16904                 break;
16905 #if(BOARD_FILES >= 10)
16906             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
16907                 p++; emptycount=10;
16908                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16909                 while (emptycount--)
16910                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16911 #endif
16912             } else if (isdigit(*p)) {
16913                 emptycount = *p++ - '0';
16914                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
16915                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16916                 while (emptycount--)
16917                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16918             } else if (*p == '+' || isalpha(*p)) {
16919                 if (j >= gameInfo.boardWidth) return FALSE;
16920                 if(*p=='+') {
16921                     piece = CharToPiece(*++p);
16922                     if(piece == EmptySquare) return FALSE; /* unknown piece */
16923                     piece = (ChessSquare) (PROMOTED piece ); p++;
16924                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
16925                 } else piece = CharToPiece(*p++);
16926
16927                 if(piece==EmptySquare) return FALSE; /* unknown piece */
16928                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
16929                     piece = (ChessSquare) (PROMOTED piece);
16930                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
16931                     p++;
16932                 }
16933                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
16934             } else {
16935                 return FALSE;
16936             }
16937         }
16938     }
16939     while (*p == '/' || *p == ' ') p++;
16940
16941     /* [HGM] look for Crazyhouse holdings here */
16942     while(*p==' ') p++;
16943     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
16944         if(*p == '[') p++;
16945         if(*p == '-' ) p++; /* empty holdings */ else {
16946             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
16947             /* if we would allow FEN reading to set board size, we would   */
16948             /* have to add holdings and shift the board read so far here   */
16949             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
16950                 p++;
16951                 if((int) piece >= (int) BlackPawn ) {
16952                     i = (int)piece - (int)BlackPawn;
16953                     i = PieceToNumber((ChessSquare)i);
16954                     if( i >= gameInfo.holdingsSize ) return FALSE;
16955                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
16956                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
16957                 } else {
16958                     i = (int)piece - (int)WhitePawn;
16959                     i = PieceToNumber((ChessSquare)i);
16960                     if( i >= gameInfo.holdingsSize ) return FALSE;
16961                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
16962                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
16963                 }
16964             }
16965         }
16966         if(*p == ']') p++;
16967     }
16968
16969     while(*p == ' ') p++;
16970
16971     /* Active color */
16972     c = *p++;
16973     if(appData.colorNickNames) {
16974       if( c == appData.colorNickNames[0] ) c = 'w'; else
16975       if( c == appData.colorNickNames[1] ) c = 'b';
16976     }
16977     switch (c) {
16978       case 'w':
16979         *blackPlaysFirst = FALSE;
16980         break;
16981       case 'b':
16982         *blackPlaysFirst = TRUE;
16983         break;
16984       default:
16985         return FALSE;
16986     }
16987
16988     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
16989     /* return the extra info in global variiables             */
16990
16991     /* set defaults in case FEN is incomplete */
16992     board[EP_STATUS] = EP_UNKNOWN;
16993     for(i=0; i<nrCastlingRights; i++ ) {
16994         board[CASTLING][i] =
16995             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
16996     }   /* assume possible unless obviously impossible */
16997     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
16998     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
16999     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
17000                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
17001     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
17002     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
17003     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
17004                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
17005     FENrulePlies = 0;
17006
17007     while(*p==' ') p++;
17008     if(nrCastlingRights) {
17009       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
17010       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
17011           /* castling indicator present, so default becomes no castlings */
17012           for(i=0; i<nrCastlingRights; i++ ) {
17013                  board[CASTLING][i] = NoRights;
17014           }
17015       }
17016       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
17017              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess) &&
17018              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
17019              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
17020         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
17021
17022         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
17023             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
17024             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
17025         }
17026         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
17027             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
17028         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
17029                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
17030         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
17031                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
17032         switch(c) {
17033           case'K':
17034               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
17035               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
17036               board[CASTLING][2] = whiteKingFile;
17037               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
17038               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17039               break;
17040           case'Q':
17041               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
17042               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
17043               board[CASTLING][2] = whiteKingFile;
17044               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
17045               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17046               break;
17047           case'k':
17048               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
17049               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
17050               board[CASTLING][5] = blackKingFile;
17051               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
17052               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17053               break;
17054           case'q':
17055               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
17056               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
17057               board[CASTLING][5] = blackKingFile;
17058               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
17059               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17060           case '-':
17061               break;
17062           default: /* FRC castlings */
17063               if(c >= 'a') { /* black rights */
17064                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
17065                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17066                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
17067                   if(i == BOARD_RGHT) break;
17068                   board[CASTLING][5] = i;
17069                   c -= AAA;
17070                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
17071                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
17072                   if(c > i)
17073                       board[CASTLING][3] = c;
17074                   else
17075                       board[CASTLING][4] = c;
17076               } else { /* white rights */
17077                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
17078                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17079                     if(board[0][i] == WhiteKing) break;
17080                   if(i == BOARD_RGHT) break;
17081                   board[CASTLING][2] = i;
17082                   c -= AAA - 'a' + 'A';
17083                   if(board[0][c] >= WhiteKing) break;
17084                   if(c > i)
17085                       board[CASTLING][0] = c;
17086                   else
17087                       board[CASTLING][1] = c;
17088               }
17089         }
17090       }
17091       for(i=0; i<nrCastlingRights; i++)
17092         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
17093       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = virgin[i];
17094     if (appData.debugMode) {
17095         fprintf(debugFP, "FEN castling rights:");
17096         for(i=0; i<nrCastlingRights; i++)
17097         fprintf(debugFP, " %d", board[CASTLING][i]);
17098         fprintf(debugFP, "\n");
17099     }
17100
17101       while(*p==' ') p++;
17102     }
17103
17104     /* read e.p. field in games that know e.p. capture */
17105     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17106        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
17107       if(*p=='-') {
17108         p++; board[EP_STATUS] = EP_NONE;
17109       } else {
17110          char c = *p++ - AAA;
17111
17112          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
17113          if(*p >= '0' && *p <='9') p++;
17114          board[EP_STATUS] = c;
17115       }
17116     }
17117
17118
17119     if(sscanf(p, "%d", &i) == 1) {
17120         FENrulePlies = i; /* 50-move ply counter */
17121         /* (The move number is still ignored)    */
17122     }
17123
17124     return TRUE;
17125 }
17126
17127 void
17128 EditPositionPasteFEN (char *fen)
17129 {
17130   if (fen != NULL) {
17131     Board initial_position;
17132
17133     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
17134       DisplayError(_("Bad FEN position in clipboard"), 0);
17135       return ;
17136     } else {
17137       int savedBlackPlaysFirst = blackPlaysFirst;
17138       EditPositionEvent();
17139       blackPlaysFirst = savedBlackPlaysFirst;
17140       CopyBoard(boards[0], initial_position);
17141       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
17142       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
17143       DisplayBothClocks();
17144       DrawPosition(FALSE, boards[currentMove]);
17145     }
17146   }
17147 }
17148
17149 static char cseq[12] = "\\   ";
17150
17151 Boolean
17152 set_cont_sequence (char *new_seq)
17153 {
17154     int len;
17155     Boolean ret;
17156
17157     // handle bad attempts to set the sequence
17158         if (!new_seq)
17159                 return 0; // acceptable error - no debug
17160
17161     len = strlen(new_seq);
17162     ret = (len > 0) && (len < sizeof(cseq));
17163     if (ret)
17164       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
17165     else if (appData.debugMode)
17166       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
17167     return ret;
17168 }
17169
17170 /*
17171     reformat a source message so words don't cross the width boundary.  internal
17172     newlines are not removed.  returns the wrapped size (no null character unless
17173     included in source message).  If dest is NULL, only calculate the size required
17174     for the dest buffer.  lp argument indicats line position upon entry, and it's
17175     passed back upon exit.
17176 */
17177 int
17178 wrap (char *dest, char *src, int count, int width, int *lp)
17179 {
17180     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
17181
17182     cseq_len = strlen(cseq);
17183     old_line = line = *lp;
17184     ansi = len = clen = 0;
17185
17186     for (i=0; i < count; i++)
17187     {
17188         if (src[i] == '\033')
17189             ansi = 1;
17190
17191         // if we hit the width, back up
17192         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
17193         {
17194             // store i & len in case the word is too long
17195             old_i = i, old_len = len;
17196
17197             // find the end of the last word
17198             while (i && src[i] != ' ' && src[i] != '\n')
17199             {
17200                 i--;
17201                 len--;
17202             }
17203
17204             // word too long?  restore i & len before splitting it
17205             if ((old_i-i+clen) >= width)
17206             {
17207                 i = old_i;
17208                 len = old_len;
17209             }
17210
17211             // extra space?
17212             if (i && src[i-1] == ' ')
17213                 len--;
17214
17215             if (src[i] != ' ' && src[i] != '\n')
17216             {
17217                 i--;
17218                 if (len)
17219                     len--;
17220             }
17221
17222             // now append the newline and continuation sequence
17223             if (dest)
17224                 dest[len] = '\n';
17225             len++;
17226             if (dest)
17227                 strncpy(dest+len, cseq, cseq_len);
17228             len += cseq_len;
17229             line = cseq_len;
17230             clen = cseq_len;
17231             continue;
17232         }
17233
17234         if (dest)
17235             dest[len] = src[i];
17236         len++;
17237         if (!ansi)
17238             line++;
17239         if (src[i] == '\n')
17240             line = 0;
17241         if (src[i] == 'm')
17242             ansi = 0;
17243     }
17244     if (dest && appData.debugMode)
17245     {
17246         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
17247             count, width, line, len, *lp);
17248         show_bytes(debugFP, src, count);
17249         fprintf(debugFP, "\ndest: ");
17250         show_bytes(debugFP, dest, len);
17251         fprintf(debugFP, "\n");
17252     }
17253     *lp = dest ? line : old_line;
17254
17255     return len;
17256 }
17257
17258 // [HGM] vari: routines for shelving variations
17259 Boolean modeRestore = FALSE;
17260
17261 void
17262 PushInner (int firstMove, int lastMove)
17263 {
17264         int i, j, nrMoves = lastMove - firstMove;
17265
17266         // push current tail of game on stack
17267         savedResult[storedGames] = gameInfo.result;
17268         savedDetails[storedGames] = gameInfo.resultDetails;
17269         gameInfo.resultDetails = NULL;
17270         savedFirst[storedGames] = firstMove;
17271         savedLast [storedGames] = lastMove;
17272         savedFramePtr[storedGames] = framePtr;
17273         framePtr -= nrMoves; // reserve space for the boards
17274         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
17275             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
17276             for(j=0; j<MOVE_LEN; j++)
17277                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
17278             for(j=0; j<2*MOVE_LEN; j++)
17279                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
17280             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
17281             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
17282             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
17283             pvInfoList[firstMove+i-1].depth = 0;
17284             commentList[framePtr+i] = commentList[firstMove+i];
17285             commentList[firstMove+i] = NULL;
17286         }
17287
17288         storedGames++;
17289         forwardMostMove = firstMove; // truncate game so we can start variation
17290 }
17291
17292 void
17293 PushTail (int firstMove, int lastMove)
17294 {
17295         if(appData.icsActive) { // only in local mode
17296                 forwardMostMove = currentMove; // mimic old ICS behavior
17297                 return;
17298         }
17299         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
17300
17301         PushInner(firstMove, lastMove);
17302         if(storedGames == 1) GreyRevert(FALSE);
17303         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
17304 }
17305
17306 void
17307 PopInner (Boolean annotate)
17308 {
17309         int i, j, nrMoves;
17310         char buf[8000], moveBuf[20];
17311
17312         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
17313         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
17314         nrMoves = savedLast[storedGames] - currentMove;
17315         if(annotate) {
17316                 int cnt = 10;
17317                 if(!WhiteOnMove(currentMove))
17318                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
17319                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
17320                 for(i=currentMove; i<forwardMostMove; i++) {
17321                         if(WhiteOnMove(i))
17322                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
17323                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
17324                         strcat(buf, moveBuf);
17325                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
17326                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
17327                 }
17328                 strcat(buf, ")");
17329         }
17330         for(i=1; i<=nrMoves; i++) { // copy last variation back
17331             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
17332             for(j=0; j<MOVE_LEN; j++)
17333                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
17334             for(j=0; j<2*MOVE_LEN; j++)
17335                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
17336             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
17337             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
17338             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
17339             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
17340             commentList[currentMove+i] = commentList[framePtr+i];
17341             commentList[framePtr+i] = NULL;
17342         }
17343         if(annotate) AppendComment(currentMove+1, buf, FALSE);
17344         framePtr = savedFramePtr[storedGames];
17345         gameInfo.result = savedResult[storedGames];
17346         if(gameInfo.resultDetails != NULL) {
17347             free(gameInfo.resultDetails);
17348       }
17349         gameInfo.resultDetails = savedDetails[storedGames];
17350         forwardMostMove = currentMove + nrMoves;
17351 }
17352
17353 Boolean
17354 PopTail (Boolean annotate)
17355 {
17356         if(appData.icsActive) return FALSE; // only in local mode
17357         if(!storedGames) return FALSE; // sanity
17358         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
17359
17360         PopInner(annotate);
17361         if(currentMove < forwardMostMove) ForwardEvent(); else
17362         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
17363
17364         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
17365         return TRUE;
17366 }
17367
17368 void
17369 CleanupTail ()
17370 {       // remove all shelved variations
17371         int i;
17372         for(i=0; i<storedGames; i++) {
17373             if(savedDetails[i])
17374                 free(savedDetails[i]);
17375             savedDetails[i] = NULL;
17376         }
17377         for(i=framePtr; i<MAX_MOVES; i++) {
17378                 if(commentList[i]) free(commentList[i]);
17379                 commentList[i] = NULL;
17380         }
17381         framePtr = MAX_MOVES-1;
17382         storedGames = 0;
17383 }
17384
17385 void
17386 LoadVariation (int index, char *text)
17387 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
17388         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
17389         int level = 0, move;
17390
17391         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
17392         // first find outermost bracketing variation
17393         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
17394             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
17395                 if(*p == '{') wait = '}'; else
17396                 if(*p == '[') wait = ']'; else
17397                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
17398                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
17399             }
17400             if(*p == wait) wait = NULLCHAR; // closing ]} found
17401             p++;
17402         }
17403         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
17404         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
17405         end[1] = NULLCHAR; // clip off comment beyond variation
17406         ToNrEvent(currentMove-1);
17407         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
17408         // kludge: use ParsePV() to append variation to game
17409         move = currentMove;
17410         ParsePV(start, TRUE, TRUE);
17411         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
17412         ClearPremoveHighlights();
17413         CommentPopDown();
17414         ToNrEvent(currentMove+1);
17415 }
17416