Add Themes dialog (WB)
[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 "evalgraph.h"
133 #include "gettext.h"
134
135 #ifdef ENABLE_NLS
136 # define _(s) gettext (s)
137 # define N_(s) gettext_noop (s)
138 # define T_(s) gettext(s)
139 #else
140 # ifdef WIN32
141 #   define _(s) T_(s)
142 #   define N_(s) s
143 # else
144 #   define _(s) (s)
145 #   define N_(s) s
146 #   define T_(s) s
147 # endif
148 #endif
149
150
151 int establish P((void));
152 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
153                          char *buf, int count, int error));
154 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
155                       char *buf, int count, int error));
156 void SendToICS P((char *s));
157 void SendToICSDelayed P((char *s, long msdelay));
158 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
159 void HandleMachineMove P((char *message, ChessProgramState *cps));
160 int AutoPlayOneMove P((void));
161 int LoadGameOneMove P((ChessMove readAhead));
162 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
163 int LoadPositionFromFile P((char *filename, int n, char *title));
164 int SavePositionToFile P((char *filename));
165 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
166 void ShowMove P((int fromX, int fromY, int toX, int toY));
167 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
168                    /*char*/int promoChar));
169 void BackwardInner P((int target));
170 void ForwardInner P((int target));
171 int Adjudicate P((ChessProgramState *cps));
172 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
173 void EditPositionDone P((Boolean fakeRights));
174 void PrintOpponents P((FILE *fp));
175 void PrintPosition P((FILE *fp, int move));
176 void StartChessProgram P((ChessProgramState *cps));
177 void SendToProgram P((char *message, ChessProgramState *cps));
178 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
179 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
180                            char *buf, int count, int error));
181 void SendTimeControl P((ChessProgramState *cps,
182                         int mps, long tc, int inc, int sd, int st));
183 char *TimeControlTagValue P((void));
184 void Attention P((ChessProgramState *cps));
185 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
186 int ResurrectChessProgram P((void));
187 void DisplayComment P((int moveNumber, char *text));
188 void DisplayMove P((int moveNumber));
189
190 void ParseGameHistory P((char *game));
191 void ParseBoard12 P((char *string));
192 void KeepAlive P((void));
193 void StartClocks P((void));
194 void SwitchClocks P((int nr));
195 void StopClocks P((void));
196 void ResetClocks P((void));
197 char *PGNDate P((void));
198 void SetGameInfo P((void));
199 int RegisterMove P((void));
200 void MakeRegisteredMove P((void));
201 void TruncateGame P((void));
202 int looking_at P((char *, int *, char *));
203 void CopyPlayerNameIntoFileName P((char **, char *));
204 char *SavePart P((char *));
205 int SaveGameOldStyle P((FILE *));
206 int SaveGamePGN P((FILE *));
207 int CheckFlags P((void));
208 long NextTickLength P((long));
209 void CheckTimeControl P((void));
210 void show_bytes P((FILE *, char *, int));
211 int string_to_rating P((char *str));
212 void ParseFeatures P((char* args, ChessProgramState *cps));
213 void InitBackEnd3 P((void));
214 void FeatureDone P((ChessProgramState* cps, int val));
215 void InitChessProgram P((ChessProgramState *cps, int setup));
216 void OutputKibitz(int window, char *text);
217 int PerpetualChase(int first, int last);
218 int EngineOutputIsUp();
219 void InitDrawingSizes(int x, int y);
220 void NextMatchGame P((void));
221 int NextTourneyGame P((int nr, int *swap));
222 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
223 FILE *WriteTourneyFile P((char *results, FILE *f));
224 void DisplayTwoMachinesTitle P(());
225 static void ExcludeClick P((int index));
226 void ToggleSecond P((void));
227
228 #ifdef WIN32
229        extern void ConsoleCreate();
230 #endif
231
232 ChessProgramState *WhitePlayer();
233 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
234 int VerifyDisplayMode P(());
235
236 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
237 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
238 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
239 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
240 void ics_update_width P((int new_width));
241 extern char installDir[MSG_SIZ];
242 VariantClass startVariant; /* [HGM] nicks: initial variant */
243 Boolean abortMatch;
244
245 extern int tinyLayout, smallLayout;
246 ChessProgramStats programStats;
247 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
248 int endPV = -1;
249 static int exiting = 0; /* [HGM] moved to top */
250 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
251 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
252 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
253 int partnerHighlight[2];
254 Boolean partnerBoardValid = 0;
255 char partnerStatus[MSG_SIZ];
256 Boolean partnerUp;
257 Boolean originalFlip;
258 Boolean twoBoards = 0;
259 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
260 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
261 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
262 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
263 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
264 int opponentKibitzes;
265 int lastSavedGame; /* [HGM] save: ID of game */
266 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
267 extern int chatCount;
268 int chattingPartner;
269 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
270 char lastMsg[MSG_SIZ];
271 ChessSquare pieceSweep = EmptySquare;
272 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
273 int promoDefaultAltered;
274 int keepInfo = 0; /* [HGM] to protect PGN tags in auto-step game analysis */
275
276 /* States for ics_getting_history */
277 #define H_FALSE 0
278 #define H_REQUESTED 1
279 #define H_GOT_REQ_HEADER 2
280 #define H_GOT_UNREQ_HEADER 3
281 #define H_GETTING_MOVES 4
282 #define H_GOT_UNWANTED_HEADER 5
283
284 /* whosays values for GameEnds */
285 #define GE_ICS 0
286 #define GE_ENGINE 1
287 #define GE_PLAYER 2
288 #define GE_FILE 3
289 #define GE_XBOARD 4
290 #define GE_ENGINE1 5
291 #define GE_ENGINE2 6
292
293 /* Maximum number of games in a cmail message */
294 #define CMAIL_MAX_GAMES 20
295
296 /* Different types of move when calling RegisterMove */
297 #define CMAIL_MOVE   0
298 #define CMAIL_RESIGN 1
299 #define CMAIL_DRAW   2
300 #define CMAIL_ACCEPT 3
301
302 /* Different types of result to remember for each game */
303 #define CMAIL_NOT_RESULT 0
304 #define CMAIL_OLD_RESULT 1
305 #define CMAIL_NEW_RESULT 2
306
307 /* Telnet protocol constants */
308 #define TN_WILL 0373
309 #define TN_WONT 0374
310 #define TN_DO   0375
311 #define TN_DONT 0376
312 #define TN_IAC  0377
313 #define TN_ECHO 0001
314 #define TN_SGA  0003
315 #define TN_PORT 23
316
317 char*
318 safeStrCpy (char *dst, const char *src, size_t count)
319 { // [HGM] made safe
320   int i;
321   assert( dst != NULL );
322   assert( src != NULL );
323   assert( count > 0 );
324
325   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
326   if(  i == count && dst[count-1] != NULLCHAR)
327     {
328       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
329       if(appData.debugMode)
330       fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
331     }
332
333   return dst;
334 }
335
336 /* Some compiler can't cast u64 to double
337  * This function do the job for us:
338
339  * We use the highest bit for cast, this only
340  * works if the highest bit is not
341  * in use (This should not happen)
342  *
343  * We used this for all compiler
344  */
345 double
346 u64ToDouble (u64 value)
347 {
348   double r;
349   u64 tmp = value & u64Const(0x7fffffffffffffff);
350   r = (double)(s64)tmp;
351   if (value & u64Const(0x8000000000000000))
352        r +=  9.2233720368547758080e18; /* 2^63 */
353  return r;
354 }
355
356 /* Fake up flags for now, as we aren't keeping track of castling
357    availability yet. [HGM] Change of logic: the flag now only
358    indicates the type of castlings allowed by the rule of the game.
359    The actual rights themselves are maintained in the array
360    castlingRights, as part of the game history, and are not probed
361    by this function.
362  */
363 int
364 PosFlags (index)
365 {
366   int flags = F_ALL_CASTLE_OK;
367   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
368   switch (gameInfo.variant) {
369   case VariantSuicide:
370     flags &= ~F_ALL_CASTLE_OK;
371   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
372     flags |= F_IGNORE_CHECK;
373   case VariantLosers:
374     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
375     break;
376   case VariantAtomic:
377     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
378     break;
379   case VariantKriegspiel:
380     flags |= F_KRIEGSPIEL_CAPTURE;
381     break;
382   case VariantCapaRandom:
383   case VariantFischeRandom:
384     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
385   case VariantNoCastle:
386   case VariantShatranj:
387   case VariantCourier:
388   case VariantMakruk:
389   case VariantGrand:
390     flags &= ~F_ALL_CASTLE_OK;
391     break;
392   default:
393     break;
394   }
395   return flags;
396 }
397
398 FILE *gameFileFP, *debugFP, *serverFP;
399 char *currentDebugFile; // [HGM] debug split: to remember name
400
401 /*
402     [AS] Note: sometimes, the sscanf() function is used to parse the input
403     into a fixed-size buffer. Because of this, we must be prepared to
404     receive strings as long as the size of the input buffer, which is currently
405     set to 4K for Windows and 8K for the rest.
406     So, we must either allocate sufficiently large buffers here, or
407     reduce the size of the input buffer in the input reading part.
408 */
409
410 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
411 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
412 char thinkOutput1[MSG_SIZ*10];
413
414 ChessProgramState first, second, pairing;
415
416 /* premove variables */
417 int premoveToX = 0;
418 int premoveToY = 0;
419 int premoveFromX = 0;
420 int premoveFromY = 0;
421 int premovePromoChar = 0;
422 int gotPremove = 0;
423 Boolean alarmSounded;
424 /* end premove variables */
425
426 char *ics_prefix = "$";
427 enum ICS_TYPE ics_type = ICS_GENERIC;
428
429 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
430 int pauseExamForwardMostMove = 0;
431 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
432 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
433 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
434 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
435 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
436 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
437 int whiteFlag = FALSE, blackFlag = FALSE;
438 int userOfferedDraw = FALSE;
439 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
440 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
441 int cmailMoveType[CMAIL_MAX_GAMES];
442 long ics_clock_paused = 0;
443 ProcRef icsPR = NoProc, cmailPR = NoProc;
444 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
445 GameMode gameMode = BeginningOfGame;
446 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
447 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
448 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
449 int hiddenThinkOutputState = 0; /* [AS] */
450 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
451 int adjudicateLossPlies = 6;
452 char white_holding[64], black_holding[64];
453 TimeMark lastNodeCountTime;
454 long lastNodeCount=0;
455 int shiftKey, controlKey; // [HGM] set by mouse handler
456
457 int have_sent_ICS_logon = 0;
458 int movesPerSession;
459 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
460 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack, activePartnerTime;
461 Boolean adjustedClock;
462 long timeControl_2; /* [AS] Allow separate time controls */
463 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC, activePartner; /* [HGM] secondary TC: merge of MPS, TC and inc */
464 long timeRemaining[2][MAX_MOVES];
465 int matchGame = 0, nextGame = 0, roundNr = 0;
466 Boolean waitingForGame = FALSE;
467 TimeMark programStartTime, pauseStart;
468 char ics_handle[MSG_SIZ];
469 int have_set_title = 0;
470
471 /* animateTraining preserves the state of appData.animate
472  * when Training mode is activated. This allows the
473  * response to be animated when appData.animate == TRUE and
474  * appData.animateDragging == TRUE.
475  */
476 Boolean animateTraining;
477
478 GameInfo gameInfo;
479
480 AppData appData;
481
482 Board boards[MAX_MOVES];
483 /* [HGM] Following 7 needed for accurate legality tests: */
484 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
485 signed char  initialRights[BOARD_FILES];
486 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
487 int   initialRulePlies, FENrulePlies;
488 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
489 int loadFlag = 0;
490 Boolean shuffleOpenings;
491 int mute; // mute all sounds
492
493 // [HGM] vari: next 12 to save and restore variations
494 #define MAX_VARIATIONS 10
495 int framePtr = MAX_MOVES-1; // points to free stack entry
496 int storedGames = 0;
497 int savedFirst[MAX_VARIATIONS];
498 int savedLast[MAX_VARIATIONS];
499 int savedFramePtr[MAX_VARIATIONS];
500 char *savedDetails[MAX_VARIATIONS];
501 ChessMove savedResult[MAX_VARIATIONS];
502
503 void PushTail P((int firstMove, int lastMove));
504 Boolean PopTail P((Boolean annotate));
505 void PushInner P((int firstMove, int lastMove));
506 void PopInner P((Boolean annotate));
507 void CleanupTail P((void));
508
509 ChessSquare  FIDEArray[2][BOARD_FILES] = {
510     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
511         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
512     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
513         BlackKing, BlackBishop, BlackKnight, BlackRook }
514 };
515
516 ChessSquare twoKingsArray[2][BOARD_FILES] = {
517     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
518         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
519     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
520         BlackKing, BlackKing, BlackKnight, BlackRook }
521 };
522
523 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
524     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
525         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
526     { BlackRook, BlackMan, BlackBishop, BlackQueen,
527         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
528 };
529
530 ChessSquare SpartanArray[2][BOARD_FILES] = {
531     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
532         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
533     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
534         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
535 };
536
537 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
538     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
539         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
540     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
541         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
542 };
543
544 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
545     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
546         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
547     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
548         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
549 };
550
551 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
552     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
553         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
554     { BlackRook, BlackKnight, BlackMan, BlackFerz,
555         BlackKing, BlackMan, BlackKnight, BlackRook }
556 };
557
558
559 #if (BOARD_FILES>=10)
560 ChessSquare ShogiArray[2][BOARD_FILES] = {
561     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
562         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
563     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
564         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
565 };
566
567 ChessSquare XiangqiArray[2][BOARD_FILES] = {
568     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
569         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
570     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
571         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
572 };
573
574 ChessSquare CapablancaArray[2][BOARD_FILES] = {
575     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
576         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
577     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
578         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
579 };
580
581 ChessSquare GreatArray[2][BOARD_FILES] = {
582     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
583         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
584     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
585         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
586 };
587
588 ChessSquare JanusArray[2][BOARD_FILES] = {
589     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
590         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
591     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
592         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
593 };
594
595 ChessSquare GrandArray[2][BOARD_FILES] = {
596     { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
597         WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
598     { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
599         BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
600 };
601
602 #ifdef GOTHIC
603 ChessSquare GothicArray[2][BOARD_FILES] = {
604     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
605         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
606     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
607         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
608 };
609 #else // !GOTHIC
610 #define GothicArray CapablancaArray
611 #endif // !GOTHIC
612
613 #ifdef FALCON
614 ChessSquare FalconArray[2][BOARD_FILES] = {
615     { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
616         WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
617     { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
618         BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
619 };
620 #else // !FALCON
621 #define FalconArray CapablancaArray
622 #endif // !FALCON
623
624 #else // !(BOARD_FILES>=10)
625 #define XiangqiPosition FIDEArray
626 #define CapablancaArray FIDEArray
627 #define GothicArray FIDEArray
628 #define GreatArray FIDEArray
629 #endif // !(BOARD_FILES>=10)
630
631 #if (BOARD_FILES>=12)
632 ChessSquare CourierArray[2][BOARD_FILES] = {
633     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
634         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
635     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
636         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
637 };
638 #else // !(BOARD_FILES>=12)
639 #define CourierArray CapablancaArray
640 #endif // !(BOARD_FILES>=12)
641
642
643 Board initialPosition;
644
645
646 /* Convert str to a rating. Checks for special cases of "----",
647
648    "++++", etc. Also strips ()'s */
649 int
650 string_to_rating (char *str)
651 {
652   while(*str && !isdigit(*str)) ++str;
653   if (!*str)
654     return 0;   /* One of the special "no rating" cases */
655   else
656     return atoi(str);
657 }
658
659 void
660 ClearProgramStats ()
661 {
662     /* Init programStats */
663     programStats.movelist[0] = 0;
664     programStats.depth = 0;
665     programStats.nr_moves = 0;
666     programStats.moves_left = 0;
667     programStats.nodes = 0;
668     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
669     programStats.score = 0;
670     programStats.got_only_move = 0;
671     programStats.got_fail = 0;
672     programStats.line_is_book = 0;
673 }
674
675 void
676 CommonEngineInit ()
677 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
678     if (appData.firstPlaysBlack) {
679         first.twoMachinesColor = "black\n";
680         second.twoMachinesColor = "white\n";
681     } else {
682         first.twoMachinesColor = "white\n";
683         second.twoMachinesColor = "black\n";
684     }
685
686     first.other = &second;
687     second.other = &first;
688
689     { float norm = 1;
690         if(appData.timeOddsMode) {
691             norm = appData.timeOdds[0];
692             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
693         }
694         first.timeOdds  = appData.timeOdds[0]/norm;
695         second.timeOdds = appData.timeOdds[1]/norm;
696     }
697
698     if(programVersion) free(programVersion);
699     if (appData.noChessProgram) {
700         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
701         sprintf(programVersion, "%s", PACKAGE_STRING);
702     } else {
703       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
704       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
705       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
706     }
707 }
708
709 void
710 UnloadEngine (ChessProgramState *cps)
711 {
712         /* Kill off first chess program */
713         if (cps->isr != NULL)
714           RemoveInputSource(cps->isr);
715         cps->isr = NULL;
716
717         if (cps->pr != NoProc) {
718             ExitAnalyzeMode();
719             DoSleep( appData.delayBeforeQuit );
720             SendToProgram("quit\n", cps);
721             DoSleep( appData.delayAfterQuit );
722             DestroyChildProcess(cps->pr, cps->useSigterm);
723         }
724         cps->pr = NoProc;
725         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
726 }
727
728 void
729 ClearOptions (ChessProgramState *cps)
730 {
731     int i;
732     cps->nrOptions = cps->comboCnt = 0;
733     for(i=0; i<MAX_OPTIONS; i++) {
734         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
735         cps->option[i].textValue = 0;
736     }
737 }
738
739 char *engineNames[] = {
740   /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
741      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
742 N_("first"),
743   /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
744      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
745 N_("second")
746 };
747
748 void
749 InitEngine (ChessProgramState *cps, int n)
750 {   // [HGM] all engine initialiation put in a function that does one engine
751
752     ClearOptions(cps);
753
754     cps->which = engineNames[n];
755     cps->maybeThinking = FALSE;
756     cps->pr = NoProc;
757     cps->isr = NULL;
758     cps->sendTime = 2;
759     cps->sendDrawOffers = 1;
760
761     cps->program = appData.chessProgram[n];
762     cps->host = appData.host[n];
763     cps->dir = appData.directory[n];
764     cps->initString = appData.engInitString[n];
765     cps->computerString = appData.computerString[n];
766     cps->useSigint  = TRUE;
767     cps->useSigterm = TRUE;
768     cps->reuse = appData.reuse[n];
769     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
770     cps->useSetboard = FALSE;
771     cps->useSAN = FALSE;
772     cps->usePing = FALSE;
773     cps->lastPing = 0;
774     cps->lastPong = 0;
775     cps->usePlayother = FALSE;
776     cps->useColors = TRUE;
777     cps->useUsermove = FALSE;
778     cps->sendICS = FALSE;
779     cps->sendName = appData.icsActive;
780     cps->sdKludge = FALSE;
781     cps->stKludge = FALSE;
782     TidyProgramName(cps->program, cps->host, cps->tidy);
783     cps->matchWins = 0;
784     safeStrCpy(cps->variants, appData.variant, MSG_SIZ);
785     cps->analysisSupport = 2; /* detect */
786     cps->analyzing = FALSE;
787     cps->initDone = FALSE;
788
789     /* New features added by Tord: */
790     cps->useFEN960 = FALSE;
791     cps->useOOCastle = TRUE;
792     /* End of new features added by Tord. */
793     cps->fenOverride  = appData.fenOverride[n];
794
795     /* [HGM] time odds: set factor for each machine */
796     cps->timeOdds  = appData.timeOdds[n];
797
798     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
799     cps->accumulateTC = appData.accumulateTC[n];
800     cps->maxNrOfSessions = 1;
801
802     /* [HGM] debug */
803     cps->debug = FALSE;
804
805     cps->supportsNPS = UNKNOWN;
806     cps->memSize = FALSE;
807     cps->maxCores = FALSE;
808     cps->egtFormats[0] = NULLCHAR;
809
810     /* [HGM] options */
811     cps->optionSettings  = appData.engOptions[n];
812
813     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
814     cps->isUCI = appData.isUCI[n]; /* [AS] */
815     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
816
817     if (appData.protocolVersion[n] > PROTOVER
818         || appData.protocolVersion[n] < 1)
819       {
820         char buf[MSG_SIZ];
821         int len;
822
823         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
824                        appData.protocolVersion[n]);
825         if( (len >= MSG_SIZ) && appData.debugMode )
826           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
827
828         DisplayFatalError(buf, 0, 2);
829       }
830     else
831       {
832         cps->protocolVersion = appData.protocolVersion[n];
833       }
834
835     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
836     ParseFeatures(appData.featureDefaults, cps);
837 }
838
839 ChessProgramState *savCps;
840
841 void
842 LoadEngine ()
843 {
844     int i;
845     if(WaitForEngine(savCps, LoadEngine)) return;
846     CommonEngineInit(); // recalculate time odds
847     if(gameInfo.variant != StringToVariant(appData.variant)) {
848         // we changed variant when loading the engine; this forces us to reset
849         Reset(TRUE, savCps != &first);
850         EditGameEvent(); // for consistency with other path, as Reset changes mode
851     }
852     InitChessProgram(savCps, FALSE);
853     SendToProgram("force\n", savCps);
854     DisplayMessage("", "");
855     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
856     for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
857     ThawUI();
858     SetGNUMode();
859 }
860
861 void
862 ReplaceEngine (ChessProgramState *cps, int n)
863 {
864     EditGameEvent();
865     UnloadEngine(cps);
866     appData.noChessProgram = FALSE;
867     appData.clockMode = TRUE;
868     InitEngine(cps, n);
869     UpdateLogos(TRUE);
870     if(n) return; // only startup first engine immediately; second can wait
871     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
872     LoadEngine();
873 }
874
875 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
876 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
877
878 static char resetOptions[] = 
879         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
880         "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
881         "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 "
882         "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
883
884 void
885 FloatToFront(char **list, char *engineLine)
886 {
887     char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
888     int i=0;
889     if(appData.recentEngines <= 0) return;
890     TidyProgramName(engineLine, "localhost", tidy+1);
891     tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
892     strncpy(buf+1, *list, MSG_SIZ-50);
893     if(p = strstr(buf, tidy)) { // tidy name appears in list
894         q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
895         while(*p++ = *++q); // squeeze out
896     }
897     strcat(tidy, buf+1); // put list behind tidy name
898     p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
899     if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
900     ASSIGN(*list, tidy+1);
901 }
902
903 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
904
905 void
906 Load (ChessProgramState *cps, int i)
907 {
908     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ];
909     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
910         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
911         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
912         ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
913         FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
914         appData.firstProtocolVersion = PROTOVER;
915         ParseArgsFromString(buf);
916         SwapEngines(i);
917         ReplaceEngine(cps, i);
918         FloatToFront(&appData.recentEngineList, engineLine);
919         return;
920     }
921     p = engineName;
922     while(q = strchr(p, SLASH)) p = q+1;
923     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
924     if(engineDir[0] != NULLCHAR) {
925         ASSIGN(appData.directory[i], engineDir); p = engineName;
926     } else if(p != engineName) { // derive directory from engine path, when not given
927         p[-1] = 0;
928         ASSIGN(appData.directory[i], engineName);
929         p[-1] = SLASH;
930         if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
931     } else { ASSIGN(appData.directory[i], "."); }
932     if(params[0]) {
933         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
934         snprintf(command, MSG_SIZ, "%s %s", p, params);
935         p = command;
936     }
937     ASSIGN(appData.chessProgram[i], p);
938     appData.isUCI[i] = isUCI;
939     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
940     appData.hasOwnBookUCI[i] = hasBook;
941     if(!nickName[0]) useNick = FALSE;
942     if(useNick) ASSIGN(appData.pgnName[i], nickName);
943     if(addToList) {
944         int len;
945         char quote;
946         q = firstChessProgramNames;
947         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
948         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
949         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
950                         quote, p, quote, appData.directory[i], 
951                         useNick ? " -fn \"" : "",
952                         useNick ? nickName : "",
953                         useNick ? "\"" : "",
954                         v1 ? " -firstProtocolVersion 1" : "",
955                         hasBook ? "" : " -fNoOwnBookUCI",
956                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
957                         storeVariant ? " -variant " : "",
958                         storeVariant ? VariantName(gameInfo.variant) : "");
959         if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
960         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
961         if(insert != q) insert[-1] = NULLCHAR;
962         snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
963         if(q)   free(q);
964         FloatToFront(&appData.recentEngineList, buf);
965     }
966     ReplaceEngine(cps, i);
967 }
968
969 void
970 InitTimeControls ()
971 {
972     int matched, min, sec;
973     /*
974      * Parse timeControl resource
975      */
976     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
977                           appData.movesPerSession)) {
978         char buf[MSG_SIZ];
979         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
980         DisplayFatalError(buf, 0, 2);
981     }
982
983     /*
984      * Parse searchTime resource
985      */
986     if (*appData.searchTime != NULLCHAR) {
987         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
988         if (matched == 1) {
989             searchTime = min * 60;
990         } else if (matched == 2) {
991             searchTime = min * 60 + sec;
992         } else {
993             char buf[MSG_SIZ];
994             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
995             DisplayFatalError(buf, 0, 2);
996         }
997     }
998 }
999
1000 void
1001 InitBackEnd1 ()
1002 {
1003
1004     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1005     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1006
1007     GetTimeMark(&programStartTime);
1008     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1009     appData.seedBase = random() + (random()<<15);
1010     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1011
1012     ClearProgramStats();
1013     programStats.ok_to_send = 1;
1014     programStats.seen_stat = 0;
1015
1016     /*
1017      * Initialize game list
1018      */
1019     ListNew(&gameList);
1020
1021
1022     /*
1023      * Internet chess server status
1024      */
1025     if (appData.icsActive) {
1026         appData.matchMode = FALSE;
1027         appData.matchGames = 0;
1028 #if ZIPPY
1029         appData.noChessProgram = !appData.zippyPlay;
1030 #else
1031         appData.zippyPlay = FALSE;
1032         appData.zippyTalk = FALSE;
1033         appData.noChessProgram = TRUE;
1034 #endif
1035         if (*appData.icsHelper != NULLCHAR) {
1036             appData.useTelnet = TRUE;
1037             appData.telnetProgram = appData.icsHelper;
1038         }
1039     } else {
1040         appData.zippyTalk = appData.zippyPlay = FALSE;
1041     }
1042
1043     /* [AS] Initialize pv info list [HGM] and game state */
1044     {
1045         int i, j;
1046
1047         for( i=0; i<=framePtr; i++ ) {
1048             pvInfoList[i].depth = -1;
1049             boards[i][EP_STATUS] = EP_NONE;
1050             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1051         }
1052     }
1053
1054     InitTimeControls();
1055
1056     /* [AS] Adjudication threshold */
1057     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1058
1059     InitEngine(&first, 0);
1060     InitEngine(&second, 1);
1061     CommonEngineInit();
1062
1063     pairing.which = "pairing"; // pairing engine
1064     pairing.pr = NoProc;
1065     pairing.isr = NULL;
1066     pairing.program = appData.pairingEngine;
1067     pairing.host = "localhost";
1068     pairing.dir = ".";
1069
1070     if (appData.icsActive) {
1071         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1072     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1073         appData.clockMode = FALSE;
1074         first.sendTime = second.sendTime = 0;
1075     }
1076
1077 #if ZIPPY
1078     /* Override some settings from environment variables, for backward
1079        compatibility.  Unfortunately it's not feasible to have the env
1080        vars just set defaults, at least in xboard.  Ugh.
1081     */
1082     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1083       ZippyInit();
1084     }
1085 #endif
1086
1087     if (!appData.icsActive) {
1088       char buf[MSG_SIZ];
1089       int len;
1090
1091       /* Check for variants that are supported only in ICS mode,
1092          or not at all.  Some that are accepted here nevertheless
1093          have bugs; see comments below.
1094       */
1095       VariantClass variant = StringToVariant(appData.variant);
1096       switch (variant) {
1097       case VariantBughouse:     /* need four players and two boards */
1098       case VariantKriegspiel:   /* need to hide pieces and move details */
1099         /* case VariantFischeRandom: (Fabien: moved below) */
1100         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1101         if( (len >= MSG_SIZ) && appData.debugMode )
1102           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1103
1104         DisplayFatalError(buf, 0, 2);
1105         return;
1106
1107       case VariantUnknown:
1108       case VariantLoadable:
1109       case Variant29:
1110       case Variant30:
1111       case Variant31:
1112       case Variant32:
1113       case Variant33:
1114       case Variant34:
1115       case Variant35:
1116       case Variant36:
1117       default:
1118         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1119         if( (len >= MSG_SIZ) && appData.debugMode )
1120           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1121
1122         DisplayFatalError(buf, 0, 2);
1123         return;
1124
1125       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1126       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1127       case VariantGothic:     /* [HGM] should work */
1128       case VariantCapablanca: /* [HGM] should work */
1129       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1130       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1131       case VariantKnightmate: /* [HGM] should work */
1132       case VariantCylinder:   /* [HGM] untested */
1133       case VariantFalcon:     /* [HGM] untested */
1134       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1135                                  offboard interposition not understood */
1136       case VariantNormal:     /* definitely works! */
1137       case VariantWildCastle: /* pieces not automatically shuffled */
1138       case VariantNoCastle:   /* pieces not automatically shuffled */
1139       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1140       case VariantLosers:     /* should work except for win condition,
1141                                  and doesn't know captures are mandatory */
1142       case VariantSuicide:    /* should work except for win condition,
1143                                  and doesn't know captures are mandatory */
1144       case VariantGiveaway:   /* should work except for win condition,
1145                                  and doesn't know captures are mandatory */
1146       case VariantTwoKings:   /* should work */
1147       case VariantAtomic:     /* should work except for win condition */
1148       case Variant3Check:     /* should work except for win condition */
1149       case VariantShatranj:   /* should work except for all win conditions */
1150       case VariantMakruk:     /* should work except for draw countdown */
1151       case VariantBerolina:   /* might work if TestLegality is off */
1152       case VariantCapaRandom: /* should work */
1153       case VariantJanus:      /* should work */
1154       case VariantSuper:      /* experimental */
1155       case VariantGreat:      /* experimental, requires legality testing to be off */
1156       case VariantSChess:     /* S-Chess, should work */
1157       case VariantGrand:      /* should work */
1158       case VariantSpartan:    /* should work */
1159         break;
1160       }
1161     }
1162
1163 }
1164
1165 int
1166 NextIntegerFromString (char ** str, long * value)
1167 {
1168     int result = -1;
1169     char * s = *str;
1170
1171     while( *s == ' ' || *s == '\t' ) {
1172         s++;
1173     }
1174
1175     *value = 0;
1176
1177     if( *s >= '0' && *s <= '9' ) {
1178         while( *s >= '0' && *s <= '9' ) {
1179             *value = *value * 10 + (*s - '0');
1180             s++;
1181         }
1182
1183         result = 0;
1184     }
1185
1186     *str = s;
1187
1188     return result;
1189 }
1190
1191 int
1192 NextTimeControlFromString (char ** str, long * value)
1193 {
1194     long temp;
1195     int result = NextIntegerFromString( str, &temp );
1196
1197     if( result == 0 ) {
1198         *value = temp * 60; /* Minutes */
1199         if( **str == ':' ) {
1200             (*str)++;
1201             result = NextIntegerFromString( str, &temp );
1202             *value += temp; /* Seconds */
1203         }
1204     }
1205
1206     return result;
1207 }
1208
1209 int
1210 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1211 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1212     int result = -1, type = 0; long temp, temp2;
1213
1214     if(**str != ':') return -1; // old params remain in force!
1215     (*str)++;
1216     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1217     if( NextIntegerFromString( str, &temp ) ) return -1;
1218     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1219
1220     if(**str != '/') {
1221         /* time only: incremental or sudden-death time control */
1222         if(**str == '+') { /* increment follows; read it */
1223             (*str)++;
1224             if(**str == '!') type = *(*str)++; // Bronstein TC
1225             if(result = NextIntegerFromString( str, &temp2)) return -1;
1226             *inc = temp2 * 1000;
1227             if(**str == '.') { // read fraction of increment
1228                 char *start = ++(*str);
1229                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1230                 temp2 *= 1000;
1231                 while(start++ < *str) temp2 /= 10;
1232                 *inc += temp2;
1233             }
1234         } else *inc = 0;
1235         *moves = 0; *tc = temp * 1000; *incType = type;
1236         return 0;
1237     }
1238
1239     (*str)++; /* classical time control */
1240     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1241
1242     if(result == 0) {
1243         *moves = temp;
1244         *tc    = temp2 * 1000;
1245         *inc   = 0;
1246         *incType = type;
1247     }
1248     return result;
1249 }
1250
1251 int
1252 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1253 {   /* [HGM] get time to add from the multi-session time-control string */
1254     int incType, moves=1; /* kludge to force reading of first session */
1255     long time, increment;
1256     char *s = tcString;
1257
1258     if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1259     do {
1260         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1261         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1262         if(movenr == -1) return time;    /* last move before new session     */
1263         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1264         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1265         if(!moves) return increment;     /* current session is incremental   */
1266         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1267     } while(movenr >= -1);               /* try again for next session       */
1268
1269     return 0; // no new time quota on this move
1270 }
1271
1272 int
1273 ParseTimeControl (char *tc, float ti, int mps)
1274 {
1275   long tc1;
1276   long tc2;
1277   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1278   int min, sec=0;
1279
1280   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1281   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1282       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1283   if(ti > 0) {
1284
1285     if(mps)
1286       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1287     else 
1288       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1289   } else {
1290     if(mps)
1291       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1292     else 
1293       snprintf(buf, MSG_SIZ, ":%s", mytc);
1294   }
1295   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1296   
1297   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1298     return FALSE;
1299   }
1300
1301   if( *tc == '/' ) {
1302     /* Parse second time control */
1303     tc++;
1304
1305     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1306       return FALSE;
1307     }
1308
1309     if( tc2 == 0 ) {
1310       return FALSE;
1311     }
1312
1313     timeControl_2 = tc2 * 1000;
1314   }
1315   else {
1316     timeControl_2 = 0;
1317   }
1318
1319   if( tc1 == 0 ) {
1320     return FALSE;
1321   }
1322
1323   timeControl = tc1 * 1000;
1324
1325   if (ti >= 0) {
1326     timeIncrement = ti * 1000;  /* convert to ms */
1327     movesPerSession = 0;
1328   } else {
1329     timeIncrement = 0;
1330     movesPerSession = mps;
1331   }
1332   return TRUE;
1333 }
1334
1335 void
1336 InitBackEnd2 ()
1337 {
1338     if (appData.debugMode) {
1339         fprintf(debugFP, "%s\n", programVersion);
1340     }
1341     ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1342
1343     set_cont_sequence(appData.wrapContSeq);
1344     if (appData.matchGames > 0) {
1345         appData.matchMode = TRUE;
1346     } else if (appData.matchMode) {
1347         appData.matchGames = 1;
1348     }
1349     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1350         appData.matchGames = appData.sameColorGames;
1351     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1352         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1353         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1354     }
1355     Reset(TRUE, FALSE);
1356     if (appData.noChessProgram || first.protocolVersion == 1) {
1357       InitBackEnd3();
1358     } else {
1359       /* kludge: allow timeout for initial "feature" commands */
1360       FreezeUI();
1361       DisplayMessage("", _("Starting chess program"));
1362       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1363     }
1364 }
1365
1366 int
1367 CalculateIndex (int index, int gameNr)
1368 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1369     int res;
1370     if(index > 0) return index; // fixed nmber
1371     if(index == 0) return 1;
1372     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1373     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1374     return res;
1375 }
1376
1377 int
1378 LoadGameOrPosition (int gameNr)
1379 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1380     if (*appData.loadGameFile != NULLCHAR) {
1381         if (!LoadGameFromFile(appData.loadGameFile,
1382                 CalculateIndex(appData.loadGameIndex, gameNr),
1383                               appData.loadGameFile, FALSE)) {
1384             DisplayFatalError(_("Bad game file"), 0, 1);
1385             return 0;
1386         }
1387     } else if (*appData.loadPositionFile != NULLCHAR) {
1388         if (!LoadPositionFromFile(appData.loadPositionFile,
1389                 CalculateIndex(appData.loadPositionIndex, gameNr),
1390                                   appData.loadPositionFile)) {
1391             DisplayFatalError(_("Bad position file"), 0, 1);
1392             return 0;
1393         }
1394     }
1395     return 1;
1396 }
1397
1398 void
1399 ReserveGame (int gameNr, char resChar)
1400 {
1401     FILE *tf = fopen(appData.tourneyFile, "r+");
1402     char *p, *q, c, buf[MSG_SIZ];
1403     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1404     safeStrCpy(buf, lastMsg, MSG_SIZ);
1405     DisplayMessage(_("Pick new game"), "");
1406     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1407     ParseArgsFromFile(tf);
1408     p = q = appData.results;
1409     if(appData.debugMode) {
1410       char *r = appData.participants;
1411       fprintf(debugFP, "results = '%s'\n", p);
1412       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1413       fprintf(debugFP, "\n");
1414     }
1415     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1416     nextGame = q - p;
1417     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1418     safeStrCpy(q, p, strlen(p) + 2);
1419     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1420     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1421     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1422         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1423         q[nextGame] = '*';
1424     }
1425     fseek(tf, -(strlen(p)+4), SEEK_END);
1426     c = fgetc(tf);
1427     if(c != '"') // depending on DOS or Unix line endings we can be one off
1428          fseek(tf, -(strlen(p)+2), SEEK_END);
1429     else fseek(tf, -(strlen(p)+3), SEEK_END);
1430     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1431     DisplayMessage(buf, "");
1432     free(p); appData.results = q;
1433     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1434        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1435       int round = appData.defaultMatchGames * appData.tourneyType;
1436       if(gameNr < 0 || appData.tourneyType < 1 ||  // gauntlet engine can always stay loaded as first engine
1437          appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1438         UnloadEngine(&first);  // next game belongs to other pairing;
1439         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1440     }
1441     if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1442 }
1443
1444 void
1445 MatchEvent (int mode)
1446 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1447         int dummy;
1448         if(matchMode) { // already in match mode: switch it off
1449             abortMatch = TRUE;
1450             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1451             return;
1452         }
1453 //      if(gameMode != BeginningOfGame) {
1454 //          DisplayError(_("You can only start a match from the initial position."), 0);
1455 //          return;
1456 //      }
1457         abortMatch = FALSE;
1458         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1459         /* Set up machine vs. machine match */
1460         nextGame = 0;
1461         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1462         if(appData.tourneyFile[0]) {
1463             ReserveGame(-1, 0);
1464             if(nextGame > appData.matchGames) {
1465                 char buf[MSG_SIZ];
1466                 if(strchr(appData.results, '*') == NULL) {
1467                     FILE *f;
1468                     appData.tourneyCycles++;
1469                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1470                         fclose(f);
1471                         NextTourneyGame(-1, &dummy);
1472                         ReserveGame(-1, 0);
1473                         if(nextGame <= appData.matchGames) {
1474                             DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1475                             matchMode = mode;
1476                             ScheduleDelayedEvent(NextMatchGame, 10000);
1477                             return;
1478                         }
1479                     }
1480                 }
1481                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1482                 DisplayError(buf, 0);
1483                 appData.tourneyFile[0] = 0;
1484                 return;
1485             }
1486         } else
1487         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1488             DisplayFatalError(_("Can't have a match with no chess programs"),
1489                               0, 2);
1490             return;
1491         }
1492         matchMode = mode;
1493         matchGame = roundNr = 1;
1494         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1495         NextMatchGame();
1496 }
1497
1498 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1499
1500 void
1501 InitBackEnd3 P((void))
1502 {
1503     GameMode initialMode;
1504     char buf[MSG_SIZ];
1505     int err, len;
1506
1507     InitChessProgram(&first, startedFromSetupPosition);
1508
1509     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1510         free(programVersion);
1511         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1512         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1513         FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1514     }
1515
1516     if (appData.icsActive) {
1517 #ifdef WIN32
1518         /* [DM] Make a console window if needed [HGM] merged ifs */
1519         ConsoleCreate();
1520 #endif
1521         err = establish();
1522         if (err != 0)
1523           {
1524             if (*appData.icsCommPort != NULLCHAR)
1525               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1526                              appData.icsCommPort);
1527             else
1528               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1529                         appData.icsHost, appData.icsPort);
1530
1531             if( (len >= MSG_SIZ) && appData.debugMode )
1532               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1533
1534             DisplayFatalError(buf, err, 1);
1535             return;
1536         }
1537         SetICSMode();
1538         telnetISR =
1539           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1540         fromUserISR =
1541           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1542         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1543             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1544     } else if (appData.noChessProgram) {
1545         SetNCPMode();
1546     } else {
1547         SetGNUMode();
1548     }
1549
1550     if (*appData.cmailGameName != NULLCHAR) {
1551         SetCmailMode();
1552         OpenLoopback(&cmailPR);
1553         cmailISR =
1554           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1555     }
1556
1557     ThawUI();
1558     DisplayMessage("", "");
1559     if (StrCaseCmp(appData.initialMode, "") == 0) {
1560       initialMode = BeginningOfGame;
1561       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1562         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1563         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1564         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1565         ModeHighlight();
1566       }
1567     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1568       initialMode = TwoMachinesPlay;
1569     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1570       initialMode = AnalyzeFile;
1571     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1572       initialMode = AnalyzeMode;
1573     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1574       initialMode = MachinePlaysWhite;
1575     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1576       initialMode = MachinePlaysBlack;
1577     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1578       initialMode = EditGame;
1579     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1580       initialMode = EditPosition;
1581     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1582       initialMode = Training;
1583     } else {
1584       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1585       if( (len >= MSG_SIZ) && appData.debugMode )
1586         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1587
1588       DisplayFatalError(buf, 0, 2);
1589       return;
1590     }
1591
1592     if (appData.matchMode) {
1593         if(appData.tourneyFile[0]) { // start tourney from command line
1594             FILE *f;
1595             if(f = fopen(appData.tourneyFile, "r")) {
1596                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1597                 fclose(f);
1598                 appData.clockMode = TRUE;
1599                 SetGNUMode();
1600             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1601         }
1602         MatchEvent(TRUE);
1603     } else if (*appData.cmailGameName != NULLCHAR) {
1604         /* Set up cmail mode */
1605         ReloadCmailMsgEvent(TRUE);
1606     } else {
1607         /* Set up other modes */
1608         if (initialMode == AnalyzeFile) {
1609           if (*appData.loadGameFile == NULLCHAR) {
1610             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1611             return;
1612           }
1613         }
1614         if (*appData.loadGameFile != NULLCHAR) {
1615             (void) LoadGameFromFile(appData.loadGameFile,
1616                                     appData.loadGameIndex,
1617                                     appData.loadGameFile, TRUE);
1618         } else if (*appData.loadPositionFile != NULLCHAR) {
1619             (void) LoadPositionFromFile(appData.loadPositionFile,
1620                                         appData.loadPositionIndex,
1621                                         appData.loadPositionFile);
1622             /* [HGM] try to make self-starting even after FEN load */
1623             /* to allow automatic setup of fairy variants with wtm */
1624             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1625                 gameMode = BeginningOfGame;
1626                 setboardSpoiledMachineBlack = 1;
1627             }
1628             /* [HGM] loadPos: make that every new game uses the setup */
1629             /* from file as long as we do not switch variant          */
1630             if(!blackPlaysFirst) {
1631                 startedFromPositionFile = TRUE;
1632                 CopyBoard(filePosition, boards[0]);
1633             }
1634         }
1635         if (initialMode == AnalyzeMode) {
1636           if (appData.noChessProgram) {
1637             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1638             return;
1639           }
1640           if (appData.icsActive) {
1641             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1642             return;
1643           }
1644           AnalyzeModeEvent();
1645         } else if (initialMode == AnalyzeFile) {
1646           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1647           ShowThinkingEvent();
1648           AnalyzeFileEvent();
1649           AnalysisPeriodicEvent(1);
1650         } else if (initialMode == MachinePlaysWhite) {
1651           if (appData.noChessProgram) {
1652             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1653                               0, 2);
1654             return;
1655           }
1656           if (appData.icsActive) {
1657             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1658                               0, 2);
1659             return;
1660           }
1661           MachineWhiteEvent();
1662         } else if (initialMode == MachinePlaysBlack) {
1663           if (appData.noChessProgram) {
1664             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1665                               0, 2);
1666             return;
1667           }
1668           if (appData.icsActive) {
1669             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1670                               0, 2);
1671             return;
1672           }
1673           MachineBlackEvent();
1674         } else if (initialMode == TwoMachinesPlay) {
1675           if (appData.noChessProgram) {
1676             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1677                               0, 2);
1678             return;
1679           }
1680           if (appData.icsActive) {
1681             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1682                               0, 2);
1683             return;
1684           }
1685           TwoMachinesEvent();
1686         } else if (initialMode == EditGame) {
1687           EditGameEvent();
1688         } else if (initialMode == EditPosition) {
1689           EditPositionEvent();
1690         } else if (initialMode == Training) {
1691           if (*appData.loadGameFile == NULLCHAR) {
1692             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1693             return;
1694           }
1695           TrainingEvent();
1696         }
1697     }
1698 }
1699
1700 void
1701 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1702 {
1703     DisplayBook(current+1);
1704
1705     MoveHistorySet( movelist, first, last, current, pvInfoList );
1706
1707     EvalGraphSet( first, last, current, pvInfoList );
1708
1709     MakeEngineOutputTitle();
1710 }
1711
1712 /*
1713  * Establish will establish a contact to a remote host.port.
1714  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1715  *  used to talk to the host.
1716  * Returns 0 if okay, error code if not.
1717  */
1718 int
1719 establish ()
1720 {
1721     char buf[MSG_SIZ];
1722
1723     if (*appData.icsCommPort != NULLCHAR) {
1724         /* Talk to the host through a serial comm port */
1725         return OpenCommPort(appData.icsCommPort, &icsPR);
1726
1727     } else if (*appData.gateway != NULLCHAR) {
1728         if (*appData.remoteShell == NULLCHAR) {
1729             /* Use the rcmd protocol to run telnet program on a gateway host */
1730             snprintf(buf, sizeof(buf), "%s %s %s",
1731                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1732             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1733
1734         } else {
1735             /* Use the rsh program to run telnet program on a gateway host */
1736             if (*appData.remoteUser == NULLCHAR) {
1737                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1738                         appData.gateway, appData.telnetProgram,
1739                         appData.icsHost, appData.icsPort);
1740             } else {
1741                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1742                         appData.remoteShell, appData.gateway,
1743                         appData.remoteUser, appData.telnetProgram,
1744                         appData.icsHost, appData.icsPort);
1745             }
1746             return StartChildProcess(buf, "", &icsPR);
1747
1748         }
1749     } else if (appData.useTelnet) {
1750         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1751
1752     } else {
1753         /* TCP socket interface differs somewhat between
1754            Unix and NT; handle details in the front end.
1755            */
1756         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1757     }
1758 }
1759
1760 void
1761 EscapeExpand (char *p, char *q)
1762 {       // [HGM] initstring: routine to shape up string arguments
1763         while(*p++ = *q++) if(p[-1] == '\\')
1764             switch(*q++) {
1765                 case 'n': p[-1] = '\n'; break;
1766                 case 'r': p[-1] = '\r'; break;
1767                 case 't': p[-1] = '\t'; break;
1768                 case '\\': p[-1] = '\\'; break;
1769                 case 0: *p = 0; return;
1770                 default: p[-1] = q[-1]; break;
1771             }
1772 }
1773
1774 void
1775 show_bytes (FILE *fp, char *buf, int count)
1776 {
1777     while (count--) {
1778         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1779             fprintf(fp, "\\%03o", *buf & 0xff);
1780         } else {
1781             putc(*buf, fp);
1782         }
1783         buf++;
1784     }
1785     fflush(fp);
1786 }
1787
1788 /* Returns an errno value */
1789 int
1790 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1791 {
1792     char buf[8192], *p, *q, *buflim;
1793     int left, newcount, outcount;
1794
1795     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1796         *appData.gateway != NULLCHAR) {
1797         if (appData.debugMode) {
1798             fprintf(debugFP, ">ICS: ");
1799             show_bytes(debugFP, message, count);
1800             fprintf(debugFP, "\n");
1801         }
1802         return OutputToProcess(pr, message, count, outError);
1803     }
1804
1805     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1806     p = message;
1807     q = buf;
1808     left = count;
1809     newcount = 0;
1810     while (left) {
1811         if (q >= buflim) {
1812             if (appData.debugMode) {
1813                 fprintf(debugFP, ">ICS: ");
1814                 show_bytes(debugFP, buf, newcount);
1815                 fprintf(debugFP, "\n");
1816             }
1817             outcount = OutputToProcess(pr, buf, newcount, outError);
1818             if (outcount < newcount) return -1; /* to be sure */
1819             q = buf;
1820             newcount = 0;
1821         }
1822         if (*p == '\n') {
1823             *q++ = '\r';
1824             newcount++;
1825         } else if (((unsigned char) *p) == TN_IAC) {
1826             *q++ = (char) TN_IAC;
1827             newcount ++;
1828         }
1829         *q++ = *p++;
1830         newcount++;
1831         left--;
1832     }
1833     if (appData.debugMode) {
1834         fprintf(debugFP, ">ICS: ");
1835         show_bytes(debugFP, buf, newcount);
1836         fprintf(debugFP, "\n");
1837     }
1838     outcount = OutputToProcess(pr, buf, newcount, outError);
1839     if (outcount < newcount) return -1; /* to be sure */
1840     return count;
1841 }
1842
1843 void
1844 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1845 {
1846     int outError, outCount;
1847     static int gotEof = 0;
1848
1849     /* Pass data read from player on to ICS */
1850     if (count > 0) {
1851         gotEof = 0;
1852         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1853         if (outCount < count) {
1854             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1855         }
1856     } else if (count < 0) {
1857         RemoveInputSource(isr);
1858         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1859     } else if (gotEof++ > 0) {
1860         RemoveInputSource(isr);
1861         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1862     }
1863 }
1864
1865 void
1866 KeepAlive ()
1867 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1868     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1869     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1870     SendToICS("date\n");
1871     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1872 }
1873
1874 /* added routine for printf style output to ics */
1875 void
1876 ics_printf (char *format, ...)
1877 {
1878     char buffer[MSG_SIZ];
1879     va_list args;
1880
1881     va_start(args, format);
1882     vsnprintf(buffer, sizeof(buffer), format, args);
1883     buffer[sizeof(buffer)-1] = '\0';
1884     SendToICS(buffer);
1885     va_end(args);
1886 }
1887
1888 void
1889 SendToICS (char *s)
1890 {
1891     int count, outCount, outError;
1892
1893     if (icsPR == NoProc) return;
1894
1895     count = strlen(s);
1896     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1897     if (outCount < count) {
1898         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1899     }
1900 }
1901
1902 /* This is used for sending logon scripts to the ICS. Sending
1903    without a delay causes problems when using timestamp on ICC
1904    (at least on my machine). */
1905 void
1906 SendToICSDelayed (char *s, long msdelay)
1907 {
1908     int count, outCount, outError;
1909
1910     if (icsPR == NoProc) return;
1911
1912     count = strlen(s);
1913     if (appData.debugMode) {
1914         fprintf(debugFP, ">ICS: ");
1915         show_bytes(debugFP, s, count);
1916         fprintf(debugFP, "\n");
1917     }
1918     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1919                                       msdelay);
1920     if (outCount < count) {
1921         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1922     }
1923 }
1924
1925
1926 /* Remove all highlighting escape sequences in s
1927    Also deletes any suffix starting with '('
1928    */
1929 char *
1930 StripHighlightAndTitle (char *s)
1931 {
1932     static char retbuf[MSG_SIZ];
1933     char *p = retbuf;
1934
1935     while (*s != NULLCHAR) {
1936         while (*s == '\033') {
1937             while (*s != NULLCHAR && !isalpha(*s)) s++;
1938             if (*s != NULLCHAR) s++;
1939         }
1940         while (*s != NULLCHAR && *s != '\033') {
1941             if (*s == '(' || *s == '[') {
1942                 *p = NULLCHAR;
1943                 return retbuf;
1944             }
1945             *p++ = *s++;
1946         }
1947     }
1948     *p = NULLCHAR;
1949     return retbuf;
1950 }
1951
1952 /* Remove all highlighting escape sequences in s */
1953 char *
1954 StripHighlight (char *s)
1955 {
1956     static char retbuf[MSG_SIZ];
1957     char *p = retbuf;
1958
1959     while (*s != NULLCHAR) {
1960         while (*s == '\033') {
1961             while (*s != NULLCHAR && !isalpha(*s)) s++;
1962             if (*s != NULLCHAR) s++;
1963         }
1964         while (*s != NULLCHAR && *s != '\033') {
1965             *p++ = *s++;
1966         }
1967     }
1968     *p = NULLCHAR;
1969     return retbuf;
1970 }
1971
1972 char *variantNames[] = VARIANT_NAMES;
1973 char *
1974 VariantName (VariantClass v)
1975 {
1976     return variantNames[v];
1977 }
1978
1979
1980 /* Identify a variant from the strings the chess servers use or the
1981    PGN Variant tag names we use. */
1982 VariantClass
1983 StringToVariant (char *e)
1984 {
1985     char *p;
1986     int wnum = -1;
1987     VariantClass v = VariantNormal;
1988     int i, found = FALSE;
1989     char buf[MSG_SIZ];
1990     int len;
1991
1992     if (!e) return v;
1993
1994     /* [HGM] skip over optional board-size prefixes */
1995     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1996         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1997         while( *e++ != '_');
1998     }
1999
2000     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2001         v = VariantNormal;
2002         found = TRUE;
2003     } else
2004     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2005       if (StrCaseStr(e, variantNames[i])) {
2006         v = (VariantClass) i;
2007         found = TRUE;
2008         break;
2009       }
2010     }
2011
2012     if (!found) {
2013       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2014           || StrCaseStr(e, "wild/fr")
2015           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2016         v = VariantFischeRandom;
2017       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2018                  (i = 1, p = StrCaseStr(e, "w"))) {
2019         p += i;
2020         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2021         if (isdigit(*p)) {
2022           wnum = atoi(p);
2023         } else {
2024           wnum = -1;
2025         }
2026         switch (wnum) {
2027         case 0: /* FICS only, actually */
2028         case 1:
2029           /* Castling legal even if K starts on d-file */
2030           v = VariantWildCastle;
2031           break;
2032         case 2:
2033         case 3:
2034         case 4:
2035           /* Castling illegal even if K & R happen to start in
2036              normal positions. */
2037           v = VariantNoCastle;
2038           break;
2039         case 5:
2040         case 7:
2041         case 8:
2042         case 10:
2043         case 11:
2044         case 12:
2045         case 13:
2046         case 14:
2047         case 15:
2048         case 18:
2049         case 19:
2050           /* Castling legal iff K & R start in normal positions */
2051           v = VariantNormal;
2052           break;
2053         case 6:
2054         case 20:
2055         case 21:
2056           /* Special wilds for position setup; unclear what to do here */
2057           v = VariantLoadable;
2058           break;
2059         case 9:
2060           /* Bizarre ICC game */
2061           v = VariantTwoKings;
2062           break;
2063         case 16:
2064           v = VariantKriegspiel;
2065           break;
2066         case 17:
2067           v = VariantLosers;
2068           break;
2069         case 22:
2070           v = VariantFischeRandom;
2071           break;
2072         case 23:
2073           v = VariantCrazyhouse;
2074           break;
2075         case 24:
2076           v = VariantBughouse;
2077           break;
2078         case 25:
2079           v = Variant3Check;
2080           break;
2081         case 26:
2082           /* Not quite the same as FICS suicide! */
2083           v = VariantGiveaway;
2084           break;
2085         case 27:
2086           v = VariantAtomic;
2087           break;
2088         case 28:
2089           v = VariantShatranj;
2090           break;
2091
2092         /* Temporary names for future ICC types.  The name *will* change in
2093            the next xboard/WinBoard release after ICC defines it. */
2094         case 29:
2095           v = Variant29;
2096           break;
2097         case 30:
2098           v = Variant30;
2099           break;
2100         case 31:
2101           v = Variant31;
2102           break;
2103         case 32:
2104           v = Variant32;
2105           break;
2106         case 33:
2107           v = Variant33;
2108           break;
2109         case 34:
2110           v = Variant34;
2111           break;
2112         case 35:
2113           v = Variant35;
2114           break;
2115         case 36:
2116           v = Variant36;
2117           break;
2118         case 37:
2119           v = VariantShogi;
2120           break;
2121         case 38:
2122           v = VariantXiangqi;
2123           break;
2124         case 39:
2125           v = VariantCourier;
2126           break;
2127         case 40:
2128           v = VariantGothic;
2129           break;
2130         case 41:
2131           v = VariantCapablanca;
2132           break;
2133         case 42:
2134           v = VariantKnightmate;
2135           break;
2136         case 43:
2137           v = VariantFairy;
2138           break;
2139         case 44:
2140           v = VariantCylinder;
2141           break;
2142         case 45:
2143           v = VariantFalcon;
2144           break;
2145         case 46:
2146           v = VariantCapaRandom;
2147           break;
2148         case 47:
2149           v = VariantBerolina;
2150           break;
2151         case 48:
2152           v = VariantJanus;
2153           break;
2154         case 49:
2155           v = VariantSuper;
2156           break;
2157         case 50:
2158           v = VariantGreat;
2159           break;
2160         case -1:
2161           /* Found "wild" or "w" in the string but no number;
2162              must assume it's normal chess. */
2163           v = VariantNormal;
2164           break;
2165         default:
2166           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2167           if( (len >= MSG_SIZ) && appData.debugMode )
2168             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2169
2170           DisplayError(buf, 0);
2171           v = VariantUnknown;
2172           break;
2173         }
2174       }
2175     }
2176     if (appData.debugMode) {
2177       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2178               e, wnum, VariantName(v));
2179     }
2180     return v;
2181 }
2182
2183 static int leftover_start = 0, leftover_len = 0;
2184 char star_match[STAR_MATCH_N][MSG_SIZ];
2185
2186 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2187    advance *index beyond it, and set leftover_start to the new value of
2188    *index; else return FALSE.  If pattern contains the character '*', it
2189    matches any sequence of characters not containing '\r', '\n', or the
2190    character following the '*' (if any), and the matched sequence(s) are
2191    copied into star_match.
2192    */
2193 int
2194 looking_at ( char *buf, int *index, char *pattern)
2195 {
2196     char *bufp = &buf[*index], *patternp = pattern;
2197     int star_count = 0;
2198     char *matchp = star_match[0];
2199
2200     for (;;) {
2201         if (*patternp == NULLCHAR) {
2202             *index = leftover_start = bufp - buf;
2203             *matchp = NULLCHAR;
2204             return TRUE;
2205         }
2206         if (*bufp == NULLCHAR) return FALSE;
2207         if (*patternp == '*') {
2208             if (*bufp == *(patternp + 1)) {
2209                 *matchp = NULLCHAR;
2210                 matchp = star_match[++star_count];
2211                 patternp += 2;
2212                 bufp++;
2213                 continue;
2214             } else if (*bufp == '\n' || *bufp == '\r') {
2215                 patternp++;
2216                 if (*patternp == NULLCHAR)
2217                   continue;
2218                 else
2219                   return FALSE;
2220             } else {
2221                 *matchp++ = *bufp++;
2222                 continue;
2223             }
2224         }
2225         if (*patternp != *bufp) return FALSE;
2226         patternp++;
2227         bufp++;
2228     }
2229 }
2230
2231 void
2232 SendToPlayer (char *data, int length)
2233 {
2234     int error, outCount;
2235     outCount = OutputToProcess(NoProc, data, length, &error);
2236     if (outCount < length) {
2237         DisplayFatalError(_("Error writing to display"), error, 1);
2238     }
2239 }
2240
2241 void
2242 PackHolding (char packed[], char *holding)
2243 {
2244     char *p = holding;
2245     char *q = packed;
2246     int runlength = 0;
2247     int curr = 9999;
2248     do {
2249         if (*p == curr) {
2250             runlength++;
2251         } else {
2252             switch (runlength) {
2253               case 0:
2254                 break;
2255               case 1:
2256                 *q++ = curr;
2257                 break;
2258               case 2:
2259                 *q++ = curr;
2260                 *q++ = curr;
2261                 break;
2262               default:
2263                 sprintf(q, "%d", runlength);
2264                 while (*q) q++;
2265                 *q++ = curr;
2266                 break;
2267             }
2268             runlength = 1;
2269             curr = *p;
2270         }
2271     } while (*p++);
2272     *q = NULLCHAR;
2273 }
2274
2275 /* Telnet protocol requests from the front end */
2276 void
2277 TelnetRequest (unsigned char ddww, unsigned char option)
2278 {
2279     unsigned char msg[3];
2280     int outCount, outError;
2281
2282     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2283
2284     if (appData.debugMode) {
2285         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2286         switch (ddww) {
2287           case TN_DO:
2288             ddwwStr = "DO";
2289             break;
2290           case TN_DONT:
2291             ddwwStr = "DONT";
2292             break;
2293           case TN_WILL:
2294             ddwwStr = "WILL";
2295             break;
2296           case TN_WONT:
2297             ddwwStr = "WONT";
2298             break;
2299           default:
2300             ddwwStr = buf1;
2301             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2302             break;
2303         }
2304         switch (option) {
2305           case TN_ECHO:
2306             optionStr = "ECHO";
2307             break;
2308           default:
2309             optionStr = buf2;
2310             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2311             break;
2312         }
2313         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2314     }
2315     msg[0] = TN_IAC;
2316     msg[1] = ddww;
2317     msg[2] = option;
2318     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2319     if (outCount < 3) {
2320         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2321     }
2322 }
2323
2324 void
2325 DoEcho ()
2326 {
2327     if (!appData.icsActive) return;
2328     TelnetRequest(TN_DO, TN_ECHO);
2329 }
2330
2331 void
2332 DontEcho ()
2333 {
2334     if (!appData.icsActive) return;
2335     TelnetRequest(TN_DONT, TN_ECHO);
2336 }
2337
2338 void
2339 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2340 {
2341     /* put the holdings sent to us by the server on the board holdings area */
2342     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2343     char p;
2344     ChessSquare piece;
2345
2346     if(gameInfo.holdingsWidth < 2)  return;
2347     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2348         return; // prevent overwriting by pre-board holdings
2349
2350     if( (int)lowestPiece >= BlackPawn ) {
2351         holdingsColumn = 0;
2352         countsColumn = 1;
2353         holdingsStartRow = BOARD_HEIGHT-1;
2354         direction = -1;
2355     } else {
2356         holdingsColumn = BOARD_WIDTH-1;
2357         countsColumn = BOARD_WIDTH-2;
2358         holdingsStartRow = 0;
2359         direction = 1;
2360     }
2361
2362     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2363         board[i][holdingsColumn] = EmptySquare;
2364         board[i][countsColumn]   = (ChessSquare) 0;
2365     }
2366     while( (p=*holdings++) != NULLCHAR ) {
2367         piece = CharToPiece( ToUpper(p) );
2368         if(piece == EmptySquare) continue;
2369         /*j = (int) piece - (int) WhitePawn;*/
2370         j = PieceToNumber(piece);
2371         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2372         if(j < 0) continue;               /* should not happen */
2373         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2374         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2375         board[holdingsStartRow+j*direction][countsColumn]++;
2376     }
2377 }
2378
2379
2380 void
2381 VariantSwitch (Board board, VariantClass newVariant)
2382 {
2383    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2384    static Board oldBoard;
2385
2386    startedFromPositionFile = FALSE;
2387    if(gameInfo.variant == newVariant) return;
2388
2389    /* [HGM] This routine is called each time an assignment is made to
2390     * gameInfo.variant during a game, to make sure the board sizes
2391     * are set to match the new variant. If that means adding or deleting
2392     * holdings, we shift the playing board accordingly
2393     * This kludge is needed because in ICS observe mode, we get boards
2394     * of an ongoing game without knowing the variant, and learn about the
2395     * latter only later. This can be because of the move list we requested,
2396     * in which case the game history is refilled from the beginning anyway,
2397     * but also when receiving holdings of a crazyhouse game. In the latter
2398     * case we want to add those holdings to the already received position.
2399     */
2400
2401
2402    if (appData.debugMode) {
2403      fprintf(debugFP, "Switch board from %s to %s\n",
2404              VariantName(gameInfo.variant), VariantName(newVariant));
2405      setbuf(debugFP, NULL);
2406    }
2407    shuffleOpenings = 0;       /* [HGM] shuffle */
2408    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2409    switch(newVariant)
2410      {
2411      case VariantShogi:
2412        newWidth = 9;  newHeight = 9;
2413        gameInfo.holdingsSize = 7;
2414      case VariantBughouse:
2415      case VariantCrazyhouse:
2416        newHoldingsWidth = 2; break;
2417      case VariantGreat:
2418        newWidth = 10;
2419      case VariantSuper:
2420        newHoldingsWidth = 2;
2421        gameInfo.holdingsSize = 8;
2422        break;
2423      case VariantGothic:
2424      case VariantCapablanca:
2425      case VariantCapaRandom:
2426        newWidth = 10;
2427      default:
2428        newHoldingsWidth = gameInfo.holdingsSize = 0;
2429      };
2430
2431    if(newWidth  != gameInfo.boardWidth  ||
2432       newHeight != gameInfo.boardHeight ||
2433       newHoldingsWidth != gameInfo.holdingsWidth ) {
2434
2435      /* shift position to new playing area, if needed */
2436      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2437        for(i=0; i<BOARD_HEIGHT; i++)
2438          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2439            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2440              board[i][j];
2441        for(i=0; i<newHeight; i++) {
2442          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2443          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2444        }
2445      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2446        for(i=0; i<BOARD_HEIGHT; i++)
2447          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2448            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2449              board[i][j];
2450      }
2451      board[HOLDINGS_SET] = 0;
2452      gameInfo.boardWidth  = newWidth;
2453      gameInfo.boardHeight = newHeight;
2454      gameInfo.holdingsWidth = newHoldingsWidth;
2455      gameInfo.variant = newVariant;
2456      InitDrawingSizes(-2, 0);
2457    } else gameInfo.variant = newVariant;
2458    CopyBoard(oldBoard, board);   // remember correctly formatted board
2459      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2460    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2461 }
2462
2463 static int loggedOn = FALSE;
2464
2465 /*-- Game start info cache: --*/
2466 int gs_gamenum;
2467 char gs_kind[MSG_SIZ];
2468 static char player1Name[128] = "";
2469 static char player2Name[128] = "";
2470 static char cont_seq[] = "\n\\   ";
2471 static int player1Rating = -1;
2472 static int player2Rating = -1;
2473 /*----------------------------*/
2474
2475 ColorClass curColor = ColorNormal;
2476 int suppressKibitz = 0;
2477
2478 // [HGM] seekgraph
2479 Boolean soughtPending = FALSE;
2480 Boolean seekGraphUp;
2481 #define MAX_SEEK_ADS 200
2482 #define SQUARE 0x80
2483 char *seekAdList[MAX_SEEK_ADS];
2484 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2485 float tcList[MAX_SEEK_ADS];
2486 char colorList[MAX_SEEK_ADS];
2487 int nrOfSeekAds = 0;
2488 int minRating = 1010, maxRating = 2800;
2489 int hMargin = 10, vMargin = 20, h, w;
2490 extern int squareSize, lineGap;
2491
2492 void
2493 PlotSeekAd (int i)
2494 {
2495         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2496         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2497         if(r < minRating+100 && r >=0 ) r = minRating+100;
2498         if(r > maxRating) r = maxRating;
2499         if(tc < 1.f) tc = 1.f;
2500         if(tc > 95.f) tc = 95.f;
2501         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2502         y = ((double)r - minRating)/(maxRating - minRating)
2503             * (h-vMargin-squareSize/8-1) + vMargin;
2504         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2505         if(strstr(seekAdList[i], " u ")) color = 1;
2506         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2507            !strstr(seekAdList[i], "bullet") &&
2508            !strstr(seekAdList[i], "blitz") &&
2509            !strstr(seekAdList[i], "standard") ) color = 2;
2510         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2511         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2512 }
2513
2514 void
2515 PlotSingleSeekAd (int i)
2516 {
2517         PlotSeekAd(i);
2518 }
2519
2520 void
2521 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2522 {
2523         char buf[MSG_SIZ], *ext = "";
2524         VariantClass v = StringToVariant(type);
2525         if(strstr(type, "wild")) {
2526             ext = type + 4; // append wild number
2527             if(v == VariantFischeRandom) type = "chess960"; else
2528             if(v == VariantLoadable) type = "setup"; else
2529             type = VariantName(v);
2530         }
2531         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2532         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2533             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2534             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2535             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2536             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2537             seekNrList[nrOfSeekAds] = nr;
2538             zList[nrOfSeekAds] = 0;
2539             seekAdList[nrOfSeekAds++] = StrSave(buf);
2540             if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2541         }
2542 }
2543
2544 void
2545 EraseSeekDot (int i)
2546 {
2547     int x = xList[i], y = yList[i], d=squareSize/4, k;
2548     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2549     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2550     // now replot every dot that overlapped
2551     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2552         int xx = xList[k], yy = yList[k];
2553         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2554             DrawSeekDot(xx, yy, colorList[k]);
2555     }
2556 }
2557
2558 void
2559 RemoveSeekAd (int nr)
2560 {
2561         int i;
2562         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2563             EraseSeekDot(i);
2564             if(seekAdList[i]) free(seekAdList[i]);
2565             seekAdList[i] = seekAdList[--nrOfSeekAds];
2566             seekNrList[i] = seekNrList[nrOfSeekAds];
2567             ratingList[i] = ratingList[nrOfSeekAds];
2568             colorList[i]  = colorList[nrOfSeekAds];
2569             tcList[i] = tcList[nrOfSeekAds];
2570             xList[i]  = xList[nrOfSeekAds];
2571             yList[i]  = yList[nrOfSeekAds];
2572             zList[i]  = zList[nrOfSeekAds];
2573             seekAdList[nrOfSeekAds] = NULL;
2574             break;
2575         }
2576 }
2577
2578 Boolean
2579 MatchSoughtLine (char *line)
2580 {
2581     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2582     int nr, base, inc, u=0; char dummy;
2583
2584     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2585        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2586        (u=1) &&
2587        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2588         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2589         // match: compact and save the line
2590         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2591         return TRUE;
2592     }
2593     return FALSE;
2594 }
2595
2596 int
2597 DrawSeekGraph ()
2598 {
2599     int i;
2600     if(!seekGraphUp) return FALSE;
2601     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2602     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2603
2604     DrawSeekBackground(0, 0, w, h);
2605     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2606     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2607     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2608         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2609         yy = h-1-yy;
2610         DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2611         if(i%500 == 0) {
2612             char buf[MSG_SIZ];
2613             snprintf(buf, MSG_SIZ, "%d", i);
2614             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2615         }
2616     }
2617     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2618     for(i=1; i<100; i+=(i<10?1:5)) {
2619         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2620         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2621         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2622             char buf[MSG_SIZ];
2623             snprintf(buf, MSG_SIZ, "%d", i);
2624             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2625         }
2626     }
2627     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2628     return TRUE;
2629 }
2630
2631 int
2632 SeekGraphClick (ClickType click, int x, int y, int moving)
2633 {
2634     static int lastDown = 0, displayed = 0, lastSecond;
2635     if(y < 0) return FALSE;
2636     if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2637         (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2638         if(!seekGraphUp) return FALSE;
2639         seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2640         DrawPosition(TRUE, NULL);
2641         return TRUE;
2642     }
2643     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2644         if(click == Release || moving) return FALSE;
2645         nrOfSeekAds = 0;
2646         soughtPending = TRUE;
2647         SendToICS(ics_prefix);
2648         SendToICS("sought\n"); // should this be "sought all"?
2649     } else { // issue challenge based on clicked ad
2650         int dist = 10000; int i, closest = 0, second = 0;
2651         for(i=0; i<nrOfSeekAds; i++) {
2652             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2653             if(d < dist) { dist = d; closest = i; }
2654             second += (d - zList[i] < 120); // count in-range ads
2655             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2656         }
2657         if(dist < 120) {
2658             char buf[MSG_SIZ];
2659             second = (second > 1);
2660             if(displayed != closest || second != lastSecond) {
2661                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2662                 lastSecond = second; displayed = closest;
2663             }
2664             if(click == Press) {
2665                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2666                 lastDown = closest;
2667                 return TRUE;
2668             } // on press 'hit', only show info
2669             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2670             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2671             SendToICS(ics_prefix);
2672             SendToICS(buf);
2673             return TRUE; // let incoming board of started game pop down the graph
2674         } else if(click == Release) { // release 'miss' is ignored
2675             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2676             if(moving == 2) { // right up-click
2677                 nrOfSeekAds = 0; // refresh graph
2678                 soughtPending = TRUE;
2679                 SendToICS(ics_prefix);
2680                 SendToICS("sought\n"); // should this be "sought all"?
2681             }
2682             return TRUE;
2683         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2684         // press miss or release hit 'pop down' seek graph
2685         seekGraphUp = FALSE;
2686         DrawPosition(TRUE, NULL);
2687     }
2688     return TRUE;
2689 }
2690
2691 void
2692 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2693 {
2694 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2695 #define STARTED_NONE 0
2696 #define STARTED_MOVES 1
2697 #define STARTED_BOARD 2
2698 #define STARTED_OBSERVE 3
2699 #define STARTED_HOLDINGS 4
2700 #define STARTED_CHATTER 5
2701 #define STARTED_COMMENT 6
2702 #define STARTED_MOVES_NOHIDE 7
2703
2704     static int started = STARTED_NONE;
2705     static char parse[20000];
2706     static int parse_pos = 0;
2707     static char buf[BUF_SIZE + 1];
2708     static int firstTime = TRUE, intfSet = FALSE;
2709     static ColorClass prevColor = ColorNormal;
2710     static int savingComment = FALSE;
2711     static int cmatch = 0; // continuation sequence match
2712     char *bp;
2713     char str[MSG_SIZ];
2714     int i, oldi;
2715     int buf_len;
2716     int next_out;
2717     int tkind;
2718     int backup;    /* [DM] For zippy color lines */
2719     char *p;
2720     char talker[MSG_SIZ]; // [HGM] chat
2721     int channel;
2722
2723     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2724
2725     if (appData.debugMode) {
2726       if (!error) {
2727         fprintf(debugFP, "<ICS: ");
2728         show_bytes(debugFP, data, count);
2729         fprintf(debugFP, "\n");
2730       }
2731     }
2732
2733     if (appData.debugMode) { int f = forwardMostMove;
2734         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2735                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2736                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2737     }
2738     if (count > 0) {
2739         /* If last read ended with a partial line that we couldn't parse,
2740            prepend it to the new read and try again. */
2741         if (leftover_len > 0) {
2742             for (i=0; i<leftover_len; i++)
2743               buf[i] = buf[leftover_start + i];
2744         }
2745
2746     /* copy new characters into the buffer */
2747     bp = buf + leftover_len;
2748     buf_len=leftover_len;
2749     for (i=0; i<count; i++)
2750     {
2751         // ignore these
2752         if (data[i] == '\r')
2753             continue;
2754
2755         // join lines split by ICS?
2756         if (!appData.noJoin)
2757         {
2758             /*
2759                 Joining just consists of finding matches against the
2760                 continuation sequence, and discarding that sequence
2761                 if found instead of copying it.  So, until a match
2762                 fails, there's nothing to do since it might be the
2763                 complete sequence, and thus, something we don't want
2764                 copied.
2765             */
2766             if (data[i] == cont_seq[cmatch])
2767             {
2768                 cmatch++;
2769                 if (cmatch == strlen(cont_seq))
2770                 {
2771                     cmatch = 0; // complete match.  just reset the counter
2772
2773                     /*
2774                         it's possible for the ICS to not include the space
2775                         at the end of the last word, making our [correct]
2776                         join operation fuse two separate words.  the server
2777                         does this when the space occurs at the width setting.
2778                     */
2779                     if (!buf_len || buf[buf_len-1] != ' ')
2780                     {
2781                         *bp++ = ' ';
2782                         buf_len++;
2783                     }
2784                 }
2785                 continue;
2786             }
2787             else if (cmatch)
2788             {
2789                 /*
2790                     match failed, so we have to copy what matched before
2791                     falling through and copying this character.  In reality,
2792                     this will only ever be just the newline character, but
2793                     it doesn't hurt to be precise.
2794                 */
2795                 strncpy(bp, cont_seq, cmatch);
2796                 bp += cmatch;
2797                 buf_len += cmatch;
2798                 cmatch = 0;
2799             }
2800         }
2801
2802         // copy this char
2803         *bp++ = data[i];
2804         buf_len++;
2805     }
2806
2807         buf[buf_len] = NULLCHAR;
2808 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2809         next_out = 0;
2810         leftover_start = 0;
2811
2812         i = 0;
2813         while (i < buf_len) {
2814             /* Deal with part of the TELNET option negotiation
2815                protocol.  We refuse to do anything beyond the
2816                defaults, except that we allow the WILL ECHO option,
2817                which ICS uses to turn off password echoing when we are
2818                directly connected to it.  We reject this option
2819                if localLineEditing mode is on (always on in xboard)
2820                and we are talking to port 23, which might be a real
2821                telnet server that will try to keep WILL ECHO on permanently.
2822              */
2823             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2824                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2825                 unsigned char option;
2826                 oldi = i;
2827                 switch ((unsigned char) buf[++i]) {
2828                   case TN_WILL:
2829                     if (appData.debugMode)
2830                       fprintf(debugFP, "\n<WILL ");
2831                     switch (option = (unsigned char) buf[++i]) {
2832                       case TN_ECHO:
2833                         if (appData.debugMode)
2834                           fprintf(debugFP, "ECHO ");
2835                         /* Reply only if this is a change, according
2836                            to the protocol rules. */
2837                         if (remoteEchoOption) break;
2838                         if (appData.localLineEditing &&
2839                             atoi(appData.icsPort) == TN_PORT) {
2840                             TelnetRequest(TN_DONT, TN_ECHO);
2841                         } else {
2842                             EchoOff();
2843                             TelnetRequest(TN_DO, TN_ECHO);
2844                             remoteEchoOption = TRUE;
2845                         }
2846                         break;
2847                       default:
2848                         if (appData.debugMode)
2849                           fprintf(debugFP, "%d ", option);
2850                         /* Whatever this is, we don't want it. */
2851                         TelnetRequest(TN_DONT, option);
2852                         break;
2853                     }
2854                     break;
2855                   case TN_WONT:
2856                     if (appData.debugMode)
2857                       fprintf(debugFP, "\n<WONT ");
2858                     switch (option = (unsigned char) buf[++i]) {
2859                       case TN_ECHO:
2860                         if (appData.debugMode)
2861                           fprintf(debugFP, "ECHO ");
2862                         /* Reply only if this is a change, according
2863                            to the protocol rules. */
2864                         if (!remoteEchoOption) break;
2865                         EchoOn();
2866                         TelnetRequest(TN_DONT, TN_ECHO);
2867                         remoteEchoOption = FALSE;
2868                         break;
2869                       default:
2870                         if (appData.debugMode)
2871                           fprintf(debugFP, "%d ", (unsigned char) option);
2872                         /* Whatever this is, it must already be turned
2873                            off, because we never agree to turn on
2874                            anything non-default, so according to the
2875                            protocol rules, we don't reply. */
2876                         break;
2877                     }
2878                     break;
2879                   case TN_DO:
2880                     if (appData.debugMode)
2881                       fprintf(debugFP, "\n<DO ");
2882                     switch (option = (unsigned char) buf[++i]) {
2883                       default:
2884                         /* Whatever this is, we refuse to do it. */
2885                         if (appData.debugMode)
2886                           fprintf(debugFP, "%d ", option);
2887                         TelnetRequest(TN_WONT, option);
2888                         break;
2889                     }
2890                     break;
2891                   case TN_DONT:
2892                     if (appData.debugMode)
2893                       fprintf(debugFP, "\n<DONT ");
2894                     switch (option = (unsigned char) buf[++i]) {
2895                       default:
2896                         if (appData.debugMode)
2897                           fprintf(debugFP, "%d ", option);
2898                         /* Whatever this is, we are already not doing
2899                            it, because we never agree to do anything
2900                            non-default, so according to the protocol
2901                            rules, we don't reply. */
2902                         break;
2903                     }
2904                     break;
2905                   case TN_IAC:
2906                     if (appData.debugMode)
2907                       fprintf(debugFP, "\n<IAC ");
2908                     /* Doubled IAC; pass it through */
2909                     i--;
2910                     break;
2911                   default:
2912                     if (appData.debugMode)
2913                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2914                     /* Drop all other telnet commands on the floor */
2915                     break;
2916                 }
2917                 if (oldi > next_out)
2918                   SendToPlayer(&buf[next_out], oldi - next_out);
2919                 if (++i > next_out)
2920                   next_out = i;
2921                 continue;
2922             }
2923
2924             /* OK, this at least will *usually* work */
2925             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2926                 loggedOn = TRUE;
2927             }
2928
2929             if (loggedOn && !intfSet) {
2930                 if (ics_type == ICS_ICC) {
2931                   snprintf(str, MSG_SIZ,
2932                           "/set-quietly interface %s\n/set-quietly style 12\n",
2933                           programVersion);
2934                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2935                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2936                 } else if (ics_type == ICS_CHESSNET) {
2937                   snprintf(str, MSG_SIZ, "/style 12\n");
2938                 } else {
2939                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2940                   strcat(str, programVersion);
2941                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2942                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2943                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2944 #ifdef WIN32
2945                   strcat(str, "$iset nohighlight 1\n");
2946 #endif
2947                   strcat(str, "$iset lock 1\n$style 12\n");
2948                 }
2949                 SendToICS(str);
2950                 NotifyFrontendLogin();
2951                 intfSet = TRUE;
2952             }
2953
2954             if (started == STARTED_COMMENT) {
2955                 /* Accumulate characters in comment */
2956                 parse[parse_pos++] = buf[i];
2957                 if (buf[i] == '\n') {
2958                     parse[parse_pos] = NULLCHAR;
2959                     if(chattingPartner>=0) {
2960                         char mess[MSG_SIZ];
2961                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2962                         OutputChatMessage(chattingPartner, mess);
2963                         chattingPartner = -1;
2964                         next_out = i+1; // [HGM] suppress printing in ICS window
2965                     } else
2966                     if(!suppressKibitz) // [HGM] kibitz
2967                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2968                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2969                         int nrDigit = 0, nrAlph = 0, j;
2970                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2971                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2972                         parse[parse_pos] = NULLCHAR;
2973                         // try to be smart: if it does not look like search info, it should go to
2974                         // ICS interaction window after all, not to engine-output window.
2975                         for(j=0; j<parse_pos; j++) { // count letters and digits
2976                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2977                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2978                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2979                         }
2980                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2981                             int depth=0; float score;
2982                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2983                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2984                                 pvInfoList[forwardMostMove-1].depth = depth;
2985                                 pvInfoList[forwardMostMove-1].score = 100*score;
2986                             }
2987                             OutputKibitz(suppressKibitz, parse);
2988                         } else {
2989                             char tmp[MSG_SIZ];
2990                             if(gameMode == IcsObserving) // restore original ICS messages
2991                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
2992                             else
2993                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2994                             SendToPlayer(tmp, strlen(tmp));
2995                         }
2996                         next_out = i+1; // [HGM] suppress printing in ICS window
2997                     }
2998                     started = STARTED_NONE;
2999                 } else {
3000                     /* Don't match patterns against characters in comment */
3001                     i++;
3002                     continue;
3003                 }
3004             }
3005             if (started == STARTED_CHATTER) {
3006                 if (buf[i] != '\n') {
3007                     /* Don't match patterns against characters in chatter */
3008                     i++;
3009                     continue;
3010                 }
3011                 started = STARTED_NONE;
3012                 if(suppressKibitz) next_out = i+1;
3013             }
3014
3015             /* Kludge to deal with rcmd protocol */
3016             if (firstTime && looking_at(buf, &i, "\001*")) {
3017                 DisplayFatalError(&buf[1], 0, 1);
3018                 continue;
3019             } else {
3020                 firstTime = FALSE;
3021             }
3022
3023             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3024                 ics_type = ICS_ICC;
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, "freechess.org")) {
3031                 ics_type = ICS_FICS;
3032                 ics_prefix = "$";
3033                 if (appData.debugMode)
3034                   fprintf(debugFP, "ics_type %d\n", ics_type);
3035                 continue;
3036             }
3037             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3038                 ics_type = ICS_CHESSNET;
3039                 ics_prefix = "/";
3040                 if (appData.debugMode)
3041                   fprintf(debugFP, "ics_type %d\n", ics_type);
3042                 continue;
3043             }
3044
3045             if (!loggedOn &&
3046                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3047                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3048                  looking_at(buf, &i, "will be \"*\""))) {
3049               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3050               continue;
3051             }
3052
3053             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3054               char buf[MSG_SIZ];
3055               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3056               DisplayIcsInteractionTitle(buf);
3057               have_set_title = TRUE;
3058             }
3059
3060             /* skip finger notes */
3061             if (started == STARTED_NONE &&
3062                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3063                  (buf[i] == '1' && buf[i+1] == '0')) &&
3064                 buf[i+2] == ':' && buf[i+3] == ' ') {
3065               started = STARTED_CHATTER;
3066               i += 3;
3067               continue;
3068             }
3069
3070             oldi = i;
3071             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3072             if(appData.seekGraph) {
3073                 if(soughtPending && MatchSoughtLine(buf+i)) {
3074                     i = strstr(buf+i, "rated") - buf;
3075                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3076                     next_out = leftover_start = i;
3077                     started = STARTED_CHATTER;
3078                     suppressKibitz = TRUE;
3079                     continue;
3080                 }
3081                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3082                         && looking_at(buf, &i, "* ads displayed")) {
3083                     soughtPending = FALSE;
3084                     seekGraphUp = TRUE;
3085                     DrawSeekGraph();
3086                     continue;
3087                 }
3088                 if(appData.autoRefresh) {
3089                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3090                         int s = (ics_type == ICS_ICC); // ICC format differs
3091                         if(seekGraphUp)
3092                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3093                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3094                         looking_at(buf, &i, "*% "); // eat prompt
3095                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3096                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3097                         next_out = i; // suppress
3098                         continue;
3099                     }
3100                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3101                         char *p = star_match[0];
3102                         while(*p) {
3103                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3104                             while(*p && *p++ != ' '); // next
3105                         }
3106                         looking_at(buf, &i, "*% "); // eat prompt
3107                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3108                         next_out = i;
3109                         continue;
3110                     }
3111                 }
3112             }
3113
3114             /* skip formula vars */
3115             if (started == STARTED_NONE &&
3116                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3117               started = STARTED_CHATTER;
3118               i += 3;
3119               continue;
3120             }
3121
3122             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3123             if (appData.autoKibitz && started == STARTED_NONE &&
3124                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3125                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3126                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3127                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3128                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3129                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3130                         suppressKibitz = TRUE;
3131                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3132                         next_out = i;
3133                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3134                                 && (gameMode == IcsPlayingWhite)) ||
3135                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3136                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3137                             started = STARTED_CHATTER; // own kibitz we simply discard
3138                         else {
3139                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3140                             parse_pos = 0; parse[0] = NULLCHAR;
3141                             savingComment = TRUE;
3142                             suppressKibitz = gameMode != IcsObserving ? 2 :
3143                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3144                         }
3145                         continue;
3146                 } else
3147                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3148                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3149                          && atoi(star_match[0])) {
3150                     // suppress the acknowledgements of our own autoKibitz
3151                     char *p;
3152                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3153                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3154                     SendToPlayer(star_match[0], strlen(star_match[0]));
3155                     if(looking_at(buf, &i, "*% ")) // eat prompt
3156                         suppressKibitz = FALSE;
3157                     next_out = i;
3158                     continue;
3159                 }
3160             } // [HGM] kibitz: end of patch
3161
3162             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3163
3164             // [HGM] chat: intercept tells by users for which we have an open chat window
3165             channel = -1;
3166             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3167                                            looking_at(buf, &i, "* whispers:") ||
3168                                            looking_at(buf, &i, "* kibitzes:") ||
3169                                            looking_at(buf, &i, "* shouts:") ||
3170                                            looking_at(buf, &i, "* c-shouts:") ||
3171                                            looking_at(buf, &i, "--> * ") ||
3172                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3173                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3174                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3175                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3176                 int p;
3177                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3178                 chattingPartner = -1;
3179
3180                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3181                 for(p=0; p<MAX_CHAT; p++) {
3182                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3183                     talker[0] = '['; strcat(talker, "] ");
3184                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3185                     chattingPartner = p; break;
3186                     }
3187                 } else
3188                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3189                 for(p=0; p<MAX_CHAT; p++) {
3190                     if(!strcmp("kibitzes", chatPartner[p])) {
3191                         talker[0] = '['; strcat(talker, "] ");
3192                         chattingPartner = p; break;
3193                     }
3194                 } else
3195                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3196                 for(p=0; p<MAX_CHAT; p++) {
3197                     if(!strcmp("whispers", chatPartner[p])) {
3198                         talker[0] = '['; strcat(talker, "] ");
3199                         chattingPartner = p; break;
3200                     }
3201                 } else
3202                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3203                   if(buf[i-8] == '-' && buf[i-3] == 't')
3204                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3205                     if(!strcmp("c-shouts", chatPartner[p])) {
3206                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3207                         chattingPartner = p; break;
3208                     }
3209                   }
3210                   if(chattingPartner < 0)
3211                   for(p=0; p<MAX_CHAT; p++) {
3212                     if(!strcmp("shouts", chatPartner[p])) {
3213                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3214                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3215                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3216                         chattingPartner = p; break;
3217                     }
3218                   }
3219                 }
3220                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3221                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3222                     talker[0] = 0; Colorize(ColorTell, FALSE);
3223                     chattingPartner = p; break;
3224                 }
3225                 if(chattingPartner<0) i = oldi; else {
3226                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3227                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3228                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3229                     started = STARTED_COMMENT;
3230                     parse_pos = 0; parse[0] = NULLCHAR;
3231                     savingComment = 3 + chattingPartner; // counts as TRUE
3232                     suppressKibitz = TRUE;
3233                     continue;
3234                 }
3235             } // [HGM] chat: end of patch
3236
3237           backup = i;
3238             if (appData.zippyTalk || appData.zippyPlay) {
3239                 /* [DM] Backup address for color zippy lines */
3240 #if ZIPPY
3241                if (loggedOn == TRUE)
3242                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3243                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3244 #endif
3245             } // [DM] 'else { ' deleted
3246                 if (
3247                     /* Regular tells and says */
3248                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3249                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3250                     looking_at(buf, &i, "* says: ") ||
3251                     /* Don't color "message" or "messages" output */
3252                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3253                     looking_at(buf, &i, "*. * at *:*: ") ||
3254                     looking_at(buf, &i, "--* (*:*): ") ||
3255                     /* Message notifications (same color as tells) */
3256                     looking_at(buf, &i, "* has left a message ") ||
3257                     looking_at(buf, &i, "* just sent you a message:\n") ||
3258                     /* Whispers and kibitzes */
3259                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3260                     looking_at(buf, &i, "* kibitzes: ") ||
3261                     /* Channel tells */
3262                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3263
3264                   if (tkind == 1 && strchr(star_match[0], ':')) {
3265                       /* Avoid "tells you:" spoofs in channels */
3266                      tkind = 3;
3267                   }
3268                   if (star_match[0][0] == NULLCHAR ||
3269                       strchr(star_match[0], ' ') ||
3270                       (tkind == 3 && strchr(star_match[1], ' '))) {
3271                     /* Reject bogus matches */
3272                     i = oldi;
3273                   } else {
3274                     if (appData.colorize) {
3275                       if (oldi > next_out) {
3276                         SendToPlayer(&buf[next_out], oldi - next_out);
3277                         next_out = oldi;
3278                       }
3279                       switch (tkind) {
3280                       case 1:
3281                         Colorize(ColorTell, FALSE);
3282                         curColor = ColorTell;
3283                         break;
3284                       case 2:
3285                         Colorize(ColorKibitz, FALSE);
3286                         curColor = ColorKibitz;
3287                         break;
3288                       case 3:
3289                         p = strrchr(star_match[1], '(');
3290                         if (p == NULL) {
3291                           p = star_match[1];
3292                         } else {
3293                           p++;
3294                         }
3295                         if (atoi(p) == 1) {
3296                           Colorize(ColorChannel1, FALSE);
3297                           curColor = ColorChannel1;
3298                         } else {
3299                           Colorize(ColorChannel, FALSE);
3300                           curColor = ColorChannel;
3301                         }
3302                         break;
3303                       case 5:
3304                         curColor = ColorNormal;
3305                         break;
3306                       }
3307                     }
3308                     if (started == STARTED_NONE && appData.autoComment &&
3309                         (gameMode == IcsObserving ||
3310                          gameMode == IcsPlayingWhite ||
3311                          gameMode == IcsPlayingBlack)) {
3312                       parse_pos = i - oldi;
3313                       memcpy(parse, &buf[oldi], parse_pos);
3314                       parse[parse_pos] = NULLCHAR;
3315                       started = STARTED_COMMENT;
3316                       savingComment = TRUE;
3317                     } else {
3318                       started = STARTED_CHATTER;
3319                       savingComment = FALSE;
3320                     }
3321                     loggedOn = TRUE;
3322                     continue;
3323                   }
3324                 }
3325
3326                 if (looking_at(buf, &i, "* s-shouts: ") ||
3327                     looking_at(buf, &i, "* c-shouts: ")) {
3328                     if (appData.colorize) {
3329                         if (oldi > next_out) {
3330                             SendToPlayer(&buf[next_out], oldi - next_out);
3331                             next_out = oldi;
3332                         }
3333                         Colorize(ColorSShout, FALSE);
3334                         curColor = ColorSShout;
3335                     }
3336                     loggedOn = TRUE;
3337                     started = STARTED_CHATTER;
3338                     continue;
3339                 }
3340
3341                 if (looking_at(buf, &i, "--->")) {
3342                     loggedOn = TRUE;
3343                     continue;
3344                 }
3345
3346                 if (looking_at(buf, &i, "* shouts: ") ||
3347                     looking_at(buf, &i, "--> ")) {
3348                     if (appData.colorize) {
3349                         if (oldi > next_out) {
3350                             SendToPlayer(&buf[next_out], oldi - next_out);
3351                             next_out = oldi;
3352                         }
3353                         Colorize(ColorShout, FALSE);
3354                         curColor = ColorShout;
3355                     }
3356                     loggedOn = TRUE;
3357                     started = STARTED_CHATTER;
3358                     continue;
3359                 }
3360
3361                 if (looking_at( buf, &i, "Challenge:")) {
3362                     if (appData.colorize) {
3363                         if (oldi > next_out) {
3364                             SendToPlayer(&buf[next_out], oldi - next_out);
3365                             next_out = oldi;
3366                         }
3367                         Colorize(ColorChallenge, FALSE);
3368                         curColor = ColorChallenge;
3369                     }
3370                     loggedOn = TRUE;
3371                     continue;
3372                 }
3373
3374                 if (looking_at(buf, &i, "* offers you") ||
3375                     looking_at(buf, &i, "* offers to be") ||
3376                     looking_at(buf, &i, "* would like to") ||
3377                     looking_at(buf, &i, "* requests to") ||
3378                     looking_at(buf, &i, "Your opponent offers") ||
3379                     looking_at(buf, &i, "Your opponent requests")) {
3380
3381                     if (appData.colorize) {
3382                         if (oldi > next_out) {
3383                             SendToPlayer(&buf[next_out], oldi - next_out);
3384                             next_out = oldi;
3385                         }
3386                         Colorize(ColorRequest, FALSE);
3387                         curColor = ColorRequest;
3388                     }
3389                     continue;
3390                 }
3391
3392                 if (looking_at(buf, &i, "* (*) seeking")) {
3393                     if (appData.colorize) {
3394                         if (oldi > next_out) {
3395                             SendToPlayer(&buf[next_out], oldi - next_out);
3396                             next_out = oldi;
3397                         }
3398                         Colorize(ColorSeek, FALSE);
3399                         curColor = ColorSeek;
3400                     }
3401                     continue;
3402             }
3403
3404           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3405
3406             if (looking_at(buf, &i, "\\   ")) {
3407                 if (prevColor != ColorNormal) {
3408                     if (oldi > next_out) {
3409                         SendToPlayer(&buf[next_out], oldi - next_out);
3410                         next_out = oldi;
3411                     }
3412                     Colorize(prevColor, TRUE);
3413                     curColor = prevColor;
3414                 }
3415                 if (savingComment) {
3416                     parse_pos = i - oldi;
3417                     memcpy(parse, &buf[oldi], parse_pos);
3418                     parse[parse_pos] = NULLCHAR;
3419                     started = STARTED_COMMENT;
3420                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3421                         chattingPartner = savingComment - 3; // kludge to remember the box
3422                 } else {
3423                     started = STARTED_CHATTER;
3424                 }
3425                 continue;
3426             }
3427
3428             if (looking_at(buf, &i, "Black Strength :") ||
3429                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3430                 looking_at(buf, &i, "<10>") ||
3431                 looking_at(buf, &i, "#@#")) {
3432                 /* Wrong board style */
3433                 loggedOn = TRUE;
3434                 SendToICS(ics_prefix);
3435                 SendToICS("set style 12\n");
3436                 SendToICS(ics_prefix);
3437                 SendToICS("refresh\n");
3438                 continue;
3439             }
3440
3441             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3442                 ICSInitScript();
3443                 have_sent_ICS_logon = 1;
3444                 continue;
3445             }
3446
3447             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3448                 (looking_at(buf, &i, "\n<12> ") ||
3449                  looking_at(buf, &i, "<12> "))) {
3450                 loggedOn = TRUE;
3451                 if (oldi > next_out) {
3452                     SendToPlayer(&buf[next_out], oldi - next_out);
3453                 }
3454                 next_out = i;
3455                 started = STARTED_BOARD;
3456                 parse_pos = 0;
3457                 continue;
3458             }
3459
3460             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3461                 looking_at(buf, &i, "<b1> ")) {
3462                 if (oldi > next_out) {
3463                     SendToPlayer(&buf[next_out], oldi - next_out);
3464                 }
3465                 next_out = i;
3466                 started = STARTED_HOLDINGS;
3467                 parse_pos = 0;
3468                 continue;
3469             }
3470
3471             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3472                 loggedOn = TRUE;
3473                 /* Header for a move list -- first line */
3474
3475                 switch (ics_getting_history) {
3476                   case H_FALSE:
3477                     switch (gameMode) {
3478                       case IcsIdle:
3479                       case BeginningOfGame:
3480                         /* User typed "moves" or "oldmoves" while we
3481                            were idle.  Pretend we asked for these
3482                            moves and soak them up so user can step
3483                            through them and/or save them.
3484                            */
3485                         Reset(FALSE, TRUE);
3486                         gameMode = IcsObserving;
3487                         ModeHighlight();
3488                         ics_gamenum = -1;
3489                         ics_getting_history = H_GOT_UNREQ_HEADER;
3490                         break;
3491                       case EditGame: /*?*/
3492                       case EditPosition: /*?*/
3493                         /* Should above feature work in these modes too? */
3494                         /* For now it doesn't */
3495                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3496                         break;
3497                       default:
3498                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3499                         break;
3500                     }
3501                     break;
3502                   case H_REQUESTED:
3503                     /* Is this the right one? */
3504                     if (gameInfo.white && gameInfo.black &&
3505                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3506                         strcmp(gameInfo.black, star_match[2]) == 0) {
3507                         /* All is well */
3508                         ics_getting_history = H_GOT_REQ_HEADER;
3509                     }
3510                     break;
3511                   case H_GOT_REQ_HEADER:
3512                   case H_GOT_UNREQ_HEADER:
3513                   case H_GOT_UNWANTED_HEADER:
3514                   case H_GETTING_MOVES:
3515                     /* Should not happen */
3516                     DisplayError(_("Error gathering move list: two headers"), 0);
3517                     ics_getting_history = H_FALSE;
3518                     break;
3519                 }
3520
3521                 /* Save player ratings into gameInfo if needed */
3522                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3523                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3524                     (gameInfo.whiteRating == -1 ||
3525                      gameInfo.blackRating == -1)) {
3526
3527                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3528                     gameInfo.blackRating = string_to_rating(star_match[3]);
3529                     if (appData.debugMode)
3530                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3531                               gameInfo.whiteRating, gameInfo.blackRating);
3532                 }
3533                 continue;
3534             }
3535
3536             if (looking_at(buf, &i,
3537               "* * match, initial time: * minute*, increment: * second")) {
3538                 /* Header for a move list -- second line */
3539                 /* Initial board will follow if this is a wild game */
3540                 if (gameInfo.event != NULL) free(gameInfo.event);
3541                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3542                 gameInfo.event = StrSave(str);
3543                 /* [HGM] we switched variant. Translate boards if needed. */
3544                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3545                 continue;
3546             }
3547
3548             if (looking_at(buf, &i, "Move  ")) {
3549                 /* Beginning of a move list */
3550                 switch (ics_getting_history) {
3551                   case H_FALSE:
3552                     /* Normally should not happen */
3553                     /* Maybe user hit reset while we were parsing */
3554                     break;
3555                   case H_REQUESTED:
3556                     /* Happens if we are ignoring a move list that is not
3557                      * the one we just requested.  Common if the user
3558                      * tries to observe two games without turning off
3559                      * getMoveList */
3560                     break;
3561                   case H_GETTING_MOVES:
3562                     /* Should not happen */
3563                     DisplayError(_("Error gathering move list: nested"), 0);
3564                     ics_getting_history = H_FALSE;
3565                     break;
3566                   case H_GOT_REQ_HEADER:
3567                     ics_getting_history = H_GETTING_MOVES;
3568                     started = STARTED_MOVES;
3569                     parse_pos = 0;
3570                     if (oldi > next_out) {
3571                         SendToPlayer(&buf[next_out], oldi - next_out);
3572                     }
3573                     break;
3574                   case H_GOT_UNREQ_HEADER:
3575                     ics_getting_history = H_GETTING_MOVES;
3576                     started = STARTED_MOVES_NOHIDE;
3577                     parse_pos = 0;
3578                     break;
3579                   case H_GOT_UNWANTED_HEADER:
3580                     ics_getting_history = H_FALSE;
3581                     break;
3582                 }
3583                 continue;
3584             }
3585
3586             if (looking_at(buf, &i, "% ") ||
3587                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3588                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3589                 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3590                     soughtPending = FALSE;
3591                     seekGraphUp = TRUE;
3592                     DrawSeekGraph();
3593                 }
3594                 if(suppressKibitz) next_out = i;
3595                 savingComment = FALSE;
3596                 suppressKibitz = 0;
3597                 switch (started) {
3598                   case STARTED_MOVES:
3599                   case STARTED_MOVES_NOHIDE:
3600                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3601                     parse[parse_pos + i - oldi] = NULLCHAR;
3602                     ParseGameHistory(parse);
3603 #if ZIPPY
3604                     if (appData.zippyPlay && first.initDone) {
3605                         FeedMovesToProgram(&first, forwardMostMove);
3606                         if (gameMode == IcsPlayingWhite) {
3607                             if (WhiteOnMove(forwardMostMove)) {
3608                                 if (first.sendTime) {
3609                                   if (first.useColors) {
3610                                     SendToProgram("black\n", &first);
3611                                   }
3612                                   SendTimeRemaining(&first, TRUE);
3613                                 }
3614                                 if (first.useColors) {
3615                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3616                                 }
3617                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3618                                 first.maybeThinking = TRUE;
3619                             } else {
3620                                 if (first.usePlayother) {
3621                                   if (first.sendTime) {
3622                                     SendTimeRemaining(&first, TRUE);
3623                                   }
3624                                   SendToProgram("playother\n", &first);
3625                                   firstMove = FALSE;
3626                                 } else {
3627                                   firstMove = TRUE;
3628                                 }
3629                             }
3630                         } else if (gameMode == IcsPlayingBlack) {
3631                             if (!WhiteOnMove(forwardMostMove)) {
3632                                 if (first.sendTime) {
3633                                   if (first.useColors) {
3634                                     SendToProgram("white\n", &first);
3635                                   }
3636                                   SendTimeRemaining(&first, FALSE);
3637                                 }
3638                                 if (first.useColors) {
3639                                   SendToProgram("black\n", &first);
3640                                 }
3641                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3642                                 first.maybeThinking = TRUE;
3643                             } else {
3644                                 if (first.usePlayother) {
3645                                   if (first.sendTime) {
3646                                     SendTimeRemaining(&first, FALSE);
3647                                   }
3648                                   SendToProgram("playother\n", &first);
3649                                   firstMove = FALSE;
3650                                 } else {
3651                                   firstMove = TRUE;
3652                                 }
3653                             }
3654                         }
3655                     }
3656 #endif
3657                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3658                         /* Moves came from oldmoves or moves command
3659                            while we weren't doing anything else.
3660                            */
3661                         currentMove = forwardMostMove;
3662                         ClearHighlights();/*!!could figure this out*/
3663                         flipView = appData.flipView;
3664                         DrawPosition(TRUE, boards[currentMove]);
3665                         DisplayBothClocks();
3666                         snprintf(str, MSG_SIZ, "%s %s %s",
3667                                 gameInfo.white, _("vs."),  gameInfo.black);
3668                         DisplayTitle(str);
3669                         gameMode = IcsIdle;
3670                     } else {
3671                         /* Moves were history of an active game */
3672                         if (gameInfo.resultDetails != NULL) {
3673                             free(gameInfo.resultDetails);
3674                             gameInfo.resultDetails = NULL;
3675                         }
3676                     }
3677                     HistorySet(parseList, backwardMostMove,
3678                                forwardMostMove, currentMove-1);
3679                     DisplayMove(currentMove - 1);
3680                     if (started == STARTED_MOVES) next_out = i;
3681                     started = STARTED_NONE;
3682                     ics_getting_history = H_FALSE;
3683                     break;
3684
3685                   case STARTED_OBSERVE:
3686                     started = STARTED_NONE;
3687                     SendToICS(ics_prefix);
3688                     SendToICS("refresh\n");
3689                     break;
3690
3691                   default:
3692                     break;
3693                 }
3694                 if(bookHit) { // [HGM] book: simulate book reply
3695                     static char bookMove[MSG_SIZ]; // a bit generous?
3696
3697                     programStats.nodes = programStats.depth = programStats.time =
3698                     programStats.score = programStats.got_only_move = 0;
3699                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3700
3701                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3702                     strcat(bookMove, bookHit);
3703                     HandleMachineMove(bookMove, &first);
3704                 }
3705                 continue;
3706             }
3707
3708             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3709                  started == STARTED_HOLDINGS ||
3710                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3711                 /* Accumulate characters in move list or board */
3712                 parse[parse_pos++] = buf[i];
3713             }
3714
3715             /* Start of game messages.  Mostly we detect start of game
3716                when the first board image arrives.  On some versions
3717                of the ICS, though, we need to do a "refresh" after starting
3718                to observe in order to get the current board right away. */
3719             if (looking_at(buf, &i, "Adding game * to observation list")) {
3720                 started = STARTED_OBSERVE;
3721                 continue;
3722             }
3723
3724             /* Handle auto-observe */
3725             if (appData.autoObserve &&
3726                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3727                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3728                 char *player;
3729                 /* Choose the player that was highlighted, if any. */
3730                 if (star_match[0][0] == '\033' ||
3731                     star_match[1][0] != '\033') {
3732                     player = star_match[0];
3733                 } else {
3734                     player = star_match[2];
3735                 }
3736                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3737                         ics_prefix, StripHighlightAndTitle(player));
3738                 SendToICS(str);
3739
3740                 /* Save ratings from notify string */
3741                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3742                 player1Rating = string_to_rating(star_match[1]);
3743                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3744                 player2Rating = string_to_rating(star_match[3]);
3745
3746                 if (appData.debugMode)
3747                   fprintf(debugFP,
3748                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3749                           player1Name, player1Rating,
3750                           player2Name, player2Rating);
3751
3752                 continue;
3753             }
3754
3755             /* Deal with automatic examine mode after a game,
3756                and with IcsObserving -> IcsExamining transition */
3757             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3758                 looking_at(buf, &i, "has made you an examiner of game *")) {
3759
3760                 int gamenum = atoi(star_match[0]);
3761                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3762                     gamenum == ics_gamenum) {
3763                     /* We were already playing or observing this game;
3764                        no need to refetch history */
3765                     gameMode = IcsExamining;
3766                     if (pausing) {
3767                         pauseExamForwardMostMove = forwardMostMove;
3768                     } else if (currentMove < forwardMostMove) {
3769                         ForwardInner(forwardMostMove);
3770                     }
3771                 } else {
3772                     /* I don't think this case really can happen */
3773                     SendToICS(ics_prefix);
3774                     SendToICS("refresh\n");
3775                 }
3776                 continue;
3777             }
3778
3779             /* Error messages */
3780 //          if (ics_user_moved) {
3781             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3782                 if (looking_at(buf, &i, "Illegal move") ||
3783                     looking_at(buf, &i, "Not a legal move") ||
3784                     looking_at(buf, &i, "Your king is in check") ||
3785                     looking_at(buf, &i, "It isn't your turn") ||
3786                     looking_at(buf, &i, "It is not your move")) {
3787                     /* Illegal move */
3788                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3789                         currentMove = forwardMostMove-1;
3790                         DisplayMove(currentMove - 1); /* before DMError */
3791                         DrawPosition(FALSE, boards[currentMove]);
3792                         SwitchClocks(forwardMostMove-1); // [HGM] race
3793                         DisplayBothClocks();
3794                     }
3795                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3796                     ics_user_moved = 0;
3797                     continue;
3798                 }
3799             }
3800
3801             if (looking_at(buf, &i, "still have time") ||
3802                 looking_at(buf, &i, "not out of time") ||
3803                 looking_at(buf, &i, "either player is out of time") ||
3804                 looking_at(buf, &i, "has timeseal; checking")) {
3805                 /* We must have called his flag a little too soon */
3806                 whiteFlag = blackFlag = FALSE;
3807                 continue;
3808             }
3809
3810             if (looking_at(buf, &i, "added * seconds to") ||
3811                 looking_at(buf, &i, "seconds were added to")) {
3812                 /* Update the clocks */
3813                 SendToICS(ics_prefix);
3814                 SendToICS("refresh\n");
3815                 continue;
3816             }
3817
3818             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3819                 ics_clock_paused = TRUE;
3820                 StopClocks();
3821                 continue;
3822             }
3823
3824             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3825                 ics_clock_paused = FALSE;
3826                 StartClocks();
3827                 continue;
3828             }
3829
3830             /* Grab player ratings from the Creating: message.
3831                Note we have to check for the special case when
3832                the ICS inserts things like [white] or [black]. */
3833             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3834                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3835                 /* star_matches:
3836                    0    player 1 name (not necessarily white)
3837                    1    player 1 rating
3838                    2    empty, white, or black (IGNORED)
3839                    3    player 2 name (not necessarily black)
3840                    4    player 2 rating
3841
3842                    The names/ratings are sorted out when the game
3843                    actually starts (below).
3844                 */
3845                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3846                 player1Rating = string_to_rating(star_match[1]);
3847                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3848                 player2Rating = string_to_rating(star_match[4]);
3849
3850                 if (appData.debugMode)
3851                   fprintf(debugFP,
3852                           "Ratings from 'Creating:' %s %d, %s %d\n",
3853                           player1Name, player1Rating,
3854                           player2Name, player2Rating);
3855
3856                 continue;
3857             }
3858
3859             /* Improved generic start/end-of-game messages */
3860             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3861                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3862                 /* If tkind == 0: */
3863                 /* star_match[0] is the game number */
3864                 /*           [1] is the white player's name */
3865                 /*           [2] is the black player's name */
3866                 /* For end-of-game: */
3867                 /*           [3] is the reason for the game end */
3868                 /*           [4] is a PGN end game-token, preceded by " " */
3869                 /* For start-of-game: */
3870                 /*           [3] begins with "Creating" or "Continuing" */
3871                 /*           [4] is " *" or empty (don't care). */
3872                 int gamenum = atoi(star_match[0]);
3873                 char *whitename, *blackname, *why, *endtoken;
3874                 ChessMove endtype = EndOfFile;
3875
3876                 if (tkind == 0) {
3877                   whitename = star_match[1];
3878                   blackname = star_match[2];
3879                   why = star_match[3];
3880                   endtoken = star_match[4];
3881                 } else {
3882                   whitename = star_match[1];
3883                   blackname = star_match[3];
3884                   why = star_match[5];
3885                   endtoken = star_match[6];
3886                 }
3887
3888                 /* Game start messages */
3889                 if (strncmp(why, "Creating ", 9) == 0 ||
3890                     strncmp(why, "Continuing ", 11) == 0) {
3891                     gs_gamenum = gamenum;
3892                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3893                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
3894                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3895 #if ZIPPY
3896                     if (appData.zippyPlay) {
3897                         ZippyGameStart(whitename, blackname);
3898                     }
3899 #endif /*ZIPPY*/
3900                     partnerBoardValid = FALSE; // [HGM] bughouse
3901                     continue;
3902                 }
3903
3904                 /* Game end messages */
3905                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3906                     ics_gamenum != gamenum) {
3907                     continue;
3908                 }
3909                 while (endtoken[0] == ' ') endtoken++;
3910                 switch (endtoken[0]) {
3911                   case '*':
3912                   default:
3913                     endtype = GameUnfinished;
3914                     break;
3915                   case '0':
3916                     endtype = BlackWins;
3917                     break;
3918                   case '1':
3919                     if (endtoken[1] == '/')
3920                       endtype = GameIsDrawn;
3921                     else
3922                       endtype = WhiteWins;
3923                     break;
3924                 }
3925                 GameEnds(endtype, why, GE_ICS);
3926 #if ZIPPY
3927                 if (appData.zippyPlay && first.initDone) {
3928                     ZippyGameEnd(endtype, why);
3929                     if (first.pr == NoProc) {
3930                       /* Start the next process early so that we'll
3931                          be ready for the next challenge */
3932                       StartChessProgram(&first);
3933                     }
3934                     /* Send "new" early, in case this command takes
3935                        a long time to finish, so that we'll be ready
3936                        for the next challenge. */
3937                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3938                     Reset(TRUE, TRUE);
3939                 }
3940 #endif /*ZIPPY*/
3941                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3942                 continue;
3943             }
3944
3945             if (looking_at(buf, &i, "Removing game * from observation") ||
3946                 looking_at(buf, &i, "no longer observing game *") ||
3947                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3948                 if (gameMode == IcsObserving &&
3949                     atoi(star_match[0]) == ics_gamenum)
3950                   {
3951                       /* icsEngineAnalyze */
3952                       if (appData.icsEngineAnalyze) {
3953                             ExitAnalyzeMode();
3954                             ModeHighlight();
3955                       }
3956                       StopClocks();
3957                       gameMode = IcsIdle;
3958                       ics_gamenum = -1;
3959                       ics_user_moved = FALSE;
3960                   }
3961                 continue;
3962             }
3963
3964             if (looking_at(buf, &i, "no longer examining game *")) {
3965                 if (gameMode == IcsExamining &&
3966                     atoi(star_match[0]) == ics_gamenum)
3967                   {
3968                       gameMode = IcsIdle;
3969                       ics_gamenum = -1;
3970                       ics_user_moved = FALSE;
3971                   }
3972                 continue;
3973             }
3974
3975             /* Advance leftover_start past any newlines we find,
3976                so only partial lines can get reparsed */
3977             if (looking_at(buf, &i, "\n")) {
3978                 prevColor = curColor;
3979                 if (curColor != ColorNormal) {
3980                     if (oldi > next_out) {
3981                         SendToPlayer(&buf[next_out], oldi - next_out);
3982                         next_out = oldi;
3983                     }
3984                     Colorize(ColorNormal, FALSE);
3985                     curColor = ColorNormal;
3986                 }
3987                 if (started == STARTED_BOARD) {
3988                     started = STARTED_NONE;
3989                     parse[parse_pos] = NULLCHAR;
3990                     ParseBoard12(parse);
3991                     ics_user_moved = 0;
3992
3993                     /* Send premove here */
3994                     if (appData.premove) {
3995                       char str[MSG_SIZ];
3996                       if (currentMove == 0 &&
3997                           gameMode == IcsPlayingWhite &&
3998                           appData.premoveWhite) {
3999                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
4000                         if (appData.debugMode)
4001                           fprintf(debugFP, "Sending premove:\n");
4002                         SendToICS(str);
4003                       } else if (currentMove == 1 &&
4004                                  gameMode == IcsPlayingBlack &&
4005                                  appData.premoveBlack) {
4006                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
4007                         if (appData.debugMode)
4008                           fprintf(debugFP, "Sending premove:\n");
4009                         SendToICS(str);
4010                       } else if (gotPremove) {
4011                         gotPremove = 0;
4012                         ClearPremoveHighlights();
4013                         if (appData.debugMode)
4014                           fprintf(debugFP, "Sending premove:\n");
4015                           UserMoveEvent(premoveFromX, premoveFromY,
4016                                         premoveToX, premoveToY,
4017                                         premovePromoChar);
4018                       }
4019                     }
4020
4021                     /* Usually suppress following prompt */
4022                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4023                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4024                         if (looking_at(buf, &i, "*% ")) {
4025                             savingComment = FALSE;
4026                             suppressKibitz = 0;
4027                         }
4028                     }
4029                     next_out = i;
4030                 } else if (started == STARTED_HOLDINGS) {
4031                     int gamenum;
4032                     char new_piece[MSG_SIZ];
4033                     started = STARTED_NONE;
4034                     parse[parse_pos] = NULLCHAR;
4035                     if (appData.debugMode)
4036                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4037                                                         parse, currentMove);
4038                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4039                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4040                         if (gameInfo.variant == VariantNormal) {
4041                           /* [HGM] We seem to switch variant during a game!
4042                            * Presumably no holdings were displayed, so we have
4043                            * to move the position two files to the right to
4044                            * create room for them!
4045                            */
4046                           VariantClass newVariant;
4047                           switch(gameInfo.boardWidth) { // base guess on board width
4048                                 case 9:  newVariant = VariantShogi; break;
4049                                 case 10: newVariant = VariantGreat; break;
4050                                 default: newVariant = VariantCrazyhouse; break;
4051                           }
4052                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4053                           /* Get a move list just to see the header, which
4054                              will tell us whether this is really bug or zh */
4055                           if (ics_getting_history == H_FALSE) {
4056                             ics_getting_history = H_REQUESTED;
4057                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4058                             SendToICS(str);
4059                           }
4060                         }
4061                         new_piece[0] = NULLCHAR;
4062                         sscanf(parse, "game %d white [%s black [%s <- %s",
4063                                &gamenum, white_holding, black_holding,
4064                                new_piece);
4065                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4066                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4067                         /* [HGM] copy holdings to board holdings area */
4068                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4069                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4070                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4071 #if ZIPPY
4072                         if (appData.zippyPlay && first.initDone) {
4073                             ZippyHoldings(white_holding, black_holding,
4074                                           new_piece);
4075                         }
4076 #endif /*ZIPPY*/
4077                         if (tinyLayout || smallLayout) {
4078                             char wh[16], bh[16];
4079                             PackHolding(wh, white_holding);
4080                             PackHolding(bh, black_holding);
4081                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4082                                     gameInfo.white, gameInfo.black);
4083                         } else {
4084                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4085                                     gameInfo.white, white_holding, _("vs."),
4086                                     gameInfo.black, black_holding);
4087                         }
4088                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4089                         DrawPosition(FALSE, boards[currentMove]);
4090                         DisplayTitle(str);
4091                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4092                         sscanf(parse, "game %d white [%s black [%s <- %s",
4093                                &gamenum, white_holding, black_holding,
4094                                new_piece);
4095                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4096                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4097                         /* [HGM] copy holdings to partner-board holdings area */
4098                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4099                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4100                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4101                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4102                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4103                       }
4104                     }
4105                     /* Suppress following prompt */
4106                     if (looking_at(buf, &i, "*% ")) {
4107                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4108                         savingComment = FALSE;
4109                         suppressKibitz = 0;
4110                     }
4111                     next_out = i;
4112                 }
4113                 continue;
4114             }
4115
4116             i++;                /* skip unparsed character and loop back */
4117         }
4118
4119         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4120 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4121 //          SendToPlayer(&buf[next_out], i - next_out);
4122             started != STARTED_HOLDINGS && leftover_start > next_out) {
4123             SendToPlayer(&buf[next_out], leftover_start - next_out);
4124             next_out = i;
4125         }
4126
4127         leftover_len = buf_len - leftover_start;
4128         /* if buffer ends with something we couldn't parse,
4129            reparse it after appending the next read */
4130
4131     } else if (count == 0) {
4132         RemoveInputSource(isr);
4133         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4134     } else {
4135         DisplayFatalError(_("Error reading from ICS"), error, 1);
4136     }
4137 }
4138
4139
4140 /* Board style 12 looks like this:
4141
4142    <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
4143
4144  * The "<12> " is stripped before it gets to this routine.  The two
4145  * trailing 0's (flip state and clock ticking) are later addition, and
4146  * some chess servers may not have them, or may have only the first.
4147  * Additional trailing fields may be added in the future.
4148  */
4149
4150 #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"
4151
4152 #define RELATION_OBSERVING_PLAYED    0
4153 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4154 #define RELATION_PLAYING_MYMOVE      1
4155 #define RELATION_PLAYING_NOTMYMOVE  -1
4156 #define RELATION_EXAMINING           2
4157 #define RELATION_ISOLATED_BOARD     -3
4158 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4159
4160 void
4161 ParseBoard12 (char *string)
4162 {
4163     GameMode newGameMode;
4164     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
4165     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
4166     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4167     char to_play, board_chars[200];
4168     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4169     char black[32], white[32];
4170     Board board;
4171     int prevMove = currentMove;
4172     int ticking = 2;
4173     ChessMove moveType;
4174     int fromX, fromY, toX, toY;
4175     char promoChar;
4176     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4177     char *bookHit = NULL; // [HGM] book
4178     Boolean weird = FALSE, reqFlag = FALSE;
4179
4180     fromX = fromY = toX = toY = -1;
4181
4182     newGame = FALSE;
4183
4184     if (appData.debugMode)
4185       fprintf(debugFP, _("Parsing board: %s\n"), string);
4186
4187     move_str[0] = NULLCHAR;
4188     elapsed_time[0] = NULLCHAR;
4189     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4190         int  i = 0, j;
4191         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4192             if(string[i] == ' ') { ranks++; files = 0; }
4193             else files++;
4194             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4195             i++;
4196         }
4197         for(j = 0; j <i; j++) board_chars[j] = string[j];
4198         board_chars[i] = '\0';
4199         string += i + 1;
4200     }
4201     n = sscanf(string, PATTERN, &to_play, &double_push,
4202                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4203                &gamenum, white, black, &relation, &basetime, &increment,
4204                &white_stren, &black_stren, &white_time, &black_time,
4205                &moveNum, str, elapsed_time, move_str, &ics_flip,
4206                &ticking);
4207
4208     if (n < 21) {
4209         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4210         DisplayError(str, 0);
4211         return;
4212     }
4213
4214     /* Convert the move number to internal form */
4215     moveNum = (moveNum - 1) * 2;
4216     if (to_play == 'B') moveNum++;
4217     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4218       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4219                         0, 1);
4220       return;
4221     }
4222
4223     switch (relation) {
4224       case RELATION_OBSERVING_PLAYED:
4225       case RELATION_OBSERVING_STATIC:
4226         if (gamenum == -1) {
4227             /* Old ICC buglet */
4228             relation = RELATION_OBSERVING_STATIC;
4229         }
4230         newGameMode = IcsObserving;
4231         break;
4232       case RELATION_PLAYING_MYMOVE:
4233       case RELATION_PLAYING_NOTMYMOVE:
4234         newGameMode =
4235           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4236             IcsPlayingWhite : IcsPlayingBlack;
4237         soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4238         break;
4239       case RELATION_EXAMINING:
4240         newGameMode = IcsExamining;
4241         break;
4242       case RELATION_ISOLATED_BOARD:
4243       default:
4244         /* Just display this board.  If user was doing something else,
4245            we will forget about it until the next board comes. */
4246         newGameMode = IcsIdle;
4247         break;
4248       case RELATION_STARTING_POSITION:
4249         newGameMode = gameMode;
4250         break;
4251     }
4252
4253     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4254         gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4255          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4256       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4257       int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4258       static int lastBgGame = -1;
4259       char *toSqr;
4260       for (k = 0; k < ranks; k++) {
4261         for (j = 0; j < files; j++)
4262           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4263         if(gameInfo.holdingsWidth > 1) {
4264              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4265              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4266         }
4267       }
4268       CopyBoard(partnerBoard, board);
4269       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4270         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4271         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4272       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4273       if(toSqr = strchr(str, '-')) {
4274         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4275         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4276       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4277       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4278       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4279       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4280       if(twoBoards) {
4281           DisplayWhiteClock(white_time*fac, to_play == 'W');
4282           DisplayBlackClock(black_time*fac, to_play != 'W');
4283           activePartner = to_play;
4284           if(gamenum != lastBgGame) {
4285               char buf[MSG_SIZ];
4286               snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4287               DisplayTitle(buf);
4288           }
4289           lastBgGame = gamenum;
4290           activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4291                       partnerUp = 0; flipView = !flipView; } // [HGM] dual
4292       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4293                  (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4294       DisplayMessage(partnerStatus, "");
4295         partnerBoardValid = TRUE;
4296       return;
4297     }
4298
4299     if(appData.dualBoard && appData.bgObserve) {
4300         if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4301             SendToICS(ics_prefix), SendToICS("pobserve\n");
4302         else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4303             char buf[MSG_SIZ];
4304             snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4305             SendToICS(buf);
4306         }
4307     }
4308
4309     /* Modify behavior for initial board display on move listing
4310        of wild games.
4311        */
4312     switch (ics_getting_history) {
4313       case H_FALSE:
4314       case H_REQUESTED:
4315         break;
4316       case H_GOT_REQ_HEADER:
4317       case H_GOT_UNREQ_HEADER:
4318         /* This is the initial position of the current game */
4319         gamenum = ics_gamenum;
4320         moveNum = 0;            /* old ICS bug workaround */
4321         if (to_play == 'B') {
4322           startedFromSetupPosition = TRUE;
4323           blackPlaysFirst = TRUE;
4324           moveNum = 1;
4325           if (forwardMostMove == 0) forwardMostMove = 1;
4326           if (backwardMostMove == 0) backwardMostMove = 1;
4327           if (currentMove == 0) currentMove = 1;
4328         }
4329         newGameMode = gameMode;
4330         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4331         break;
4332       case H_GOT_UNWANTED_HEADER:
4333         /* This is an initial board that we don't want */
4334         return;
4335       case H_GETTING_MOVES:
4336         /* Should not happen */
4337         DisplayError(_("Error gathering move list: extra board"), 0);
4338         ics_getting_history = H_FALSE;
4339         return;
4340     }
4341
4342    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4343                                         move_str[1] == '@' && !gameInfo.holdingsWidth ||
4344                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4345      /* [HGM] We seem to have switched variant unexpectedly
4346       * Try to guess new variant from board size
4347       */
4348           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4349           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4350           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4351           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4352           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4353           if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4354           if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4355           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4356           /* Get a move list just to see the header, which
4357              will tell us whether this is really bug or zh */
4358           if (ics_getting_history == H_FALSE) {
4359             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4360             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4361             SendToICS(str);
4362           }
4363     }
4364
4365     /* Take action if this is the first board of a new game, or of a
4366        different game than is currently being displayed.  */
4367     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4368         relation == RELATION_ISOLATED_BOARD) {
4369
4370         /* Forget the old game and get the history (if any) of the new one */
4371         if (gameMode != BeginningOfGame) {
4372           Reset(TRUE, TRUE);
4373         }
4374         newGame = TRUE;
4375         if (appData.autoRaiseBoard) BoardToTop();
4376         prevMove = -3;
4377         if (gamenum == -1) {
4378             newGameMode = IcsIdle;
4379         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4380                    appData.getMoveList && !reqFlag) {
4381             /* Need to get game history */
4382             ics_getting_history = H_REQUESTED;
4383             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4384             SendToICS(str);
4385         }
4386
4387         /* Initially flip the board to have black on the bottom if playing
4388            black or if the ICS flip flag is set, but let the user change
4389            it with the Flip View button. */
4390         flipView = appData.autoFlipView ?
4391           (newGameMode == IcsPlayingBlack) || ics_flip :
4392           appData.flipView;
4393
4394         /* Done with values from previous mode; copy in new ones */
4395         gameMode = newGameMode;
4396         ModeHighlight();
4397         ics_gamenum = gamenum;
4398         if (gamenum == gs_gamenum) {
4399             int klen = strlen(gs_kind);
4400             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4401             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4402             gameInfo.event = StrSave(str);
4403         } else {
4404             gameInfo.event = StrSave("ICS game");
4405         }
4406         gameInfo.site = StrSave(appData.icsHost);
4407         gameInfo.date = PGNDate();
4408         gameInfo.round = StrSave("-");
4409         gameInfo.white = StrSave(white);
4410         gameInfo.black = StrSave(black);
4411         timeControl = basetime * 60 * 1000;
4412         timeControl_2 = 0;
4413         timeIncrement = increment * 1000;
4414         movesPerSession = 0;
4415         gameInfo.timeControl = TimeControlTagValue();
4416         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4417   if (appData.debugMode) {
4418     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4419     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4420     setbuf(debugFP, NULL);
4421   }
4422
4423         gameInfo.outOfBook = NULL;
4424
4425         /* Do we have the ratings? */
4426         if (strcmp(player1Name, white) == 0 &&
4427             strcmp(player2Name, black) == 0) {
4428             if (appData.debugMode)
4429               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4430                       player1Rating, player2Rating);
4431             gameInfo.whiteRating = player1Rating;
4432             gameInfo.blackRating = player2Rating;
4433         } else if (strcmp(player2Name, white) == 0 &&
4434                    strcmp(player1Name, black) == 0) {
4435             if (appData.debugMode)
4436               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4437                       player2Rating, player1Rating);
4438             gameInfo.whiteRating = player2Rating;
4439             gameInfo.blackRating = player1Rating;
4440         }
4441         player1Name[0] = player2Name[0] = NULLCHAR;
4442
4443         /* Silence shouts if requested */
4444         if (appData.quietPlay &&
4445             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4446             SendToICS(ics_prefix);
4447             SendToICS("set shout 0\n");
4448         }
4449     }
4450
4451     /* Deal with midgame name changes */
4452     if (!newGame) {
4453         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4454             if (gameInfo.white) free(gameInfo.white);
4455             gameInfo.white = StrSave(white);
4456         }
4457         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4458             if (gameInfo.black) free(gameInfo.black);
4459             gameInfo.black = StrSave(black);
4460         }
4461     }
4462
4463     /* Throw away game result if anything actually changes in examine mode */
4464     if (gameMode == IcsExamining && !newGame) {
4465         gameInfo.result = GameUnfinished;
4466         if (gameInfo.resultDetails != NULL) {
4467             free(gameInfo.resultDetails);
4468             gameInfo.resultDetails = NULL;
4469         }
4470     }
4471
4472     /* In pausing && IcsExamining mode, we ignore boards coming
4473        in if they are in a different variation than we are. */
4474     if (pauseExamInvalid) return;
4475     if (pausing && gameMode == IcsExamining) {
4476         if (moveNum <= pauseExamForwardMostMove) {
4477             pauseExamInvalid = TRUE;
4478             forwardMostMove = pauseExamForwardMostMove;
4479             return;
4480         }
4481     }
4482
4483   if (appData.debugMode) {
4484     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4485   }
4486     /* Parse the board */
4487     for (k = 0; k < ranks; k++) {
4488       for (j = 0; j < files; j++)
4489         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4490       if(gameInfo.holdingsWidth > 1) {
4491            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4492            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4493       }
4494     }
4495     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4496       board[5][BOARD_RGHT+1] = WhiteAngel;
4497       board[6][BOARD_RGHT+1] = WhiteMarshall;
4498       board[1][0] = BlackMarshall;
4499       board[2][0] = BlackAngel;
4500       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4501     }
4502     CopyBoard(boards[moveNum], board);
4503     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4504     if (moveNum == 0) {
4505         startedFromSetupPosition =
4506           !CompareBoards(board, initialPosition);
4507         if(startedFromSetupPosition)
4508             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4509     }
4510
4511     /* [HGM] Set castling rights. Take the outermost Rooks,
4512        to make it also work for FRC opening positions. Note that board12
4513        is really defective for later FRC positions, as it has no way to
4514        indicate which Rook can castle if they are on the same side of King.
4515        For the initial position we grant rights to the outermost Rooks,
4516        and remember thos rights, and we then copy them on positions
4517        later in an FRC game. This means WB might not recognize castlings with
4518        Rooks that have moved back to their original position as illegal,
4519        but in ICS mode that is not its job anyway.
4520     */
4521     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4522     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4523
4524         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4525             if(board[0][i] == WhiteRook) j = i;
4526         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4527         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4528             if(board[0][i] == WhiteRook) j = i;
4529         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4530         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4531             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4532         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4533         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4534             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4535         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4536
4537         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4538         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4539         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4540             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4541         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4542             if(board[BOARD_HEIGHT-1][k] == bKing)
4543                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4544         if(gameInfo.variant == VariantTwoKings) {
4545             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4546             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4547             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4548         }
4549     } else { int r;
4550         r = boards[moveNum][CASTLING][0] = initialRights[0];
4551         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4552         r = boards[moveNum][CASTLING][1] = initialRights[1];
4553         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4554         r = boards[moveNum][CASTLING][3] = initialRights[3];
4555         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4556         r = boards[moveNum][CASTLING][4] = initialRights[4];
4557         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4558         /* wildcastle kludge: always assume King has rights */
4559         r = boards[moveNum][CASTLING][2] = initialRights[2];
4560         r = boards[moveNum][CASTLING][5] = initialRights[5];
4561     }
4562     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4563     boards[moveNum][EP_STATUS] = EP_NONE;
4564     if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4565     if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4566     if(double_push !=  -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4567
4568
4569     if (ics_getting_history == H_GOT_REQ_HEADER ||
4570         ics_getting_history == H_GOT_UNREQ_HEADER) {
4571         /* This was an initial position from a move list, not
4572            the current position */
4573         return;
4574     }
4575
4576     /* Update currentMove and known move number limits */
4577     newMove = newGame || moveNum > forwardMostMove;
4578
4579     if (newGame) {
4580         forwardMostMove = backwardMostMove = currentMove = moveNum;
4581         if (gameMode == IcsExamining && moveNum == 0) {
4582           /* Workaround for ICS limitation: we are not told the wild
4583              type when starting to examine a game.  But if we ask for
4584              the move list, the move list header will tell us */
4585             ics_getting_history = H_REQUESTED;
4586             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4587             SendToICS(str);
4588         }
4589     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4590                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4591 #if ZIPPY
4592         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4593         /* [HGM] applied this also to an engine that is silently watching        */
4594         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4595             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4596             gameInfo.variant == currentlyInitializedVariant) {
4597           takeback = forwardMostMove - moveNum;
4598           for (i = 0; i < takeback; i++) {
4599             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4600             SendToProgram("undo\n", &first);
4601           }
4602         }
4603 #endif
4604
4605         forwardMostMove = moveNum;
4606         if (!pausing || currentMove > forwardMostMove)
4607           currentMove = forwardMostMove;
4608     } else {
4609         /* New part of history that is not contiguous with old part */
4610         if (pausing && gameMode == IcsExamining) {
4611             pauseExamInvalid = TRUE;
4612             forwardMostMove = pauseExamForwardMostMove;
4613             return;
4614         }
4615         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4616 #if ZIPPY
4617             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4618                 // [HGM] when we will receive the move list we now request, it will be
4619                 // fed to the engine from the first move on. So if the engine is not
4620                 // in the initial position now, bring it there.
4621                 InitChessProgram(&first, 0);
4622             }
4623 #endif
4624             ics_getting_history = H_REQUESTED;
4625             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4626             SendToICS(str);
4627         }
4628         forwardMostMove = backwardMostMove = currentMove = moveNum;
4629     }
4630
4631     /* Update the clocks */
4632     if (strchr(elapsed_time, '.')) {
4633       /* Time is in ms */
4634       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4635       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4636     } else {
4637       /* Time is in seconds */
4638       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4639       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4640     }
4641
4642
4643 #if ZIPPY
4644     if (appData.zippyPlay && newGame &&
4645         gameMode != IcsObserving && gameMode != IcsIdle &&
4646         gameMode != IcsExamining)
4647       ZippyFirstBoard(moveNum, basetime, increment);
4648 #endif
4649
4650     /* Put the move on the move list, first converting
4651        to canonical algebraic form. */
4652     if (moveNum > 0) {
4653   if (appData.debugMode) {
4654     if (appData.debugMode) { int f = forwardMostMove;
4655         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4656                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4657                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4658     }
4659     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4660     fprintf(debugFP, "moveNum = %d\n", moveNum);
4661     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4662     setbuf(debugFP, NULL);
4663   }
4664         if (moveNum <= backwardMostMove) {
4665             /* We don't know what the board looked like before
4666                this move.  Punt. */
4667           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4668             strcat(parseList[moveNum - 1], " ");
4669             strcat(parseList[moveNum - 1], elapsed_time);
4670             moveList[moveNum - 1][0] = NULLCHAR;
4671         } else if (strcmp(move_str, "none") == 0) {
4672             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4673             /* Again, we don't know what the board looked like;
4674                this is really the start of the game. */
4675             parseList[moveNum - 1][0] = NULLCHAR;
4676             moveList[moveNum - 1][0] = NULLCHAR;
4677             backwardMostMove = moveNum;
4678             startedFromSetupPosition = TRUE;
4679             fromX = fromY = toX = toY = -1;
4680         } else {
4681           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4682           //                 So we parse the long-algebraic move string in stead of the SAN move
4683           int valid; char buf[MSG_SIZ], *prom;
4684
4685           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4686                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4687           // str looks something like "Q/a1-a2"; kill the slash
4688           if(str[1] == '/')
4689             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4690           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4691           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4692                 strcat(buf, prom); // long move lacks promo specification!
4693           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4694                 if(appData.debugMode)
4695                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4696                 safeStrCpy(move_str, buf, MSG_SIZ);
4697           }
4698           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4699                                 &fromX, &fromY, &toX, &toY, &promoChar)
4700                || ParseOneMove(buf, moveNum - 1, &moveType,
4701                                 &fromX, &fromY, &toX, &toY, &promoChar);
4702           // end of long SAN patch
4703           if (valid) {
4704             (void) CoordsToAlgebraic(boards[moveNum - 1],
4705                                      PosFlags(moveNum - 1),
4706                                      fromY, fromX, toY, toX, promoChar,
4707                                      parseList[moveNum-1]);
4708             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4709               case MT_NONE:
4710               case MT_STALEMATE:
4711               default:
4712                 break;
4713               case MT_CHECK:
4714                 if(gameInfo.variant != VariantShogi)
4715                     strcat(parseList[moveNum - 1], "+");
4716                 break;
4717               case MT_CHECKMATE:
4718               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4719                 strcat(parseList[moveNum - 1], "#");
4720                 break;
4721             }
4722             strcat(parseList[moveNum - 1], " ");
4723             strcat(parseList[moveNum - 1], elapsed_time);
4724             /* currentMoveString is set as a side-effect of ParseOneMove */
4725             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4726             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4727             strcat(moveList[moveNum - 1], "\n");
4728
4729             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4730                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4731               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4732                 ChessSquare old, new = boards[moveNum][k][j];
4733                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4734                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4735                   if(old == new) continue;
4736                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4737                   else if(new == WhiteWazir || new == BlackWazir) {
4738                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4739                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4740                       else boards[moveNum][k][j] = old; // preserve type of Gold
4741                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4742                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4743               }
4744           } else {
4745             /* Move from ICS was illegal!?  Punt. */
4746             if (appData.debugMode) {
4747               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4748               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4749             }
4750             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4751             strcat(parseList[moveNum - 1], " ");
4752             strcat(parseList[moveNum - 1], elapsed_time);
4753             moveList[moveNum - 1][0] = NULLCHAR;
4754             fromX = fromY = toX = toY = -1;
4755           }
4756         }
4757   if (appData.debugMode) {
4758     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4759     setbuf(debugFP, NULL);
4760   }
4761
4762 #if ZIPPY
4763         /* Send move to chess program (BEFORE animating it). */
4764         if (appData.zippyPlay && !newGame && newMove &&
4765            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4766
4767             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4768                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4769                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4770                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4771                             move_str);
4772                     DisplayError(str, 0);
4773                 } else {
4774                     if (first.sendTime) {
4775                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4776                     }
4777                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4778                     if (firstMove && !bookHit) {
4779                         firstMove = FALSE;
4780                         if (first.useColors) {
4781                           SendToProgram(gameMode == IcsPlayingWhite ?
4782                                         "white\ngo\n" :
4783                                         "black\ngo\n", &first);
4784                         } else {
4785                           SendToProgram("go\n", &first);
4786                         }
4787                         first.maybeThinking = TRUE;
4788                     }
4789                 }
4790             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4791               if (moveList[moveNum - 1][0] == NULLCHAR) {
4792                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4793                 DisplayError(str, 0);
4794               } else {
4795                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4796                 SendMoveToProgram(moveNum - 1, &first);
4797               }
4798             }
4799         }
4800 #endif
4801     }
4802
4803     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4804         /* If move comes from a remote source, animate it.  If it
4805            isn't remote, it will have already been animated. */
4806         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4807             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4808         }
4809         if (!pausing && appData.highlightLastMove) {
4810             SetHighlights(fromX, fromY, toX, toY);
4811         }
4812     }
4813
4814     /* Start the clocks */
4815     whiteFlag = blackFlag = FALSE;
4816     appData.clockMode = !(basetime == 0 && increment == 0);
4817     if (ticking == 0) {
4818       ics_clock_paused = TRUE;
4819       StopClocks();
4820     } else if (ticking == 1) {
4821       ics_clock_paused = FALSE;
4822     }
4823     if (gameMode == IcsIdle ||
4824         relation == RELATION_OBSERVING_STATIC ||
4825         relation == RELATION_EXAMINING ||
4826         ics_clock_paused)
4827       DisplayBothClocks();
4828     else
4829       StartClocks();
4830
4831     /* Display opponents and material strengths */
4832     if (gameInfo.variant != VariantBughouse &&
4833         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4834         if (tinyLayout || smallLayout) {
4835             if(gameInfo.variant == VariantNormal)
4836               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4837                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4838                     basetime, increment);
4839             else
4840               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4841                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4842                     basetime, increment, (int) gameInfo.variant);
4843         } else {
4844             if(gameInfo.variant == VariantNormal)
4845               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4846                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4847                     basetime, increment);
4848             else
4849               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
4850                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4851                     basetime, increment, VariantName(gameInfo.variant));
4852         }
4853         DisplayTitle(str);
4854   if (appData.debugMode) {
4855     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4856   }
4857     }
4858
4859
4860     /* Display the board */
4861     if (!pausing && !appData.noGUI) {
4862
4863       if (appData.premove)
4864           if (!gotPremove ||
4865              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4866              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4867               ClearPremoveHighlights();
4868
4869       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4870         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4871       DrawPosition(j, boards[currentMove]);
4872
4873       DisplayMove(moveNum - 1);
4874       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4875             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4876               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4877         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4878       }
4879     }
4880
4881     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4882 #if ZIPPY
4883     if(bookHit) { // [HGM] book: simulate book reply
4884         static char bookMove[MSG_SIZ]; // a bit generous?
4885
4886         programStats.nodes = programStats.depth = programStats.time =
4887         programStats.score = programStats.got_only_move = 0;
4888         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4889
4890         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4891         strcat(bookMove, bookHit);
4892         HandleMachineMove(bookMove, &first);
4893     }
4894 #endif
4895 }
4896
4897 void
4898 GetMoveListEvent ()
4899 {
4900     char buf[MSG_SIZ];
4901     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4902         ics_getting_history = H_REQUESTED;
4903         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4904         SendToICS(buf);
4905     }
4906 }
4907
4908 void
4909 SendToBoth (char *msg)
4910 {   // to make it easy to keep two engines in step in dual analysis
4911     SendToProgram(msg, &first);
4912     if(second.analyzing) SendToProgram(msg, &second);
4913 }
4914
4915 void
4916 AnalysisPeriodicEvent (int force)
4917 {
4918     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4919          && !force) || !appData.periodicUpdates)
4920       return;
4921
4922     /* Send . command to Crafty to collect stats */
4923     SendToBoth(".\n");
4924
4925     /* Don't send another until we get a response (this makes
4926        us stop sending to old Crafty's which don't understand
4927        the "." command (sending illegal cmds resets node count & time,
4928        which looks bad)) */
4929     programStats.ok_to_send = 0;
4930 }
4931
4932 void
4933 ics_update_width (int new_width)
4934 {
4935         ics_printf("set width %d\n", new_width);
4936 }
4937
4938 void
4939 SendMoveToProgram (int moveNum, ChessProgramState *cps)
4940 {
4941     char buf[MSG_SIZ];
4942
4943     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
4944         // null move in variant where engine does not understand it (for analysis purposes)
4945         SendBoard(cps, moveNum + 1); // send position after move in stead.
4946         return;
4947     }
4948     if (cps->useUsermove) {
4949       SendToProgram("usermove ", cps);
4950     }
4951     if (cps->useSAN) {
4952       char *space;
4953       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4954         int len = space - parseList[moveNum];
4955         memcpy(buf, parseList[moveNum], len);
4956         buf[len++] = '\n';
4957         buf[len] = NULLCHAR;
4958       } else {
4959         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4960       }
4961       SendToProgram(buf, cps);
4962     } else {
4963       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4964         AlphaRank(moveList[moveNum], 4);
4965         SendToProgram(moveList[moveNum], cps);
4966         AlphaRank(moveList[moveNum], 4); // and back
4967       } else
4968       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4969        * the engine. It would be nice to have a better way to identify castle
4970        * moves here. */
4971       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4972                                                                          && cps->useOOCastle) {
4973         int fromX = moveList[moveNum][0] - AAA;
4974         int fromY = moveList[moveNum][1] - ONE;
4975         int toX = moveList[moveNum][2] - AAA;
4976         int toY = moveList[moveNum][3] - ONE;
4977         if((boards[moveNum][fromY][fromX] == WhiteKing
4978             && boards[moveNum][toY][toX] == WhiteRook)
4979            || (boards[moveNum][fromY][fromX] == BlackKing
4980                && boards[moveNum][toY][toX] == BlackRook)) {
4981           if(toX > fromX) SendToProgram("O-O\n", cps);
4982           else SendToProgram("O-O-O\n", cps);
4983         }
4984         else SendToProgram(moveList[moveNum], cps);
4985       } else
4986       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
4987         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
4988           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
4989           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
4990                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4991         } else
4992           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
4993                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4994         SendToProgram(buf, cps);
4995       }
4996       else SendToProgram(moveList[moveNum], cps);
4997       /* End of additions by Tord */
4998     }
4999
5000     /* [HGM] setting up the opening has brought engine in force mode! */
5001     /*       Send 'go' if we are in a mode where machine should play. */
5002     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5003         (gameMode == TwoMachinesPlay   ||
5004 #if ZIPPY
5005          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
5006 #endif
5007          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5008         SendToProgram("go\n", cps);
5009   if (appData.debugMode) {
5010     fprintf(debugFP, "(extra)\n");
5011   }
5012     }
5013     setboardSpoiledMachineBlack = 0;
5014 }
5015
5016 void
5017 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5018 {
5019     char user_move[MSG_SIZ];
5020     char suffix[4];
5021
5022     if(gameInfo.variant == VariantSChess && promoChar) {
5023         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5024         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5025     } else suffix[0] = NULLCHAR;
5026
5027     switch (moveType) {
5028       default:
5029         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5030                 (int)moveType, fromX, fromY, toX, toY);
5031         DisplayError(user_move + strlen("say "), 0);
5032         break;
5033       case WhiteKingSideCastle:
5034       case BlackKingSideCastle:
5035       case WhiteQueenSideCastleWild:
5036       case BlackQueenSideCastleWild:
5037       /* PUSH Fabien */
5038       case WhiteHSideCastleFR:
5039       case BlackHSideCastleFR:
5040       /* POP Fabien */
5041         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5042         break;
5043       case WhiteQueenSideCastle:
5044       case BlackQueenSideCastle:
5045       case WhiteKingSideCastleWild:
5046       case BlackKingSideCastleWild:
5047       /* PUSH Fabien */
5048       case WhiteASideCastleFR:
5049       case BlackASideCastleFR:
5050       /* POP Fabien */
5051         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5052         break;
5053       case WhiteNonPromotion:
5054       case BlackNonPromotion:
5055         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5056         break;
5057       case WhitePromotion:
5058       case BlackPromotion:
5059         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
5060           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5061                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5062                 PieceToChar(WhiteFerz));
5063         else if(gameInfo.variant == VariantGreat)
5064           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5065                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5066                 PieceToChar(WhiteMan));
5067         else
5068           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5069                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5070                 promoChar);
5071         break;
5072       case WhiteDrop:
5073       case BlackDrop:
5074       drop:
5075         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5076                  ToUpper(PieceToChar((ChessSquare) fromX)),
5077                  AAA + toX, ONE + toY);
5078         break;
5079       case IllegalMove:  /* could be a variant we don't quite understand */
5080         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5081       case NormalMove:
5082       case WhiteCapturesEnPassant:
5083       case BlackCapturesEnPassant:
5084         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5085                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5086         break;
5087     }
5088     SendToICS(user_move);
5089     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5090         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5091 }
5092
5093 void
5094 UploadGameEvent ()
5095 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5096     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5097     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5098     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5099       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5100       return;
5101     }
5102     if(gameMode != IcsExamining) { // is this ever not the case?
5103         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5104
5105         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5106           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5107         } else { // on FICS we must first go to general examine mode
5108           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5109         }
5110         if(gameInfo.variant != VariantNormal) {
5111             // try figure out wild number, as xboard names are not always valid on ICS
5112             for(i=1; i<=36; i++) {
5113               snprintf(buf, MSG_SIZ, "wild/%d", i);
5114                 if(StringToVariant(buf) == gameInfo.variant) break;
5115             }
5116             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5117             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5118             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5119         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5120         SendToICS(ics_prefix);
5121         SendToICS(buf);
5122         if(startedFromSetupPosition || backwardMostMove != 0) {
5123           fen = PositionToFEN(backwardMostMove, NULL);
5124           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5125             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5126             SendToICS(buf);
5127           } else { // FICS: everything has to set by separate bsetup commands
5128             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5129             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5130             SendToICS(buf);
5131             if(!WhiteOnMove(backwardMostMove)) {
5132                 SendToICS("bsetup tomove black\n");
5133             }
5134             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5135             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5136             SendToICS(buf);
5137             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5138             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5139             SendToICS(buf);
5140             i = boards[backwardMostMove][EP_STATUS];
5141             if(i >= 0) { // set e.p.
5142               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5143                 SendToICS(buf);
5144             }
5145             bsetup++;
5146           }
5147         }
5148       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5149             SendToICS("bsetup done\n"); // switch to normal examining.
5150     }
5151     for(i = backwardMostMove; i<last; i++) {
5152         char buf[20];
5153         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5154         if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5155             int len = strlen(moveList[i]);
5156             snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5157             if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5158         }
5159         SendToICS(buf);
5160     }
5161     SendToICS(ics_prefix);
5162     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5163 }
5164
5165 void
5166 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5167 {
5168     if (rf == DROP_RANK) {
5169       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5170       sprintf(move, "%c@%c%c\n",
5171                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5172     } else {
5173         if (promoChar == 'x' || promoChar == NULLCHAR) {
5174           sprintf(move, "%c%c%c%c\n",
5175                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5176         } else {
5177             sprintf(move, "%c%c%c%c%c\n",
5178                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5179         }
5180     }
5181 }
5182
5183 void
5184 ProcessICSInitScript (FILE *f)
5185 {
5186     char buf[MSG_SIZ];
5187
5188     while (fgets(buf, MSG_SIZ, f)) {
5189         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5190     }
5191
5192     fclose(f);
5193 }
5194
5195
5196 static int lastX, lastY, selectFlag, dragging;
5197
5198 void
5199 Sweep (int step)
5200 {
5201     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5202     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5203     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5204     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5205     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5206     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5207     do {
5208         promoSweep -= step;
5209         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5210         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5211         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5212         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5213         if(!step) step = -1;
5214     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5215             appData.testLegality && (promoSweep == king ||
5216             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5217     if(toX >= 0) {
5218         int victim = boards[currentMove][toY][toX];
5219         boards[currentMove][toY][toX] = promoSweep;
5220         DrawPosition(FALSE, boards[currentMove]);
5221         boards[currentMove][toY][toX] = victim;
5222     } else
5223     ChangeDragPiece(promoSweep);
5224 }
5225
5226 int
5227 PromoScroll (int x, int y)
5228 {
5229   int step = 0;
5230
5231   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5232   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5233   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5234   if(!step) return FALSE;
5235   lastX = x; lastY = y;
5236   if((promoSweep < BlackPawn) == flipView) step = -step;
5237   if(step > 0) selectFlag = 1;
5238   if(!selectFlag) Sweep(step);
5239   return FALSE;
5240 }
5241
5242 void
5243 NextPiece (int step)
5244 {
5245     ChessSquare piece = boards[currentMove][toY][toX];
5246     do {
5247         pieceSweep -= step;
5248         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5249         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5250         if(!step) step = -1;
5251     } while(PieceToChar(pieceSweep) == '.');
5252     boards[currentMove][toY][toX] = pieceSweep;
5253     DrawPosition(FALSE, boards[currentMove]);
5254     boards[currentMove][toY][toX] = piece;
5255 }
5256 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5257 void
5258 AlphaRank (char *move, int n)
5259 {
5260 //    char *p = move, c; int x, y;
5261
5262     if (appData.debugMode) {
5263         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5264     }
5265
5266     if(move[1]=='*' &&
5267        move[2]>='0' && move[2]<='9' &&
5268        move[3]>='a' && move[3]<='x'    ) {
5269         move[1] = '@';
5270         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5271         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5272     } else
5273     if(move[0]>='0' && move[0]<='9' &&
5274        move[1]>='a' && move[1]<='x' &&
5275        move[2]>='0' && move[2]<='9' &&
5276        move[3]>='a' && move[3]<='x'    ) {
5277         /* input move, Shogi -> normal */
5278         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5279         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5280         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5281         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5282     } else
5283     if(move[1]=='@' &&
5284        move[3]>='0' && move[3]<='9' &&
5285        move[2]>='a' && move[2]<='x'    ) {
5286         move[1] = '*';
5287         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5288         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5289     } else
5290     if(
5291        move[0]>='a' && move[0]<='x' &&
5292        move[3]>='0' && move[3]<='9' &&
5293        move[2]>='a' && move[2]<='x'    ) {
5294          /* output move, normal -> Shogi */
5295         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5296         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5297         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5298         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5299         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5300     }
5301     if (appData.debugMode) {
5302         fprintf(debugFP, "   out = '%s'\n", move);
5303     }
5304 }
5305
5306 char yy_textstr[8000];
5307
5308 /* Parser for moves from gnuchess, ICS, or user typein box */
5309 Boolean
5310 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5311 {
5312     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5313
5314     switch (*moveType) {
5315       case WhitePromotion:
5316       case BlackPromotion:
5317       case WhiteNonPromotion:
5318       case BlackNonPromotion:
5319       case NormalMove:
5320       case WhiteCapturesEnPassant:
5321       case BlackCapturesEnPassant:
5322       case WhiteKingSideCastle:
5323       case WhiteQueenSideCastle:
5324       case BlackKingSideCastle:
5325       case BlackQueenSideCastle:
5326       case WhiteKingSideCastleWild:
5327       case WhiteQueenSideCastleWild:
5328       case BlackKingSideCastleWild:
5329       case BlackQueenSideCastleWild:
5330       /* Code added by Tord: */
5331       case WhiteHSideCastleFR:
5332       case WhiteASideCastleFR:
5333       case BlackHSideCastleFR:
5334       case BlackASideCastleFR:
5335       /* End of code added by Tord */
5336       case IllegalMove:         /* bug or odd chess variant */
5337         *fromX = currentMoveString[0] - AAA;
5338         *fromY = currentMoveString[1] - ONE;
5339         *toX = currentMoveString[2] - AAA;
5340         *toY = currentMoveString[3] - ONE;
5341         *promoChar = currentMoveString[4];
5342         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5343             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5344     if (appData.debugMode) {
5345         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5346     }
5347             *fromX = *fromY = *toX = *toY = 0;
5348             return FALSE;
5349         }
5350         if (appData.testLegality) {
5351           return (*moveType != IllegalMove);
5352         } else {
5353           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5354                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5355         }
5356
5357       case WhiteDrop:
5358       case BlackDrop:
5359         *fromX = *moveType == WhiteDrop ?
5360           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5361           (int) CharToPiece(ToLower(currentMoveString[0]));
5362         *fromY = DROP_RANK;
5363         *toX = currentMoveString[2] - AAA;
5364         *toY = currentMoveString[3] - ONE;
5365         *promoChar = NULLCHAR;
5366         return TRUE;
5367
5368       case AmbiguousMove:
5369       case ImpossibleMove:
5370       case EndOfFile:
5371       case ElapsedTime:
5372       case Comment:
5373       case PGNTag:
5374       case NAG:
5375       case WhiteWins:
5376       case BlackWins:
5377       case GameIsDrawn:
5378       default:
5379     if (appData.debugMode) {
5380         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5381     }
5382         /* bug? */
5383         *fromX = *fromY = *toX = *toY = 0;
5384         *promoChar = NULLCHAR;
5385         return FALSE;
5386     }
5387 }
5388
5389 Boolean pushed = FALSE;
5390 char *lastParseAttempt;
5391
5392 void
5393 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5394 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5395   int fromX, fromY, toX, toY; char promoChar;
5396   ChessMove moveType;
5397   Boolean valid;
5398   int nr = 0;
5399
5400   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5401     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5402     pushed = TRUE;
5403   }
5404   endPV = forwardMostMove;
5405   do {
5406     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5407     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5408     lastParseAttempt = pv;
5409     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5410     if(!valid && nr == 0 &&
5411        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5412         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5413         // Hande case where played move is different from leading PV move
5414         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5415         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5416         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5417         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5418           endPV += 2; // if position different, keep this
5419           moveList[endPV-1][0] = fromX + AAA;
5420           moveList[endPV-1][1] = fromY + ONE;
5421           moveList[endPV-1][2] = toX + AAA;
5422           moveList[endPV-1][3] = toY + ONE;
5423           parseList[endPV-1][0] = NULLCHAR;
5424           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5425         }
5426       }
5427     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5428     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5429     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5430     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5431         valid++; // allow comments in PV
5432         continue;
5433     }
5434     nr++;
5435     if(endPV+1 > framePtr) break; // no space, truncate
5436     if(!valid) break;
5437     endPV++;
5438     CopyBoard(boards[endPV], boards[endPV-1]);
5439     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5440     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5441     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5442     CoordsToAlgebraic(boards[endPV - 1],
5443                              PosFlags(endPV - 1),
5444                              fromY, fromX, toY, toX, promoChar,
5445                              parseList[endPV - 1]);
5446   } while(valid);
5447   if(atEnd == 2) return; // used hidden, for PV conversion
5448   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5449   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5450   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5451                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5452   DrawPosition(TRUE, boards[currentMove]);
5453 }
5454
5455 int
5456 MultiPV (ChessProgramState *cps)
5457 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5458         int i;
5459         for(i=0; i<cps->nrOptions; i++)
5460             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5461                 return i;
5462         return -1;
5463 }
5464
5465 Boolean
5466 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5467 {
5468         int startPV, multi, lineStart, origIndex = index;
5469         char *p, buf2[MSG_SIZ];
5470         ChessProgramState *cps = (pane ? &second : &first);
5471
5472         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5473         lastX = x; lastY = y;
5474         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5475         lineStart = startPV = index;
5476         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5477         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5478         index = startPV;
5479         do{ while(buf[index] && buf[index] != '\n') index++;
5480         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5481         buf[index] = 0;
5482         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(cps)) >= 0) {
5483                 int n = cps->option[multi].value;
5484                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5485                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5486                 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5487                 cps->option[multi].value = n;
5488                 *start = *end = 0;
5489                 return FALSE;
5490         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5491                 ExcludeClick(origIndex - lineStart);
5492                 return FALSE;
5493         }
5494         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5495         *start = startPV; *end = index-1;
5496         return TRUE;
5497 }
5498
5499 char *
5500 PvToSAN (char *pv)
5501 {
5502         static char buf[10*MSG_SIZ];
5503         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5504         *buf = NULLCHAR;
5505         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV);
5506         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5507         for(i = forwardMostMove; i<endPV; i++){
5508             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5509             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5510             k += strlen(buf+k);
5511         }
5512         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5513         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5514         endPV = savedEnd;
5515         return buf;
5516 }
5517
5518 Boolean
5519 LoadPV (int x, int y)
5520 { // called on right mouse click to load PV
5521   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5522   lastX = x; lastY = y;
5523   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5524   return TRUE;
5525 }
5526
5527 void
5528 UnLoadPV ()
5529 {
5530   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5531   if(endPV < 0) return;
5532   if(appData.autoCopyPV) CopyFENToClipboard();
5533   endPV = -1;
5534   if(gameMode == AnalyzeMode && currentMove > forwardMostMove) {
5535         Boolean saveAnimate = appData.animate;
5536         if(pushed) {
5537             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5538                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5539             } else storedGames--; // abandon shelved tail of original game
5540         }
5541         pushed = FALSE;
5542         forwardMostMove = currentMove;
5543         currentMove = oldFMM;
5544         appData.animate = FALSE;
5545         ToNrEvent(forwardMostMove);
5546         appData.animate = saveAnimate;
5547   }
5548   currentMove = forwardMostMove;
5549   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5550   ClearPremoveHighlights();
5551   DrawPosition(TRUE, boards[currentMove]);
5552 }
5553
5554 void
5555 MovePV (int x, int y, int h)
5556 { // step through PV based on mouse coordinates (called on mouse move)
5557   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5558
5559   // we must somehow check if right button is still down (might be released off board!)
5560   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5561   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5562   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5563   if(!step) return;
5564   lastX = x; lastY = y;
5565
5566   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5567   if(endPV < 0) return;
5568   if(y < margin) step = 1; else
5569   if(y > h - margin) step = -1;
5570   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5571   currentMove += step;
5572   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5573   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5574                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5575   DrawPosition(FALSE, boards[currentMove]);
5576 }
5577
5578
5579 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5580 // All positions will have equal probability, but the current method will not provide a unique
5581 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5582 #define DARK 1
5583 #define LITE 2
5584 #define ANY 3
5585
5586 int squaresLeft[4];
5587 int piecesLeft[(int)BlackPawn];
5588 int seed, nrOfShuffles;
5589
5590 void
5591 GetPositionNumber ()
5592 {       // sets global variable seed
5593         int i;
5594
5595         seed = appData.defaultFrcPosition;
5596         if(seed < 0) { // randomize based on time for negative FRC position numbers
5597                 for(i=0; i<50; i++) seed += random();
5598                 seed = random() ^ random() >> 8 ^ random() << 8;
5599                 if(seed<0) seed = -seed;
5600         }
5601 }
5602
5603 int
5604 put (Board board, int pieceType, int rank, int n, int shade)
5605 // put the piece on the (n-1)-th empty squares of the given shade
5606 {
5607         int i;
5608
5609         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5610                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5611                         board[rank][i] = (ChessSquare) pieceType;
5612                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5613                         squaresLeft[ANY]--;
5614                         piecesLeft[pieceType]--;
5615                         return i;
5616                 }
5617         }
5618         return -1;
5619 }
5620
5621
5622 void
5623 AddOnePiece (Board board, int pieceType, int rank, int shade)
5624 // calculate where the next piece goes, (any empty square), and put it there
5625 {
5626         int i;
5627
5628         i = seed % squaresLeft[shade];
5629         nrOfShuffles *= squaresLeft[shade];
5630         seed /= squaresLeft[shade];
5631         put(board, pieceType, rank, i, shade);
5632 }
5633
5634 void
5635 AddTwoPieces (Board board, int pieceType, int rank)
5636 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5637 {
5638         int i, n=squaresLeft[ANY], j=n-1, k;
5639
5640         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5641         i = seed % k;  // pick one
5642         nrOfShuffles *= k;
5643         seed /= k;
5644         while(i >= j) i -= j--;
5645         j = n - 1 - j; i += j;
5646         put(board, pieceType, rank, j, ANY);
5647         put(board, pieceType, rank, i, ANY);
5648 }
5649
5650 void
5651 SetUpShuffle (Board board, int number)
5652 {
5653         int i, p, first=1;
5654
5655         GetPositionNumber(); nrOfShuffles = 1;
5656
5657         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5658         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5659         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5660
5661         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5662
5663         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5664             p = (int) board[0][i];
5665             if(p < (int) BlackPawn) piecesLeft[p] ++;
5666             board[0][i] = EmptySquare;
5667         }
5668
5669         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5670             // shuffles restricted to allow normal castling put KRR first
5671             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5672                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5673             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5674                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5675             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5676                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5677             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5678                 put(board, WhiteRook, 0, 0, ANY);
5679             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5680         }
5681
5682         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5683             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5684             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5685                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5686                 while(piecesLeft[p] >= 2) {
5687                     AddOnePiece(board, p, 0, LITE);
5688                     AddOnePiece(board, p, 0, DARK);
5689                 }
5690                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5691             }
5692
5693         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5694             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5695             // but we leave King and Rooks for last, to possibly obey FRC restriction
5696             if(p == (int)WhiteRook) continue;
5697             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5698             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5699         }
5700
5701         // now everything is placed, except perhaps King (Unicorn) and Rooks
5702
5703         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5704             // Last King gets castling rights
5705             while(piecesLeft[(int)WhiteUnicorn]) {
5706                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5707                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5708             }
5709
5710             while(piecesLeft[(int)WhiteKing]) {
5711                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5712                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5713             }
5714
5715
5716         } else {
5717             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5718             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5719         }
5720
5721         // Only Rooks can be left; simply place them all
5722         while(piecesLeft[(int)WhiteRook]) {
5723                 i = put(board, WhiteRook, 0, 0, ANY);
5724                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5725                         if(first) {
5726                                 first=0;
5727                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5728                         }
5729                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5730                 }
5731         }
5732         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5733             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5734         }
5735
5736         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5737 }
5738
5739 int
5740 SetCharTable (char *table, const char * map)
5741 /* [HGM] moved here from winboard.c because of its general usefulness */
5742 /*       Basically a safe strcpy that uses the last character as King */
5743 {
5744     int result = FALSE; int NrPieces;
5745
5746     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5747                     && NrPieces >= 12 && !(NrPieces&1)) {
5748         int i; /* [HGM] Accept even length from 12 to 34 */
5749
5750         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5751         for( i=0; i<NrPieces/2-1; i++ ) {
5752             table[i] = map[i];
5753             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5754         }
5755         table[(int) WhiteKing]  = map[NrPieces/2-1];
5756         table[(int) BlackKing]  = map[NrPieces-1];
5757
5758         result = TRUE;
5759     }
5760
5761     return result;
5762 }
5763
5764 void
5765 Prelude (Board board)
5766 {       // [HGM] superchess: random selection of exo-pieces
5767         int i, j, k; ChessSquare p;
5768         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5769
5770         GetPositionNumber(); // use FRC position number
5771
5772         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5773             SetCharTable(pieceToChar, appData.pieceToCharTable);
5774             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5775                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5776         }
5777
5778         j = seed%4;                 seed /= 4;
5779         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5780         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5781         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5782         j = seed%3 + (seed%3 >= j); seed /= 3;
5783         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5784         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5785         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5786         j = seed%3;                 seed /= 3;
5787         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5788         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5789         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5790         j = seed%2 + (seed%2 >= j); seed /= 2;
5791         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5792         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5793         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5794         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5795         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5796         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5797         put(board, exoPieces[0],    0, 0, ANY);
5798         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5799 }
5800
5801 void
5802 InitPosition (int redraw)
5803 {
5804     ChessSquare (* pieces)[BOARD_FILES];
5805     int i, j, pawnRow, overrule,
5806     oldx = gameInfo.boardWidth,
5807     oldy = gameInfo.boardHeight,
5808     oldh = gameInfo.holdingsWidth;
5809     static int oldv;
5810
5811     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5812
5813     /* [AS] Initialize pv info list [HGM] and game status */
5814     {
5815         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5816             pvInfoList[i].depth = 0;
5817             boards[i][EP_STATUS] = EP_NONE;
5818             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5819         }
5820
5821         initialRulePlies = 0; /* 50-move counter start */
5822
5823         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5824         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5825     }
5826
5827
5828     /* [HGM] logic here is completely changed. In stead of full positions */
5829     /* the initialized data only consist of the two backranks. The switch */
5830     /* selects which one we will use, which is than copied to the Board   */
5831     /* initialPosition, which for the rest is initialized by Pawns and    */
5832     /* empty squares. This initial position is then copied to boards[0],  */
5833     /* possibly after shuffling, so that it remains available.            */
5834
5835     gameInfo.holdingsWidth = 0; /* default board sizes */
5836     gameInfo.boardWidth    = 8;
5837     gameInfo.boardHeight   = 8;
5838     gameInfo.holdingsSize  = 0;
5839     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5840     for(i=0; i<BOARD_FILES-2; i++)
5841       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5842     initialPosition[EP_STATUS] = EP_NONE;
5843     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5844     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5845          SetCharTable(pieceNickName, appData.pieceNickNames);
5846     else SetCharTable(pieceNickName, "............");
5847     pieces = FIDEArray;
5848
5849     switch (gameInfo.variant) {
5850     case VariantFischeRandom:
5851       shuffleOpenings = TRUE;
5852     default:
5853       break;
5854     case VariantShatranj:
5855       pieces = ShatranjArray;
5856       nrCastlingRights = 0;
5857       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5858       break;
5859     case VariantMakruk:
5860       pieces = makrukArray;
5861       nrCastlingRights = 0;
5862       startedFromSetupPosition = TRUE;
5863       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5864       break;
5865     case VariantTwoKings:
5866       pieces = twoKingsArray;
5867       break;
5868     case VariantGrand:
5869       pieces = GrandArray;
5870       nrCastlingRights = 0;
5871       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5872       gameInfo.boardWidth = 10;
5873       gameInfo.boardHeight = 10;
5874       gameInfo.holdingsSize = 7;
5875       break;
5876     case VariantCapaRandom:
5877       shuffleOpenings = TRUE;
5878     case VariantCapablanca:
5879       pieces = CapablancaArray;
5880       gameInfo.boardWidth = 10;
5881       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5882       break;
5883     case VariantGothic:
5884       pieces = GothicArray;
5885       gameInfo.boardWidth = 10;
5886       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5887       break;
5888     case VariantSChess:
5889       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5890       gameInfo.holdingsSize = 7;
5891       for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
5892       break;
5893     case VariantJanus:
5894       pieces = JanusArray;
5895       gameInfo.boardWidth = 10;
5896       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5897       nrCastlingRights = 6;
5898         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5899         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5900         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5901         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5902         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5903         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5904       break;
5905     case VariantFalcon:
5906       pieces = FalconArray;
5907       gameInfo.boardWidth = 10;
5908       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5909       break;
5910     case VariantXiangqi:
5911       pieces = XiangqiArray;
5912       gameInfo.boardWidth  = 9;
5913       gameInfo.boardHeight = 10;
5914       nrCastlingRights = 0;
5915       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5916       break;
5917     case VariantShogi:
5918       pieces = ShogiArray;
5919       gameInfo.boardWidth  = 9;
5920       gameInfo.boardHeight = 9;
5921       gameInfo.holdingsSize = 7;
5922       nrCastlingRights = 0;
5923       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5924       break;
5925     case VariantCourier:
5926       pieces = CourierArray;
5927       gameInfo.boardWidth  = 12;
5928       nrCastlingRights = 0;
5929       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5930       break;
5931     case VariantKnightmate:
5932       pieces = KnightmateArray;
5933       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5934       break;
5935     case VariantSpartan:
5936       pieces = SpartanArray;
5937       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5938       break;
5939     case VariantFairy:
5940       pieces = fairyArray;
5941       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5942       break;
5943     case VariantGreat:
5944       pieces = GreatArray;
5945       gameInfo.boardWidth = 10;
5946       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5947       gameInfo.holdingsSize = 8;
5948       break;
5949     case VariantSuper:
5950       pieces = FIDEArray;
5951       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5952       gameInfo.holdingsSize = 8;
5953       startedFromSetupPosition = TRUE;
5954       break;
5955     case VariantCrazyhouse:
5956     case VariantBughouse:
5957       pieces = FIDEArray;
5958       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5959       gameInfo.holdingsSize = 5;
5960       break;
5961     case VariantWildCastle:
5962       pieces = FIDEArray;
5963       /* !!?shuffle with kings guaranteed to be on d or e file */
5964       shuffleOpenings = 1;
5965       break;
5966     case VariantNoCastle:
5967       pieces = FIDEArray;
5968       nrCastlingRights = 0;
5969       /* !!?unconstrained back-rank shuffle */
5970       shuffleOpenings = 1;
5971       break;
5972     }
5973
5974     overrule = 0;
5975     if(appData.NrFiles >= 0) {
5976         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5977         gameInfo.boardWidth = appData.NrFiles;
5978     }
5979     if(appData.NrRanks >= 0) {
5980         gameInfo.boardHeight = appData.NrRanks;
5981     }
5982     if(appData.holdingsSize >= 0) {
5983         i = appData.holdingsSize;
5984         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5985         gameInfo.holdingsSize = i;
5986     }
5987     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5988     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5989         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5990
5991     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5992     if(pawnRow < 1) pawnRow = 1;
5993     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) pawnRow = 2;
5994
5995     /* User pieceToChar list overrules defaults */
5996     if(appData.pieceToCharTable != NULL)
5997         SetCharTable(pieceToChar, appData.pieceToCharTable);
5998
5999     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6000
6001         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6002             s = (ChessSquare) 0; /* account holding counts in guard band */
6003         for( i=0; i<BOARD_HEIGHT; i++ )
6004             initialPosition[i][j] = s;
6005
6006         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6007         initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
6008         initialPosition[pawnRow][j] = WhitePawn;
6009         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6010         if(gameInfo.variant == VariantXiangqi) {
6011             if(j&1) {
6012                 initialPosition[pawnRow][j] =
6013                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6014                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6015                    initialPosition[2][j] = WhiteCannon;
6016                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6017                 }
6018             }
6019         }
6020         if(gameInfo.variant == VariantGrand) {
6021             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6022                initialPosition[0][j] = WhiteRook;
6023                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6024             }
6025         }
6026         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] =  pieces[1][j-gameInfo.holdingsWidth];
6027     }
6028     if( (gameInfo.variant == VariantShogi) && !overrule ) {
6029
6030             j=BOARD_LEFT+1;
6031             initialPosition[1][j] = WhiteBishop;
6032             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6033             j=BOARD_RGHT-2;
6034             initialPosition[1][j] = WhiteRook;
6035             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6036     }
6037
6038     if( nrCastlingRights == -1) {
6039         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6040         /*       This sets default castling rights from none to normal corners   */
6041         /* Variants with other castling rights must set them themselves above    */
6042         nrCastlingRights = 6;
6043
6044         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6045         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6046         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6047         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6048         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6049         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6050      }
6051
6052      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6053      if(gameInfo.variant == VariantGreat) { // promotion commoners
6054         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6055         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6056         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6057         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6058      }
6059      if( gameInfo.variant == VariantSChess ) {
6060       initialPosition[1][0] = BlackMarshall;
6061       initialPosition[2][0] = BlackAngel;
6062       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6063       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6064       initialPosition[1][1] = initialPosition[2][1] = 
6065       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6066      }
6067   if (appData.debugMode) {
6068     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6069   }
6070     if(shuffleOpenings) {
6071         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6072         startedFromSetupPosition = TRUE;
6073     }
6074     if(startedFromPositionFile) {
6075       /* [HGM] loadPos: use PositionFile for every new game */
6076       CopyBoard(initialPosition, filePosition);
6077       for(i=0; i<nrCastlingRights; i++)
6078           initialRights[i] = filePosition[CASTLING][i];
6079       startedFromSetupPosition = TRUE;
6080     }
6081
6082     CopyBoard(boards[0], initialPosition);
6083
6084     if(oldx != gameInfo.boardWidth ||
6085        oldy != gameInfo.boardHeight ||
6086        oldv != gameInfo.variant ||
6087        oldh != gameInfo.holdingsWidth
6088                                          )
6089             InitDrawingSizes(-2 ,0);
6090
6091     oldv = gameInfo.variant;
6092     if (redraw)
6093       DrawPosition(TRUE, boards[currentMove]);
6094 }
6095
6096 void
6097 SendBoard (ChessProgramState *cps, int moveNum)
6098 {
6099     char message[MSG_SIZ];
6100
6101     if (cps->useSetboard) {
6102       char* fen = PositionToFEN(moveNum, cps->fenOverride);
6103       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6104       SendToProgram(message, cps);
6105       free(fen);
6106
6107     } else {
6108       ChessSquare *bp;
6109       int i, j, left=0, right=BOARD_WIDTH;
6110       /* Kludge to set black to move, avoiding the troublesome and now
6111        * deprecated "black" command.
6112        */
6113       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6114         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6115
6116       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6117
6118       SendToProgram("edit\n", cps);
6119       SendToProgram("#\n", cps);
6120       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6121         bp = &boards[moveNum][i][left];
6122         for (j = left; j < right; j++, bp++) {
6123           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6124           if ((int) *bp < (int) BlackPawn) {
6125             if(j == BOARD_RGHT+1)
6126                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6127             else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6128             if(message[0] == '+' || message[0] == '~') {
6129               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6130                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6131                         AAA + j, ONE + i);
6132             }
6133             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6134                 message[1] = BOARD_RGHT   - 1 - j + '1';
6135                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6136             }
6137             SendToProgram(message, cps);
6138           }
6139         }
6140       }
6141
6142       SendToProgram("c\n", cps);
6143       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6144         bp = &boards[moveNum][i][left];
6145         for (j = left; j < right; j++, bp++) {
6146           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6147           if (((int) *bp != (int) EmptySquare)
6148               && ((int) *bp >= (int) BlackPawn)) {
6149             if(j == BOARD_LEFT-2)
6150                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6151             else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6152                     AAA + j, ONE + i);
6153             if(message[0] == '+' || message[0] == '~') {
6154               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6155                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6156                         AAA + j, ONE + i);
6157             }
6158             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6159                 message[1] = BOARD_RGHT   - 1 - j + '1';
6160                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6161             }
6162             SendToProgram(message, cps);
6163           }
6164         }
6165       }
6166
6167       SendToProgram(".\n", cps);
6168     }
6169     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6170 }
6171
6172 char exclusionHeader[MSG_SIZ];
6173 int exCnt, excludePtr;
6174 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6175 static Exclusion excluTab[200];
6176 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6177
6178 static void
6179 WriteMap (int s)
6180 {
6181     int j;
6182     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6183     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6184 }
6185
6186 static void
6187 ClearMap ()
6188 {
6189     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6190     excludePtr = 24; exCnt = 0;
6191     WriteMap(0);
6192 }
6193
6194 static void
6195 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6196 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6197     char buf[2*MOVE_LEN], *p;
6198     Exclusion *e = excluTab;
6199     int i;
6200     for(i=0; i<exCnt; i++)
6201         if(e[i].ff == fromX && e[i].fr == fromY &&
6202            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6203     if(i == exCnt) { // was not in exclude list; add it
6204         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6205         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6206             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6207             return; // abort
6208         }
6209         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6210         excludePtr++; e[i].mark = excludePtr++;
6211         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6212         exCnt++;
6213     }
6214     exclusionHeader[e[i].mark] = state;
6215 }
6216
6217 static int
6218 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6219 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6220     char buf[MSG_SIZ];
6221     int j, k;
6222     ChessMove moveType;
6223     if((signed char)promoChar == -1) { // kludge to indicate best move
6224         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6225             return 1; // if unparsable, abort
6226     }
6227     // update exclusion map (resolving toggle by consulting existing state)
6228     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6229     j = k%8; k >>= 3;
6230     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6231     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6232          excludeMap[k] |=   1<<j;
6233     else excludeMap[k] &= ~(1<<j);
6234     // update header
6235     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6236     // inform engine
6237     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6238     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6239     SendToBoth(buf);
6240     return (state == '+');
6241 }
6242
6243 static void
6244 ExcludeClick (int index)
6245 {
6246     int i, j;
6247     Exclusion *e = excluTab;
6248     if(index < 25) { // none, best or tail clicked
6249         if(index < 13) { // none: include all
6250             WriteMap(0); // clear map
6251             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6252             SendToBoth("include all\n"); // and inform engine
6253         } else if(index > 18) { // tail
6254             if(exclusionHeader[19] == '-') { // tail was excluded
6255                 SendToBoth("include all\n");
6256                 WriteMap(0); // clear map completely
6257                 // now re-exclude selected moves
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, '-');
6260             } else { // tail was included or in mixed state
6261                 SendToBoth("exclude all\n");
6262                 WriteMap(0xFF); // fill map completely
6263                 // now re-include selected moves
6264                 j = 0; // count them
6265                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6266                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6267                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6268             }
6269         } else { // best
6270             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6271         }
6272     } else {
6273         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6274             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6275             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6276             break;
6277         }
6278     }
6279 }
6280
6281 ChessSquare
6282 DefaultPromoChoice (int white)
6283 {
6284     ChessSquare result;
6285     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
6286         result = WhiteFerz; // no choice
6287     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6288         result= WhiteKing; // in Suicide Q is the last thing we want
6289     else if(gameInfo.variant == VariantSpartan)
6290         result = white ? WhiteQueen : WhiteAngel;
6291     else result = WhiteQueen;
6292     if(!white) result = WHITE_TO_BLACK result;
6293     return result;
6294 }
6295
6296 static int autoQueen; // [HGM] oneclick
6297
6298 int
6299 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6300 {
6301     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6302     /* [HGM] add Shogi promotions */
6303     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6304     ChessSquare piece;
6305     ChessMove moveType;
6306     Boolean premove;
6307
6308     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6309     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6310
6311     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6312       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6313         return FALSE;
6314
6315     piece = boards[currentMove][fromY][fromX];
6316     if(gameInfo.variant == VariantShogi) {
6317         promotionZoneSize = BOARD_HEIGHT/3;
6318         highestPromotingPiece = (int)WhiteFerz;
6319     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6320         promotionZoneSize = 3;
6321     }
6322
6323     // Treat Lance as Pawn when it is not representing Amazon
6324     if(gameInfo.variant != VariantSuper) {
6325         if(piece == WhiteLance) piece = WhitePawn; else
6326         if(piece == BlackLance) piece = BlackPawn;
6327     }
6328
6329     // next weed out all moves that do not touch the promotion zone at all
6330     if((int)piece >= BlackPawn) {
6331         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6332              return FALSE;
6333         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6334     } else {
6335         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6336            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6337     }
6338
6339     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6340
6341     // weed out mandatory Shogi promotions
6342     if(gameInfo.variant == VariantShogi) {
6343         if(piece >= BlackPawn) {
6344             if(toY == 0 && piece == BlackPawn ||
6345                toY == 0 && piece == BlackQueen ||
6346                toY <= 1 && piece == BlackKnight) {
6347                 *promoChoice = '+';
6348                 return FALSE;
6349             }
6350         } else {
6351             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6352                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6353                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6354                 *promoChoice = '+';
6355                 return FALSE;
6356             }
6357         }
6358     }
6359
6360     // weed out obviously illegal Pawn moves
6361     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6362         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6363         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6364         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6365         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6366         // note we are not allowed to test for valid (non-)capture, due to premove
6367     }
6368
6369     // we either have a choice what to promote to, or (in Shogi) whether to promote
6370     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6371         *promoChoice = PieceToChar(BlackFerz);  // no choice
6372         return FALSE;
6373     }
6374     // no sense asking what we must promote to if it is going to explode...
6375     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6376         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6377         return FALSE;
6378     }
6379     // give caller the default choice even if we will not make it
6380     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6381     if(gameInfo.variant == VariantShogi) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6382     if(        sweepSelect && gameInfo.variant != VariantGreat
6383                            && gameInfo.variant != VariantGrand
6384                            && gameInfo.variant != VariantSuper) return FALSE;
6385     if(autoQueen) return FALSE; // predetermined
6386
6387     // suppress promotion popup on illegal moves that are not premoves
6388     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6389               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6390     if(appData.testLegality && !premove) {
6391         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6392                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6393         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6394             return FALSE;
6395     }
6396
6397     return TRUE;
6398 }
6399
6400 int
6401 InPalace (int row, int column)
6402 {   /* [HGM] for Xiangqi */
6403     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6404          column < (BOARD_WIDTH + 4)/2 &&
6405          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6406     return FALSE;
6407 }
6408
6409 int
6410 PieceForSquare (int x, int y)
6411 {
6412   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6413      return -1;
6414   else
6415      return boards[currentMove][y][x];
6416 }
6417
6418 int
6419 OKToStartUserMove (int x, int y)
6420 {
6421     ChessSquare from_piece;
6422     int white_piece;
6423
6424     if (matchMode) return FALSE;
6425     if (gameMode == EditPosition) return TRUE;
6426
6427     if (x >= 0 && y >= 0)
6428       from_piece = boards[currentMove][y][x];
6429     else
6430       from_piece = EmptySquare;
6431
6432     if (from_piece == EmptySquare) return FALSE;
6433
6434     white_piece = (int)from_piece >= (int)WhitePawn &&
6435       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6436
6437     switch (gameMode) {
6438       case AnalyzeFile:
6439       case TwoMachinesPlay:
6440       case EndOfGame:
6441         return FALSE;
6442
6443       case IcsObserving:
6444       case IcsIdle:
6445         return FALSE;
6446
6447       case MachinePlaysWhite:
6448       case IcsPlayingBlack:
6449         if (appData.zippyPlay) return FALSE;
6450         if (white_piece) {
6451             DisplayMoveError(_("You are playing Black"));
6452             return FALSE;
6453         }
6454         break;
6455
6456       case MachinePlaysBlack:
6457       case IcsPlayingWhite:
6458         if (appData.zippyPlay) return FALSE;
6459         if (!white_piece) {
6460             DisplayMoveError(_("You are playing White"));
6461             return FALSE;
6462         }
6463         break;
6464
6465       case PlayFromGameFile:
6466             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6467       case EditGame:
6468         if (!white_piece && WhiteOnMove(currentMove)) {
6469             DisplayMoveError(_("It is White's turn"));
6470             return FALSE;
6471         }
6472         if (white_piece && !WhiteOnMove(currentMove)) {
6473             DisplayMoveError(_("It is Black's turn"));
6474             return FALSE;
6475         }
6476         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6477             /* Editing correspondence game history */
6478             /* Could disallow this or prompt for confirmation */
6479             cmailOldMove = -1;
6480         }
6481         break;
6482
6483       case BeginningOfGame:
6484         if (appData.icsActive) return FALSE;
6485         if (!appData.noChessProgram) {
6486             if (!white_piece) {
6487                 DisplayMoveError(_("You are playing White"));
6488                 return FALSE;
6489             }
6490         }
6491         break;
6492
6493       case Training:
6494         if (!white_piece && WhiteOnMove(currentMove)) {
6495             DisplayMoveError(_("It is White's turn"));
6496             return FALSE;
6497         }
6498         if (white_piece && !WhiteOnMove(currentMove)) {
6499             DisplayMoveError(_("It is Black's turn"));
6500             return FALSE;
6501         }
6502         break;
6503
6504       default:
6505       case IcsExamining:
6506         break;
6507     }
6508     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6509         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6510         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6511         && gameMode != AnalyzeFile && gameMode != Training) {
6512         DisplayMoveError(_("Displayed position is not current"));
6513         return FALSE;
6514     }
6515     return TRUE;
6516 }
6517
6518 Boolean
6519 OnlyMove (int *x, int *y, Boolean captures) 
6520 {
6521     DisambiguateClosure cl;
6522     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6523     switch(gameMode) {
6524       case MachinePlaysBlack:
6525       case IcsPlayingWhite:
6526       case BeginningOfGame:
6527         if(!WhiteOnMove(currentMove)) return FALSE;
6528         break;
6529       case MachinePlaysWhite:
6530       case IcsPlayingBlack:
6531         if(WhiteOnMove(currentMove)) return FALSE;
6532         break;
6533       case EditGame:
6534         break;
6535       default:
6536         return FALSE;
6537     }
6538     cl.pieceIn = EmptySquare;
6539     cl.rfIn = *y;
6540     cl.ffIn = *x;
6541     cl.rtIn = -1;
6542     cl.ftIn = -1;
6543     cl.promoCharIn = NULLCHAR;
6544     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6545     if( cl.kind == NormalMove ||
6546         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6547         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6548         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6549       fromX = cl.ff;
6550       fromY = cl.rf;
6551       *x = cl.ft;
6552       *y = cl.rt;
6553       return TRUE;
6554     }
6555     if(cl.kind != ImpossibleMove) return FALSE;
6556     cl.pieceIn = EmptySquare;
6557     cl.rfIn = -1;
6558     cl.ffIn = -1;
6559     cl.rtIn = *y;
6560     cl.ftIn = *x;
6561     cl.promoCharIn = NULLCHAR;
6562     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6563     if( cl.kind == NormalMove ||
6564         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6565         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6566         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6567       fromX = cl.ff;
6568       fromY = cl.rf;
6569       *x = cl.ft;
6570       *y = cl.rt;
6571       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6572       return TRUE;
6573     }
6574     return FALSE;
6575 }
6576
6577 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6578 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6579 int lastLoadGameUseList = FALSE;
6580 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6581 ChessMove lastLoadGameStart = EndOfFile;
6582 int doubleClick;
6583
6584 void
6585 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6586 {
6587     ChessMove moveType;
6588     ChessSquare pup;
6589     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6590
6591     /* Check if the user is playing in turn.  This is complicated because we
6592        let the user "pick up" a piece before it is his turn.  So the piece he
6593        tried to pick up may have been captured by the time he puts it down!
6594        Therefore we use the color the user is supposed to be playing in this
6595        test, not the color of the piece that is currently on the starting
6596        square---except in EditGame mode, where the user is playing both
6597        sides; fortunately there the capture race can't happen.  (It can
6598        now happen in IcsExamining mode, but that's just too bad.  The user
6599        will get a somewhat confusing message in that case.)
6600        */
6601
6602     switch (gameMode) {
6603       case AnalyzeFile:
6604       case TwoMachinesPlay:
6605       case EndOfGame:
6606       case IcsObserving:
6607       case IcsIdle:
6608         /* We switched into a game mode where moves are not accepted,
6609            perhaps while the mouse button was down. */
6610         return;
6611
6612       case MachinePlaysWhite:
6613         /* User is moving for Black */
6614         if (WhiteOnMove(currentMove)) {
6615             DisplayMoveError(_("It is White's turn"));
6616             return;
6617         }
6618         break;
6619
6620       case MachinePlaysBlack:
6621         /* User is moving for White */
6622         if (!WhiteOnMove(currentMove)) {
6623             DisplayMoveError(_("It is Black's turn"));
6624             return;
6625         }
6626         break;
6627
6628       case PlayFromGameFile:
6629             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6630       case EditGame:
6631       case IcsExamining:
6632       case BeginningOfGame:
6633       case AnalyzeMode:
6634       case Training:
6635         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6636         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6637             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6638             /* User is moving for Black */
6639             if (WhiteOnMove(currentMove)) {
6640                 DisplayMoveError(_("It is White's turn"));
6641                 return;
6642             }
6643         } else {
6644             /* User is moving for White */
6645             if (!WhiteOnMove(currentMove)) {
6646                 DisplayMoveError(_("It is Black's turn"));
6647                 return;
6648             }
6649         }
6650         break;
6651
6652       case IcsPlayingBlack:
6653         /* User is moving for Black */
6654         if (WhiteOnMove(currentMove)) {
6655             if (!appData.premove) {
6656                 DisplayMoveError(_("It is White's turn"));
6657             } else if (toX >= 0 && toY >= 0) {
6658                 premoveToX = toX;
6659                 premoveToY = toY;
6660                 premoveFromX = fromX;
6661                 premoveFromY = fromY;
6662                 premovePromoChar = promoChar;
6663                 gotPremove = 1;
6664                 if (appData.debugMode)
6665                     fprintf(debugFP, "Got premove: fromX %d,"
6666                             "fromY %d, toX %d, toY %d\n",
6667                             fromX, fromY, toX, toY);
6668             }
6669             return;
6670         }
6671         break;
6672
6673       case IcsPlayingWhite:
6674         /* User is moving for White */
6675         if (!WhiteOnMove(currentMove)) {
6676             if (!appData.premove) {
6677                 DisplayMoveError(_("It is Black's turn"));
6678             } else if (toX >= 0 && toY >= 0) {
6679                 premoveToX = toX;
6680                 premoveToY = toY;
6681                 premoveFromX = fromX;
6682                 premoveFromY = fromY;
6683                 premovePromoChar = promoChar;
6684                 gotPremove = 1;
6685                 if (appData.debugMode)
6686                     fprintf(debugFP, "Got premove: fromX %d,"
6687                             "fromY %d, toX %d, toY %d\n",
6688                             fromX, fromY, toX, toY);
6689             }
6690             return;
6691         }
6692         break;
6693
6694       default:
6695         break;
6696
6697       case EditPosition:
6698         /* EditPosition, empty square, or different color piece;
6699            click-click move is possible */
6700         if (toX == -2 || toY == -2) {
6701             boards[0][fromY][fromX] = EmptySquare;
6702             DrawPosition(FALSE, boards[currentMove]);
6703             return;
6704         } else if (toX >= 0 && toY >= 0) {
6705             boards[0][toY][toX] = boards[0][fromY][fromX];
6706             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6707                 if(boards[0][fromY][0] != EmptySquare) {
6708                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6709                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6710                 }
6711             } else
6712             if(fromX == BOARD_RGHT+1) {
6713                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6714                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6715                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6716                 }
6717             } else
6718             boards[0][fromY][fromX] = gatingPiece;
6719             DrawPosition(FALSE, boards[currentMove]);
6720             return;
6721         }
6722         return;
6723     }
6724
6725     if(toX < 0 || toY < 0) return;
6726     pup = boards[currentMove][toY][toX];
6727
6728     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6729     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6730          if( pup != EmptySquare ) return;
6731          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6732            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6733                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6734            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6735            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6736            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6737            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6738          fromY = DROP_RANK;
6739     }
6740
6741     /* [HGM] always test for legality, to get promotion info */
6742     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6743                                          fromY, fromX, toY, toX, promoChar);
6744
6745     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6746
6747     /* [HGM] but possibly ignore an IllegalMove result */
6748     if (appData.testLegality) {
6749         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6750             DisplayMoveError(_("Illegal move"));
6751             return;
6752         }
6753     }
6754
6755     if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
6756         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
6757              ClearPremoveHighlights(); // was included
6758         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
6759         return;
6760     }
6761
6762     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6763 }
6764
6765 /* Common tail of UserMoveEvent and DropMenuEvent */
6766 int
6767 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
6768 {
6769     char *bookHit = 0;
6770
6771     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6772         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6773         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6774         if(WhiteOnMove(currentMove)) {
6775             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6776         } else {
6777             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6778         }
6779     }
6780
6781     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6782        move type in caller when we know the move is a legal promotion */
6783     if(moveType == NormalMove && promoChar)
6784         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6785
6786     /* [HGM] <popupFix> The following if has been moved here from
6787        UserMoveEvent(). Because it seemed to belong here (why not allow
6788        piece drops in training games?), and because it can only be
6789        performed after it is known to what we promote. */
6790     if (gameMode == Training) {
6791       /* compare the move played on the board to the next move in the
6792        * game. If they match, display the move and the opponent's response.
6793        * If they don't match, display an error message.
6794        */
6795       int saveAnimate;
6796       Board testBoard;
6797       CopyBoard(testBoard, boards[currentMove]);
6798       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6799
6800       if (CompareBoards(testBoard, boards[currentMove+1])) {
6801         ForwardInner(currentMove+1);
6802
6803         /* Autoplay the opponent's response.
6804          * if appData.animate was TRUE when Training mode was entered,
6805          * the response will be animated.
6806          */
6807         saveAnimate = appData.animate;
6808         appData.animate = animateTraining;
6809         ForwardInner(currentMove+1);
6810         appData.animate = saveAnimate;
6811
6812         /* check for the end of the game */
6813         if (currentMove >= forwardMostMove) {
6814           gameMode = PlayFromGameFile;
6815           ModeHighlight();
6816           SetTrainingModeOff();
6817           DisplayInformation(_("End of game"));
6818         }
6819       } else {
6820         DisplayError(_("Incorrect move"), 0);
6821       }
6822       return 1;
6823     }
6824
6825   /* Ok, now we know that the move is good, so we can kill
6826      the previous line in Analysis Mode */
6827   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6828                                 && currentMove < forwardMostMove) {
6829     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6830     else forwardMostMove = currentMove;
6831   }
6832
6833   ClearMap();
6834
6835   /* If we need the chess program but it's dead, restart it */
6836   ResurrectChessProgram();
6837
6838   /* A user move restarts a paused game*/
6839   if (pausing)
6840     PauseEvent();
6841
6842   thinkOutput[0] = NULLCHAR;
6843
6844   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6845
6846   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6847     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6848     return 1;
6849   }
6850
6851   if (gameMode == BeginningOfGame) {
6852     if (appData.noChessProgram) {
6853       gameMode = EditGame;
6854       SetGameInfo();
6855     } else {
6856       char buf[MSG_SIZ];
6857       gameMode = MachinePlaysBlack;
6858       StartClocks();
6859       SetGameInfo();
6860       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
6861       DisplayTitle(buf);
6862       if (first.sendName) {
6863         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6864         SendToProgram(buf, &first);
6865       }
6866       StartClocks();
6867     }
6868     ModeHighlight();
6869   }
6870
6871   /* Relay move to ICS or chess engine */
6872   if (appData.icsActive) {
6873     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6874         gameMode == IcsExamining) {
6875       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6876         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6877         SendToICS("draw ");
6878         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6879       }
6880       // also send plain move, in case ICS does not understand atomic claims
6881       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6882       ics_user_moved = 1;
6883     }
6884   } else {
6885     if (first.sendTime && (gameMode == BeginningOfGame ||
6886                            gameMode == MachinePlaysWhite ||
6887                            gameMode == MachinePlaysBlack)) {
6888       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6889     }
6890     if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
6891          // [HGM] book: if program might be playing, let it use book
6892         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6893         first.maybeThinking = TRUE;
6894     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
6895         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
6896         SendBoard(&first, currentMove+1);
6897         if(second.analyzing) {
6898             if(!second.useSetboard) SendToProgram("undo\n", &second);
6899             SendBoard(&second, currentMove+1);
6900         }
6901     } else {
6902         SendMoveToProgram(forwardMostMove-1, &first);
6903         if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
6904     }
6905     if (currentMove == cmailOldMove + 1) {
6906       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6907     }
6908   }
6909
6910   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6911
6912   switch (gameMode) {
6913   case EditGame:
6914     if(appData.testLegality)
6915     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6916     case MT_NONE:
6917     case MT_CHECK:
6918       break;
6919     case MT_CHECKMATE:
6920     case MT_STAINMATE:
6921       if (WhiteOnMove(currentMove)) {
6922         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6923       } else {
6924         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6925       }
6926       break;
6927     case MT_STALEMATE:
6928       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6929       break;
6930     }
6931     break;
6932
6933   case MachinePlaysBlack:
6934   case MachinePlaysWhite:
6935     /* disable certain menu options while machine is thinking */
6936     SetMachineThinkingEnables();
6937     break;
6938
6939   default:
6940     break;
6941   }
6942
6943   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6944   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6945
6946   if(bookHit) { // [HGM] book: simulate book reply
6947         static char bookMove[MSG_SIZ]; // a bit generous?
6948
6949         programStats.nodes = programStats.depth = programStats.time =
6950         programStats.score = programStats.got_only_move = 0;
6951         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6952
6953         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6954         strcat(bookMove, bookHit);
6955         HandleMachineMove(bookMove, &first);
6956   }
6957   return 1;
6958 }
6959
6960 void
6961 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
6962 {
6963     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6964     Markers *m = (Markers *) closure;
6965     if(rf == fromY && ff == fromX)
6966         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6967                          || kind == WhiteCapturesEnPassant
6968                          || kind == BlackCapturesEnPassant);
6969     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6970 }
6971
6972 void
6973 MarkTargetSquares (int clear)
6974 {
6975   int x, y;
6976   if(clear) // no reason to ever suppress clearing
6977     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6978   if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
6979      !appData.testLegality || gameMode == EditPosition) return;
6980   if(!clear) {
6981     int capt = 0;
6982     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
6983     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6984       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6985       if(capt)
6986       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6987     }
6988   }
6989   DrawPosition(FALSE, NULL);
6990 }
6991
6992 int
6993 Explode (Board board, int fromX, int fromY, int toX, int toY)
6994 {
6995     if(gameInfo.variant == VariantAtomic &&
6996        (board[toY][toX] != EmptySquare ||                     // capture?
6997         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6998                          board[fromY][fromX] == BlackPawn   )
6999       )) {
7000         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7001         return TRUE;
7002     }
7003     return FALSE;
7004 }
7005
7006 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7007
7008 int
7009 CanPromote (ChessSquare piece, int y)
7010 {
7011         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7012         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7013         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
7014            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7015            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7016                                                   gameInfo.variant == VariantMakruk) return FALSE;
7017         return (piece == BlackPawn && y == 1 ||
7018                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
7019                 piece == BlackLance && y == 1 ||
7020                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
7021 }
7022
7023 void
7024 LeftClick (ClickType clickType, int xPix, int yPix)
7025 {
7026     int x, y;
7027     Boolean saveAnimate;
7028     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7029     char promoChoice = NULLCHAR;
7030     ChessSquare piece;
7031     static TimeMark lastClickTime, prevClickTime;
7032
7033     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7034
7035     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7036
7037     if (clickType == Press) ErrorPopDown();
7038
7039     x = EventToSquare(xPix, BOARD_WIDTH);
7040     y = EventToSquare(yPix, BOARD_HEIGHT);
7041     if (!flipView && y >= 0) {
7042         y = BOARD_HEIGHT - 1 - y;
7043     }
7044     if (flipView && x >= 0) {
7045         x = BOARD_WIDTH - 1 - x;
7046     }
7047
7048     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7049         defaultPromoChoice = promoSweep;
7050         promoSweep = EmptySquare;   // terminate sweep
7051         promoDefaultAltered = TRUE;
7052         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7053     }
7054
7055     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7056         if(clickType == Release) return; // ignore upclick of click-click destination
7057         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7058         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7059         if(gameInfo.holdingsWidth &&
7060                 (WhiteOnMove(currentMove)
7061                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7062                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7063             // click in right holdings, for determining promotion piece
7064             ChessSquare p = boards[currentMove][y][x];
7065             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7066             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7067             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7068                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7069                 fromX = fromY = -1;
7070                 return;
7071             }
7072         }
7073         DrawPosition(FALSE, boards[currentMove]);
7074         return;
7075     }
7076
7077     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7078     if(clickType == Press
7079             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7080               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7081               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7082         return;
7083
7084     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7085         // could be static click on premove from-square: abort premove
7086         gotPremove = 0;
7087         ClearPremoveHighlights();
7088     }
7089
7090     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7091         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7092
7093     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7094         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7095                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7096         defaultPromoChoice = DefaultPromoChoice(side);
7097     }
7098
7099     autoQueen = appData.alwaysPromoteToQueen;
7100
7101     if (fromX == -1) {
7102       int originalY = y;
7103       gatingPiece = EmptySquare;
7104       if (clickType != Press) {
7105         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7106             DragPieceEnd(xPix, yPix); dragging = 0;
7107             DrawPosition(FALSE, NULL);
7108         }
7109         return;
7110       }
7111       doubleClick = FALSE;
7112       if(gameMode == AnalyzeMode && pausing && first.excludeMoves) { // use pause state to exclude moves
7113         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7114       }
7115       fromX = x; fromY = y; toX = toY = -1;
7116       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7117          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7118          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7119             /* First square */
7120             if (OKToStartUserMove(fromX, fromY)) {
7121                 second = 0;
7122                 MarkTargetSquares(0);
7123                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7124                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7125                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7126                     promoSweep = defaultPromoChoice;
7127                     selectFlag = 0; lastX = xPix; lastY = yPix;
7128                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7129                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7130                 }
7131                 if (appData.highlightDragging) {
7132                     SetHighlights(fromX, fromY, -1, -1);
7133                 } else {
7134                     ClearHighlights();
7135                 }
7136             } else fromX = fromY = -1;
7137             return;
7138         }
7139     }
7140
7141     /* fromX != -1 */
7142     if (clickType == Press && gameMode != EditPosition) {
7143         ChessSquare fromP;
7144         ChessSquare toP;
7145         int frc;
7146
7147         // ignore off-board to clicks
7148         if(y < 0 || x < 0) return;
7149
7150         /* Check if clicking again on the same color piece */
7151         fromP = boards[currentMove][fromY][fromX];
7152         toP = boards[currentMove][y][x];
7153         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
7154         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
7155              WhitePawn <= toP && toP <= WhiteKing &&
7156              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7157              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7158             (BlackPawn <= fromP && fromP <= BlackKing &&
7159              BlackPawn <= toP && toP <= BlackKing &&
7160              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7161              !(fromP == BlackKing && toP == BlackRook && frc))) {
7162             /* Clicked again on same color piece -- changed his mind */
7163             second = (x == fromX && y == fromY);
7164             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7165                 second = FALSE; // first double-click rather than scond click
7166                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7167             }
7168             promoDefaultAltered = FALSE;
7169             MarkTargetSquares(1);
7170            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
7171             if (appData.highlightDragging) {
7172                 SetHighlights(x, y, -1, -1);
7173             } else {
7174                 ClearHighlights();
7175             }
7176             if (OKToStartUserMove(x, y)) {
7177                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7178                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7179                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7180                  gatingPiece = boards[currentMove][fromY][fromX];
7181                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7182                 fromX = x;
7183                 fromY = y; dragging = 1;
7184                 MarkTargetSquares(0);
7185                 DragPieceBegin(xPix, yPix, FALSE);
7186                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7187                     promoSweep = defaultPromoChoice;
7188                     selectFlag = 0; lastX = xPix; lastY = yPix;
7189                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7190                 }
7191             }
7192            }
7193            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7194            second = FALSE; 
7195         }
7196         // ignore clicks on holdings
7197         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7198     }
7199
7200     if (clickType == Release && x == fromX && y == fromY) {
7201         DragPieceEnd(xPix, yPix); dragging = 0;
7202         if(clearFlag) {
7203             // a deferred attempt to click-click move an empty square on top of a piece
7204             boards[currentMove][y][x] = EmptySquare;
7205             ClearHighlights();
7206             DrawPosition(FALSE, boards[currentMove]);
7207             fromX = fromY = -1; clearFlag = 0;
7208             return;
7209         }
7210         if (appData.animateDragging) {
7211             /* Undo animation damage if any */
7212             DrawPosition(FALSE, NULL);
7213         }
7214         if (second || sweepSelecting) {
7215             /* Second up/down in same square; just abort move */
7216             if(sweepSelecting) DrawPosition(FALSE, boards[currentMove]);
7217             second = sweepSelecting = 0;
7218             fromX = fromY = -1;
7219             gatingPiece = EmptySquare;
7220             ClearHighlights();
7221             gotPremove = 0;
7222             ClearPremoveHighlights();
7223         } else {
7224             /* First upclick in same square; start click-click mode */
7225             SetHighlights(x, y, -1, -1);
7226         }
7227         return;
7228     }
7229
7230     clearFlag = 0;
7231
7232     /* we now have a different from- and (possibly off-board) to-square */
7233     /* Completed move */
7234     if(!sweepSelecting) {
7235         toX = x;
7236         toY = y;
7237     } else sweepSelecting = 0; // this must be the up-click corresponding to the down-click that started the sweep
7238
7239     saveAnimate = appData.animate;
7240     if (clickType == Press) {
7241         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7242             // must be Edit Position mode with empty-square selected
7243             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7244             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7245             return;
7246         }
7247         if(HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7248           if(appData.sweepSelect) {
7249             ChessSquare piece = boards[currentMove][fromY][fromX];
7250             promoSweep = defaultPromoChoice;
7251             if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
7252             selectFlag = 0; lastX = xPix; lastY = yPix;
7253             Sweep(0); // Pawn that is going to promote: preview promotion piece
7254             sweepSelecting = 1;
7255             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7256             MarkTargetSquares(1);
7257           }
7258           return; // promo popup appears on up-click
7259         }
7260         /* Finish clickclick move */
7261         if (appData.animate || appData.highlightLastMove) {
7262             SetHighlights(fromX, fromY, toX, toY);
7263         } else {
7264             ClearHighlights();
7265         }
7266     } else {
7267 #if 0
7268 // [HGM] this must be done after the move is made, as with arrow it could lead to a board redraw with piece still on from square
7269         /* Finish drag move */
7270         if (appData.highlightLastMove) {
7271             SetHighlights(fromX, fromY, toX, toY);
7272         } else {
7273             ClearHighlights();
7274         }
7275 #endif
7276         DragPieceEnd(xPix, yPix); dragging = 0;
7277         /* Don't animate move and drag both */
7278         appData.animate = FALSE;
7279     }
7280
7281     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7282     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7283         ChessSquare piece = boards[currentMove][fromY][fromX];
7284         if(gameMode == EditPosition && piece != EmptySquare &&
7285            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7286             int n;
7287
7288             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7289                 n = PieceToNumber(piece - (int)BlackPawn);
7290                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7291                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7292                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7293             } else
7294             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7295                 n = PieceToNumber(piece);
7296                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7297                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7298                 boards[currentMove][n][BOARD_WIDTH-2]++;
7299             }
7300             boards[currentMove][fromY][fromX] = EmptySquare;
7301         }
7302         ClearHighlights();
7303         fromX = fromY = -1;
7304         MarkTargetSquares(1);
7305         DrawPosition(TRUE, boards[currentMove]);
7306         return;
7307     }
7308
7309     // off-board moves should not be highlighted
7310     if(x < 0 || y < 0) ClearHighlights();
7311
7312     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7313
7314     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7315         SetHighlights(fromX, fromY, toX, toY);
7316         MarkTargetSquares(1);
7317         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7318             // [HGM] super: promotion to captured piece selected from holdings
7319             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7320             promotionChoice = TRUE;
7321             // kludge follows to temporarily execute move on display, without promoting yet
7322             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7323             boards[currentMove][toY][toX] = p;
7324             DrawPosition(FALSE, boards[currentMove]);
7325             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7326             boards[currentMove][toY][toX] = q;
7327             DisplayMessage("Click in holdings to choose piece", "");
7328             return;
7329         }
7330         PromotionPopUp();
7331     } else {
7332         int oldMove = currentMove;
7333         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7334         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7335         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7336         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7337            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7338             DrawPosition(TRUE, boards[currentMove]);
7339         MarkTargetSquares(1);
7340         fromX = fromY = -1;
7341     }
7342     appData.animate = saveAnimate;
7343     if (appData.animate || appData.animateDragging) {
7344         /* Undo animation damage if needed */
7345         DrawPosition(FALSE, NULL);
7346     }
7347 }
7348
7349 int
7350 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7351 {   // front-end-free part taken out of PieceMenuPopup
7352     int whichMenu; int xSqr, ySqr;
7353
7354     if(seekGraphUp) { // [HGM] seekgraph
7355         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7356         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7357         return -2;
7358     }
7359
7360     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7361          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7362         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7363         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7364         if(action == Press)   {
7365             originalFlip = flipView;
7366             flipView = !flipView; // temporarily flip board to see game from partners perspective
7367             DrawPosition(TRUE, partnerBoard);
7368             DisplayMessage(partnerStatus, "");
7369             partnerUp = TRUE;
7370         } else if(action == Release) {
7371             flipView = originalFlip;
7372             DrawPosition(TRUE, boards[currentMove]);
7373             partnerUp = FALSE;
7374         }
7375         return -2;
7376     }
7377
7378     xSqr = EventToSquare(x, BOARD_WIDTH);
7379     ySqr = EventToSquare(y, BOARD_HEIGHT);
7380     if (action == Release) {
7381         if(pieceSweep != EmptySquare) {
7382             EditPositionMenuEvent(pieceSweep, toX, toY);
7383             pieceSweep = EmptySquare;
7384         } else UnLoadPV(); // [HGM] pv
7385     }
7386     if (action != Press) return -2; // return code to be ignored
7387     switch (gameMode) {
7388       case IcsExamining:
7389         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7390       case EditPosition:
7391         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7392         if (xSqr < 0 || ySqr < 0) return -1;
7393         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7394         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7395         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7396         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7397         NextPiece(0);
7398         return 2; // grab
7399       case IcsObserving:
7400         if(!appData.icsEngineAnalyze) return -1;
7401       case IcsPlayingWhite:
7402       case IcsPlayingBlack:
7403         if(!appData.zippyPlay) goto noZip;
7404       case AnalyzeMode:
7405       case AnalyzeFile:
7406       case MachinePlaysWhite:
7407       case MachinePlaysBlack:
7408       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7409         if (!appData.dropMenu) {
7410           LoadPV(x, y);
7411           return 2; // flag front-end to grab mouse events
7412         }
7413         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7414            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7415       case EditGame:
7416       noZip:
7417         if (xSqr < 0 || ySqr < 0) return -1;
7418         if (!appData.dropMenu || appData.testLegality &&
7419             gameInfo.variant != VariantBughouse &&
7420             gameInfo.variant != VariantCrazyhouse) return -1;
7421         whichMenu = 1; // drop menu
7422         break;
7423       default:
7424         return -1;
7425     }
7426
7427     if (((*fromX = xSqr) < 0) ||
7428         ((*fromY = ySqr) < 0)) {
7429         *fromX = *fromY = -1;
7430         return -1;
7431     }
7432     if (flipView)
7433       *fromX = BOARD_WIDTH - 1 - *fromX;
7434     else
7435       *fromY = BOARD_HEIGHT - 1 - *fromY;
7436
7437     return whichMenu;
7438 }
7439
7440 void
7441 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7442 {
7443 //    char * hint = lastHint;
7444     FrontEndProgramStats stats;
7445
7446     stats.which = cps == &first ? 0 : 1;
7447     stats.depth = cpstats->depth;
7448     stats.nodes = cpstats->nodes;
7449     stats.score = cpstats->score;
7450     stats.time = cpstats->time;
7451     stats.pv = cpstats->movelist;
7452     stats.hint = lastHint;
7453     stats.an_move_index = 0;
7454     stats.an_move_count = 0;
7455
7456     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7457         stats.hint = cpstats->move_name;
7458         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7459         stats.an_move_count = cpstats->nr_moves;
7460     }
7461
7462     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
7463
7464     SetProgramStats( &stats );
7465 }
7466
7467 void
7468 ClearEngineOutputPane (int which)
7469 {
7470     static FrontEndProgramStats dummyStats;
7471     dummyStats.which = which;
7472     dummyStats.pv = "#";
7473     SetProgramStats( &dummyStats );
7474 }
7475
7476 #define MAXPLAYERS 500
7477
7478 char *
7479 TourneyStandings (int display)
7480 {
7481     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7482     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7483     char result, *p, *names[MAXPLAYERS];
7484
7485     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7486         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7487     names[0] = p = strdup(appData.participants);
7488     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7489
7490     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7491
7492     while(result = appData.results[nr]) {
7493         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7494         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7495         wScore = bScore = 0;
7496         switch(result) {
7497           case '+': wScore = 2; break;
7498           case '-': bScore = 2; break;
7499           case '=': wScore = bScore = 1; break;
7500           case ' ':
7501           case '*': return strdup("busy"); // tourney not finished
7502         }
7503         score[w] += wScore;
7504         score[b] += bScore;
7505         games[w]++;
7506         games[b]++;
7507         nr++;
7508     }
7509     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7510     for(w=0; w<nPlayers; w++) {
7511         bScore = -1;
7512         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7513         ranking[w] = b; points[w] = bScore; score[b] = -2;
7514     }
7515     p = malloc(nPlayers*34+1);
7516     for(w=0; w<nPlayers && w<display; w++)
7517         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7518     free(names[0]);
7519     return p;
7520 }
7521
7522 void
7523 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7524 {       // count all piece types
7525         int p, f, r;
7526         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7527         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7528         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7529                 p = board[r][f];
7530                 pCnt[p]++;
7531                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7532                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7533                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7534                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7535                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7536                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7537         }
7538 }
7539
7540 int
7541 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7542 {
7543         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7544         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7545
7546         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7547         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7548         if(myPawns == 2 && nMine == 3) // KPP
7549             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7550         if(myPawns == 1 && nMine == 2) // KP
7551             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7552         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7553             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7554         if(myPawns) return FALSE;
7555         if(pCnt[WhiteRook+side])
7556             return pCnt[BlackRook-side] ||
7557                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7558                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7559                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7560         if(pCnt[WhiteCannon+side]) {
7561             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7562             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7563         }
7564         if(pCnt[WhiteKnight+side])
7565             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7566         return FALSE;
7567 }
7568
7569 int
7570 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7571 {
7572         VariantClass v = gameInfo.variant;
7573
7574         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7575         if(v == VariantShatranj) return TRUE; // always winnable through baring
7576         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7577         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7578
7579         if(v == VariantXiangqi) {
7580                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7581
7582                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7583                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7584                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7585                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7586                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7587                 if(stale) // we have at least one last-rank P plus perhaps C
7588                     return majors // KPKX
7589                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7590                 else // KCA*E*
7591                     return pCnt[WhiteFerz+side] // KCAK
7592                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7593                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7594                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7595
7596         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7597                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7598
7599                 if(nMine == 1) return FALSE; // bare King
7600                 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
7601                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7602                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7603                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7604                 if(pCnt[WhiteKnight+side])
7605                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7606                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7607                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7608                 if(nBishops)
7609                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7610                 if(pCnt[WhiteAlfil+side])
7611                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7612                 if(pCnt[WhiteWazir+side])
7613                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7614         }
7615
7616         return TRUE;
7617 }
7618
7619 int
7620 CompareWithRights (Board b1, Board b2)
7621 {
7622     int rights = 0;
7623     if(!CompareBoards(b1, b2)) return FALSE;
7624     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7625     /* compare castling rights */
7626     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7627            rights++; /* King lost rights, while rook still had them */
7628     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7629         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7630            rights++; /* but at least one rook lost them */
7631     }
7632     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7633            rights++;
7634     if( b1[CASTLING][5] != NoRights ) {
7635         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7636            rights++;
7637     }
7638     return rights == 0;
7639 }
7640
7641 int
7642 Adjudicate (ChessProgramState *cps)
7643 {       // [HGM] some adjudications useful with buggy engines
7644         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7645         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7646         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7647         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7648         int k, count = 0; static int bare = 1;
7649         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7650         Boolean canAdjudicate = !appData.icsActive;
7651
7652         // most tests only when we understand the game, i.e. legality-checking on
7653             if( appData.testLegality )
7654             {   /* [HGM] Some more adjudications for obstinate engines */
7655                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7656                 static int moveCount = 6;
7657                 ChessMove result;
7658                 char *reason = NULL;
7659
7660                 /* Count what is on board. */
7661                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7662
7663                 /* Some material-based adjudications that have to be made before stalemate test */
7664                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7665                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7666                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7667                      if(canAdjudicate && appData.checkMates) {
7668                          if(engineOpponent)
7669                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7670                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7671                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7672                          return 1;
7673                      }
7674                 }
7675
7676                 /* Bare King in Shatranj (loses) or Losers (wins) */
7677                 if( nrW == 1 || nrB == 1) {
7678                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7679                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7680                      if(canAdjudicate && appData.checkMates) {
7681                          if(engineOpponent)
7682                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7683                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7684                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7685                          return 1;
7686                      }
7687                   } else
7688                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7689                   {    /* bare King */
7690                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7691                         if(canAdjudicate && appData.checkMates) {
7692                             /* but only adjudicate if adjudication enabled */
7693                             if(engineOpponent)
7694                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7695                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7696                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7697                             return 1;
7698                         }
7699                   }
7700                 } else bare = 1;
7701
7702
7703             // don't wait for engine to announce game end if we can judge ourselves
7704             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7705               case MT_CHECK:
7706                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7707                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7708                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7709                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7710                             checkCnt++;
7711                         if(checkCnt >= 2) {
7712                             reason = "Xboard adjudication: 3rd check";
7713                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7714                             break;
7715                         }
7716                     }
7717                 }
7718               case MT_NONE:
7719               default:
7720                 break;
7721               case MT_STALEMATE:
7722               case MT_STAINMATE:
7723                 reason = "Xboard adjudication: Stalemate";
7724                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7725                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7726                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7727                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7728                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7729                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7730                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7731                                                                         EP_CHECKMATE : EP_WINS);
7732                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7733                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7734                 }
7735                 break;
7736               case MT_CHECKMATE:
7737                 reason = "Xboard adjudication: Checkmate";
7738                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7739                 break;
7740             }
7741
7742                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7743                     case EP_STALEMATE:
7744                         result = GameIsDrawn; break;
7745                     case EP_CHECKMATE:
7746                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7747                     case EP_WINS:
7748                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7749                     default:
7750                         result = EndOfFile;
7751                 }
7752                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7753                     if(engineOpponent)
7754                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7755                     GameEnds( result, reason, GE_XBOARD );
7756                     return 1;
7757                 }
7758
7759                 /* Next absolutely insufficient mating material. */
7760                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7761                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7762                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7763
7764                      /* always flag draws, for judging claims */
7765                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7766
7767                      if(canAdjudicate && appData.materialDraws) {
7768                          /* but only adjudicate them if adjudication enabled */
7769                          if(engineOpponent) {
7770                            SendToProgram("force\n", engineOpponent); // suppress reply
7771                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7772                          }
7773                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7774                          return 1;
7775                      }
7776                 }
7777
7778                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7779                 if(gameInfo.variant == VariantXiangqi ?
7780                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7781                  : nrW + nrB == 4 &&
7782                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7783                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7784                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7785                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7786                    ) ) {
7787                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7788                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7789                           if(engineOpponent) {
7790                             SendToProgram("force\n", engineOpponent); // suppress reply
7791                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7792                           }
7793                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7794                           return 1;
7795                      }
7796                 } else moveCount = 6;
7797             }
7798
7799         // Repetition draws and 50-move rule can be applied independently of legality testing
7800
7801                 /* Check for rep-draws */
7802                 count = 0;
7803                 for(k = forwardMostMove-2;
7804                     k>=backwardMostMove && k>=forwardMostMove-100 &&
7805                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7806                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7807                     k-=2)
7808                 {   int rights=0;
7809                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7810                         /* compare castling rights */
7811                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7812                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7813                                 rights++; /* King lost rights, while rook still had them */
7814                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7815                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7816                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7817                                    rights++; /* but at least one rook lost them */
7818                         }
7819                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7820                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7821                                 rights++;
7822                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7823                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7824                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7825                                    rights++;
7826                         }
7827                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7828                             && appData.drawRepeats > 1) {
7829                              /* adjudicate after user-specified nr of repeats */
7830                              int result = GameIsDrawn;
7831                              char *details = "XBoard adjudication: repetition draw";
7832                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7833                                 // [HGM] xiangqi: check for forbidden perpetuals
7834                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7835                                 for(m=forwardMostMove; m>k; m-=2) {
7836                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7837                                         ourPerpetual = 0; // the current mover did not always check
7838                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7839                                         hisPerpetual = 0; // the opponent did not always check
7840                                 }
7841                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7842                                                                         ourPerpetual, hisPerpetual);
7843                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7844                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7845                                     details = "Xboard adjudication: perpetual checking";
7846                                 } else
7847                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7848                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7849                                 } else
7850                                 // Now check for perpetual chases
7851                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7852                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7853                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7854                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7855                                         static char resdet[MSG_SIZ];
7856                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7857                                         details = resdet;
7858                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
7859                                     } else
7860                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7861                                         break; // Abort repetition-checking loop.
7862                                 }
7863                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7864                              }
7865                              if(engineOpponent) {
7866                                SendToProgram("force\n", engineOpponent); // suppress reply
7867                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7868                              }
7869                              GameEnds( result, details, GE_XBOARD );
7870                              return 1;
7871                         }
7872                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7873                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7874                     }
7875                 }
7876
7877                 /* Now we test for 50-move draws. Determine ply count */
7878                 count = forwardMostMove;
7879                 /* look for last irreversble move */
7880                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7881                     count--;
7882                 /* if we hit starting position, add initial plies */
7883                 if( count == backwardMostMove )
7884                     count -= initialRulePlies;
7885                 count = forwardMostMove - count;
7886                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7887                         // adjust reversible move counter for checks in Xiangqi
7888                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7889                         if(i < backwardMostMove) i = backwardMostMove;
7890                         while(i <= forwardMostMove) {
7891                                 lastCheck = inCheck; // check evasion does not count
7892                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7893                                 if(inCheck || lastCheck) count--; // check does not count
7894                                 i++;
7895                         }
7896                 }
7897                 if( count >= 100)
7898                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7899                          /* this is used to judge if draw claims are legal */
7900                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7901                          if(engineOpponent) {
7902                            SendToProgram("force\n", engineOpponent); // suppress reply
7903                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7904                          }
7905                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7906                          return 1;
7907                 }
7908
7909                 /* if draw offer is pending, treat it as a draw claim
7910                  * when draw condition present, to allow engines a way to
7911                  * claim draws before making their move to avoid a race
7912                  * condition occurring after their move
7913                  */
7914                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7915                          char *p = NULL;
7916                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7917                              p = "Draw claim: 50-move rule";
7918                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7919                              p = "Draw claim: 3-fold repetition";
7920                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7921                              p = "Draw claim: insufficient mating material";
7922                          if( p != NULL && canAdjudicate) {
7923                              if(engineOpponent) {
7924                                SendToProgram("force\n", engineOpponent); // suppress reply
7925                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7926                              }
7927                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7928                              return 1;
7929                          }
7930                 }
7931
7932                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7933                     if(engineOpponent) {
7934                       SendToProgram("force\n", engineOpponent); // suppress reply
7935                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7936                     }
7937                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7938                     return 1;
7939                 }
7940         return 0;
7941 }
7942
7943 char *
7944 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
7945 {   // [HGM] book: this routine intercepts moves to simulate book replies
7946     char *bookHit = NULL;
7947
7948     //first determine if the incoming move brings opponent into his book
7949     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7950         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7951     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7952     if(bookHit != NULL && !cps->bookSuspend) {
7953         // make sure opponent is not going to reply after receiving move to book position
7954         SendToProgram("force\n", cps);
7955         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7956     }
7957     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7958     // now arrange restart after book miss
7959     if(bookHit) {
7960         // after a book hit we never send 'go', and the code after the call to this routine
7961         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7962         char buf[MSG_SIZ], *move = bookHit;
7963         if(cps->useSAN) {
7964             int fromX, fromY, toX, toY;
7965             char promoChar;
7966             ChessMove moveType;
7967             move = buf + 30;
7968             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
7969                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7970                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7971                                     PosFlags(forwardMostMove),
7972                                     fromY, fromX, toY, toX, promoChar, move);
7973             } else {
7974                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
7975                 bookHit = NULL;
7976             }
7977         }
7978         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
7979         SendToProgram(buf, cps);
7980         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7981     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7982         SendToProgram("go\n", cps);
7983         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7984     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7985         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7986             SendToProgram("go\n", cps);
7987         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7988     }
7989     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7990 }
7991
7992 int
7993 LoadError (char *errmess, ChessProgramState *cps)
7994 {   // unloads engine and switches back to -ncp mode if it was first
7995     if(cps->initDone) return FALSE;
7996     cps->isr = NULL; // this should suppress further error popups from breaking pipes
7997     DestroyChildProcess(cps->pr, 9 ); // just to be sure
7998     cps->pr = NoProc; 
7999     if(cps == &first) {
8000         appData.noChessProgram = TRUE;
8001         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8002         gameMode = BeginningOfGame; ModeHighlight();
8003         SetNCPMode();
8004     }
8005     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8006     DisplayMessage("", ""); // erase waiting message
8007     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8008     return TRUE;
8009 }
8010
8011 char *savedMessage;
8012 ChessProgramState *savedState;
8013 void
8014 DeferredBookMove (void)
8015 {
8016         if(savedState->lastPing != savedState->lastPong)
8017                     ScheduleDelayedEvent(DeferredBookMove, 10);
8018         else
8019         HandleMachineMove(savedMessage, savedState);
8020 }
8021
8022 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8023
8024 void
8025 HandleMachineMove (char *message, ChessProgramState *cps)
8026 {
8027     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8028     char realname[MSG_SIZ];
8029     int fromX, fromY, toX, toY;
8030     ChessMove moveType;
8031     char promoChar;
8032     char *p, *pv=buf1;
8033     int machineWhite, oldError;
8034     char *bookHit;
8035
8036     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8037         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8038         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8039             DisplayError(_("Invalid pairing from pairing engine"), 0);
8040             return;
8041         }
8042         pairingReceived = 1;
8043         NextMatchGame();
8044         return; // Skim the pairing messages here.
8045     }
8046
8047     oldError = cps->userError; cps->userError = 0;
8048
8049 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8050     /*
8051      * Kludge to ignore BEL characters
8052      */
8053     while (*message == '\007') message++;
8054
8055     /*
8056      * [HGM] engine debug message: ignore lines starting with '#' character
8057      */
8058     if(cps->debug && *message == '#') return;
8059
8060     /*
8061      * Look for book output
8062      */
8063     if (cps == &first && bookRequested) {
8064         if (message[0] == '\t' || message[0] == ' ') {
8065             /* Part of the book output is here; append it */
8066             strcat(bookOutput, message);
8067             strcat(bookOutput, "  \n");
8068             return;
8069         } else if (bookOutput[0] != NULLCHAR) {
8070             /* All of book output has arrived; display it */
8071             char *p = bookOutput;
8072             while (*p != NULLCHAR) {
8073                 if (*p == '\t') *p = ' ';
8074                 p++;
8075             }
8076             DisplayInformation(bookOutput);
8077             bookRequested = FALSE;
8078             /* Fall through to parse the current output */
8079         }
8080     }
8081
8082     /*
8083      * Look for machine move.
8084      */
8085     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8086         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8087     {
8088         /* This method is only useful on engines that support ping */
8089         if (cps->lastPing != cps->lastPong) {
8090           if (gameMode == BeginningOfGame) {
8091             /* Extra move from before last new; ignore */
8092             if (appData.debugMode) {
8093                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8094             }
8095           } else {
8096             if (appData.debugMode) {
8097                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8098                         cps->which, gameMode);
8099             }
8100
8101             SendToProgram("undo\n", cps);
8102           }
8103           return;
8104         }
8105
8106         switch (gameMode) {
8107           case BeginningOfGame:
8108             /* Extra move from before last reset; ignore */
8109             if (appData.debugMode) {
8110                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8111             }
8112             return;
8113
8114           case EndOfGame:
8115           case IcsIdle:
8116           default:
8117             /* Extra move after we tried to stop.  The mode test is
8118                not a reliable way of detecting this problem, but it's
8119                the best we can do on engines that don't support ping.
8120             */
8121             if (appData.debugMode) {
8122                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8123                         cps->which, gameMode);
8124             }
8125             SendToProgram("undo\n", cps);
8126             return;
8127
8128           case MachinePlaysWhite:
8129           case IcsPlayingWhite:
8130             machineWhite = TRUE;
8131             break;
8132
8133           case MachinePlaysBlack:
8134           case IcsPlayingBlack:
8135             machineWhite = FALSE;
8136             break;
8137
8138           case TwoMachinesPlay:
8139             machineWhite = (cps->twoMachinesColor[0] == 'w');
8140             break;
8141         }
8142         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8143             if (appData.debugMode) {
8144                 fprintf(debugFP,
8145                         "Ignoring move out of turn by %s, gameMode %d"
8146                         ", forwardMost %d\n",
8147                         cps->which, gameMode, forwardMostMove);
8148             }
8149             return;
8150         }
8151
8152         if(cps->alphaRank) AlphaRank(machineMove, 4);
8153         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8154                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8155             /* Machine move could not be parsed; ignore it. */
8156           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8157                     machineMove, _(cps->which));
8158             DisplayError(buf1, 0);
8159             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
8160                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
8161             if (gameMode == TwoMachinesPlay) {
8162               GameEnds(machineWhite ? BlackWins : WhiteWins,
8163                        buf1, GE_XBOARD);
8164             }
8165             return;
8166         }
8167
8168         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8169         /* So we have to redo legality test with true e.p. status here,  */
8170         /* to make sure an illegal e.p. capture does not slip through,   */
8171         /* to cause a forfeit on a justified illegal-move complaint      */
8172         /* of the opponent.                                              */
8173         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8174            ChessMove moveType;
8175            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8176                              fromY, fromX, toY, toX, promoChar);
8177             if(moveType == IllegalMove) {
8178               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8179                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8180                 GameEnds(machineWhite ? BlackWins : WhiteWins,
8181                            buf1, GE_XBOARD);
8182                 return;
8183            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
8184            /* [HGM] Kludge to handle engines that send FRC-style castling
8185               when they shouldn't (like TSCP-Gothic) */
8186            switch(moveType) {
8187              case WhiteASideCastleFR:
8188              case BlackASideCastleFR:
8189                toX+=2;
8190                currentMoveString[2]++;
8191                break;
8192              case WhiteHSideCastleFR:
8193              case BlackHSideCastleFR:
8194                toX--;
8195                currentMoveString[2]--;
8196                break;
8197              default: ; // nothing to do, but suppresses warning of pedantic compilers
8198            }
8199         }
8200         hintRequested = FALSE;
8201         lastHint[0] = NULLCHAR;
8202         bookRequested = FALSE;
8203         /* Program may be pondering now */
8204         cps->maybeThinking = TRUE;
8205         if (cps->sendTime == 2) cps->sendTime = 1;
8206         if (cps->offeredDraw) cps->offeredDraw--;
8207
8208         /* [AS] Save move info*/
8209         pvInfoList[ forwardMostMove ].score = programStats.score;
8210         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8211         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8212
8213         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8214
8215         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8216         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8217             int count = 0;
8218
8219             while( count < adjudicateLossPlies ) {
8220                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8221
8222                 if( count & 1 ) {
8223                     score = -score; /* Flip score for winning side */
8224                 }
8225
8226                 if( score > adjudicateLossThreshold ) {
8227                     break;
8228                 }
8229
8230                 count++;
8231             }
8232
8233             if( count >= adjudicateLossPlies ) {
8234                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8235
8236                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8237                     "Xboard adjudication",
8238                     GE_XBOARD );
8239
8240                 return;
8241             }
8242         }
8243
8244         if(Adjudicate(cps)) {
8245             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8246             return; // [HGM] adjudicate: for all automatic game ends
8247         }
8248
8249 #if ZIPPY
8250         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8251             first.initDone) {
8252           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8253                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8254                 SendToICS("draw ");
8255                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8256           }
8257           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8258           ics_user_moved = 1;
8259           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8260                 char buf[3*MSG_SIZ];
8261
8262                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8263                         programStats.score / 100.,
8264                         programStats.depth,
8265                         programStats.time / 100.,
8266                         (unsigned int)programStats.nodes,
8267                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8268                         programStats.movelist);
8269                 SendToICS(buf);
8270 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8271           }
8272         }
8273 #endif
8274
8275         /* [AS] Clear stats for next move */
8276         ClearProgramStats();
8277         thinkOutput[0] = NULLCHAR;
8278         hiddenThinkOutputState = 0;
8279
8280         bookHit = NULL;
8281         if (gameMode == TwoMachinesPlay) {
8282             /* [HGM] relaying draw offers moved to after reception of move */
8283             /* and interpreting offer as claim if it brings draw condition */
8284             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8285                 SendToProgram("draw\n", cps->other);
8286             }
8287             if (cps->other->sendTime) {
8288                 SendTimeRemaining(cps->other,
8289                                   cps->other->twoMachinesColor[0] == 'w');
8290             }
8291             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8292             if (firstMove && !bookHit) {
8293                 firstMove = FALSE;
8294                 if (cps->other->useColors) {
8295                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8296                 }
8297                 SendToProgram("go\n", cps->other);
8298             }
8299             cps->other->maybeThinking = TRUE;
8300         }
8301
8302         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8303
8304         if (!pausing && appData.ringBellAfterMoves) {
8305             RingBell();
8306         }
8307
8308         /*
8309          * Reenable menu items that were disabled while
8310          * machine was thinking
8311          */
8312         if (gameMode != TwoMachinesPlay)
8313             SetUserThinkingEnables();
8314
8315         // [HGM] book: after book hit opponent has received move and is now in force mode
8316         // force the book reply into it, and then fake that it outputted this move by jumping
8317         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8318         if(bookHit) {
8319                 static char bookMove[MSG_SIZ]; // a bit generous?
8320
8321                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8322                 strcat(bookMove, bookHit);
8323                 message = bookMove;
8324                 cps = cps->other;
8325                 programStats.nodes = programStats.depth = programStats.time =
8326                 programStats.score = programStats.got_only_move = 0;
8327                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8328
8329                 if(cps->lastPing != cps->lastPong) {
8330                     savedMessage = message; // args for deferred call
8331                     savedState = cps;
8332                     ScheduleDelayedEvent(DeferredBookMove, 10);
8333                     return;
8334                 }
8335                 goto FakeBookMove;
8336         }
8337
8338         return;
8339     }
8340
8341     /* Set special modes for chess engines.  Later something general
8342      *  could be added here; for now there is just one kludge feature,
8343      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8344      *  when "xboard" is given as an interactive command.
8345      */
8346     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8347         cps->useSigint = FALSE;
8348         cps->useSigterm = FALSE;
8349     }
8350     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8351       ParseFeatures(message+8, cps);
8352       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8353     }
8354
8355     if ((!appData.testLegality || gameInfo.variant == VariantFairy) && 
8356                                         !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
8357       int dummy, s=6; char buf[MSG_SIZ];
8358       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8359       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8360       if(startedFromSetupPosition) return;
8361       ParseFEN(boards[0], &dummy, message+s);
8362       DrawPosition(TRUE, boards[0]);
8363       startedFromSetupPosition = TRUE;
8364       return;
8365     }
8366     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8367      * want this, I was asked to put it in, and obliged.
8368      */
8369     if (!strncmp(message, "setboard ", 9)) {
8370         Board initial_position;
8371
8372         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8373
8374         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8375             DisplayError(_("Bad FEN received from engine"), 0);
8376             return ;
8377         } else {
8378            Reset(TRUE, FALSE);
8379            CopyBoard(boards[0], initial_position);
8380            initialRulePlies = FENrulePlies;
8381            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8382            else gameMode = MachinePlaysBlack;
8383            DrawPosition(FALSE, boards[currentMove]);
8384         }
8385         return;
8386     }
8387
8388     /*
8389      * Look for communication commands
8390      */
8391     if (!strncmp(message, "telluser ", 9)) {
8392         if(message[9] == '\\' && message[10] == '\\')
8393             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8394         PlayTellSound();
8395         DisplayNote(message + 9);
8396         return;
8397     }
8398     if (!strncmp(message, "tellusererror ", 14)) {
8399         cps->userError = 1;
8400         if(message[14] == '\\' && message[15] == '\\')
8401             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8402         PlayTellSound();
8403         DisplayError(message + 14, 0);
8404         return;
8405     }
8406     if (!strncmp(message, "tellopponent ", 13)) {
8407       if (appData.icsActive) {
8408         if (loggedOn) {
8409           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8410           SendToICS(buf1);
8411         }
8412       } else {
8413         DisplayNote(message + 13);
8414       }
8415       return;
8416     }
8417     if (!strncmp(message, "tellothers ", 11)) {
8418       if (appData.icsActive) {
8419         if (loggedOn) {
8420           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8421           SendToICS(buf1);
8422         }
8423       }
8424       return;
8425     }
8426     if (!strncmp(message, "tellall ", 8)) {
8427       if (appData.icsActive) {
8428         if (loggedOn) {
8429           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8430           SendToICS(buf1);
8431         }
8432       } else {
8433         DisplayNote(message + 8);
8434       }
8435       return;
8436     }
8437     if (strncmp(message, "warning", 7) == 0) {
8438         /* Undocumented feature, use tellusererror in new code */
8439         DisplayError(message, 0);
8440         return;
8441     }
8442     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8443         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8444         strcat(realname, " query");
8445         AskQuestion(realname, buf2, buf1, cps->pr);
8446         return;
8447     }
8448     /* Commands from the engine directly to ICS.  We don't allow these to be
8449      *  sent until we are logged on. Crafty kibitzes have been known to
8450      *  interfere with the login process.
8451      */
8452     if (loggedOn) {
8453         if (!strncmp(message, "tellics ", 8)) {
8454             SendToICS(message + 8);
8455             SendToICS("\n");
8456             return;
8457         }
8458         if (!strncmp(message, "tellicsnoalias ", 15)) {
8459             SendToICS(ics_prefix);
8460             SendToICS(message + 15);
8461             SendToICS("\n");
8462             return;
8463         }
8464         /* The following are for backward compatibility only */
8465         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8466             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8467             SendToICS(ics_prefix);
8468             SendToICS(message);
8469             SendToICS("\n");
8470             return;
8471         }
8472     }
8473     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8474         return;
8475     }
8476     /*
8477      * If the move is illegal, cancel it and redraw the board.
8478      * Also deal with other error cases.  Matching is rather loose
8479      * here to accommodate engines written before the spec.
8480      */
8481     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8482         strncmp(message, "Error", 5) == 0) {
8483         if (StrStr(message, "name") ||
8484             StrStr(message, "rating") || StrStr(message, "?") ||
8485             StrStr(message, "result") || StrStr(message, "board") ||
8486             StrStr(message, "bk") || StrStr(message, "computer") ||
8487             StrStr(message, "variant") || StrStr(message, "hint") ||
8488             StrStr(message, "random") || StrStr(message, "depth") ||
8489             StrStr(message, "accepted")) {
8490             return;
8491         }
8492         if (StrStr(message, "protover")) {
8493           /* Program is responding to input, so it's apparently done
8494              initializing, and this error message indicates it is
8495              protocol version 1.  So we don't need to wait any longer
8496              for it to initialize and send feature commands. */
8497           FeatureDone(cps, 1);
8498           cps->protocolVersion = 1;
8499           return;
8500         }
8501         cps->maybeThinking = FALSE;
8502
8503         if (StrStr(message, "draw")) {
8504             /* Program doesn't have "draw" command */
8505             cps->sendDrawOffers = 0;
8506             return;
8507         }
8508         if (cps->sendTime != 1 &&
8509             (StrStr(message, "time") || StrStr(message, "otim"))) {
8510           /* Program apparently doesn't have "time" or "otim" command */
8511           cps->sendTime = 0;
8512           return;
8513         }
8514         if (StrStr(message, "analyze")) {
8515             cps->analysisSupport = FALSE;
8516             cps->analyzing = FALSE;
8517 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8518             EditGameEvent(); // [HGM] try to preserve loaded game
8519             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8520             DisplayError(buf2, 0);
8521             return;
8522         }
8523         if (StrStr(message, "(no matching move)st")) {
8524           /* Special kludge for GNU Chess 4 only */
8525           cps->stKludge = TRUE;
8526           SendTimeControl(cps, movesPerSession, timeControl,
8527                           timeIncrement, appData.searchDepth,
8528                           searchTime);
8529           return;
8530         }
8531         if (StrStr(message, "(no matching move)sd")) {
8532           /* Special kludge for GNU Chess 4 only */
8533           cps->sdKludge = TRUE;
8534           SendTimeControl(cps, movesPerSession, timeControl,
8535                           timeIncrement, appData.searchDepth,
8536                           searchTime);
8537           return;
8538         }
8539         if (!StrStr(message, "llegal")) {
8540             return;
8541         }
8542         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8543             gameMode == IcsIdle) return;
8544         if (forwardMostMove <= backwardMostMove) return;
8545         if (pausing) PauseEvent();
8546       if(appData.forceIllegal) {
8547             // [HGM] illegal: machine refused move; force position after move into it
8548           SendToProgram("force\n", cps);
8549           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8550                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8551                 // when black is to move, while there might be nothing on a2 or black
8552                 // might already have the move. So send the board as if white has the move.
8553                 // But first we must change the stm of the engine, as it refused the last move
8554                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8555                 if(WhiteOnMove(forwardMostMove)) {
8556                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8557                     SendBoard(cps, forwardMostMove); // kludgeless board
8558                 } else {
8559                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8560                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8561                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8562                 }
8563           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8564             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8565                  gameMode == TwoMachinesPlay)
8566               SendToProgram("go\n", cps);
8567             return;
8568       } else
8569         if (gameMode == PlayFromGameFile) {
8570             /* Stop reading this game file */
8571             gameMode = EditGame;
8572             ModeHighlight();
8573         }
8574         /* [HGM] illegal-move claim should forfeit game when Xboard */
8575         /* only passes fully legal moves                            */
8576         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8577             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8578                                 "False illegal-move claim", GE_XBOARD );
8579             return; // do not take back move we tested as valid
8580         }
8581         currentMove = forwardMostMove-1;
8582         DisplayMove(currentMove-1); /* before DisplayMoveError */
8583         SwitchClocks(forwardMostMove-1); // [HGM] race
8584         DisplayBothClocks();
8585         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8586                 parseList[currentMove], _(cps->which));
8587         DisplayMoveError(buf1);
8588         DrawPosition(FALSE, boards[currentMove]);
8589
8590         SetUserThinkingEnables();
8591         return;
8592     }
8593     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8594         /* Program has a broken "time" command that
8595            outputs a string not ending in newline.
8596            Don't use it. */
8597         cps->sendTime = 0;
8598     }
8599
8600     /*
8601      * If chess program startup fails, exit with an error message.
8602      * Attempts to recover here are futile. [HGM] Well, we try anyway
8603      */
8604     if ((StrStr(message, "unknown host") != NULL)
8605         || (StrStr(message, "No remote directory") != NULL)
8606         || (StrStr(message, "not found") != NULL)
8607         || (StrStr(message, "No such file") != NULL)
8608         || (StrStr(message, "can't alloc") != NULL)
8609         || (StrStr(message, "Permission denied") != NULL)) {
8610
8611         cps->maybeThinking = FALSE;
8612         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8613                 _(cps->which), cps->program, cps->host, message);
8614         RemoveInputSource(cps->isr);
8615         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8616             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
8617             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
8618         }
8619         return;
8620     }
8621
8622     /*
8623      * Look for hint output
8624      */
8625     if (sscanf(message, "Hint: %s", buf1) == 1) {
8626         if (cps == &first && hintRequested) {
8627             hintRequested = FALSE;
8628             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8629                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8630                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8631                                     PosFlags(forwardMostMove),
8632                                     fromY, fromX, toY, toX, promoChar, buf1);
8633                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8634                 DisplayInformation(buf2);
8635             } else {
8636                 /* Hint move could not be parsed!? */
8637               snprintf(buf2, sizeof(buf2),
8638                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8639                         buf1, _(cps->which));
8640                 DisplayError(buf2, 0);
8641             }
8642         } else {
8643           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8644         }
8645         return;
8646     }
8647
8648     /*
8649      * Ignore other messages if game is not in progress
8650      */
8651     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8652         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8653
8654     /*
8655      * look for win, lose, draw, or draw offer
8656      */
8657     if (strncmp(message, "1-0", 3) == 0) {
8658         char *p, *q, *r = "";
8659         p = strchr(message, '{');
8660         if (p) {
8661             q = strchr(p, '}');
8662             if (q) {
8663                 *q = NULLCHAR;
8664                 r = p + 1;
8665             }
8666         }
8667         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8668         return;
8669     } else if (strncmp(message, "0-1", 3) == 0) {
8670         char *p, *q, *r = "";
8671         p = strchr(message, '{');
8672         if (p) {
8673             q = strchr(p, '}');
8674             if (q) {
8675                 *q = NULLCHAR;
8676                 r = p + 1;
8677             }
8678         }
8679         /* Kludge for Arasan 4.1 bug */
8680         if (strcmp(r, "Black resigns") == 0) {
8681             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8682             return;
8683         }
8684         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8685         return;
8686     } else if (strncmp(message, "1/2", 3) == 0) {
8687         char *p, *q, *r = "";
8688         p = strchr(message, '{');
8689         if (p) {
8690             q = strchr(p, '}');
8691             if (q) {
8692                 *q = NULLCHAR;
8693                 r = p + 1;
8694             }
8695         }
8696
8697         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8698         return;
8699
8700     } else if (strncmp(message, "White resign", 12) == 0) {
8701         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8702         return;
8703     } else if (strncmp(message, "Black resign", 12) == 0) {
8704         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8705         return;
8706     } else if (strncmp(message, "White matches", 13) == 0 ||
8707                strncmp(message, "Black matches", 13) == 0   ) {
8708         /* [HGM] ignore GNUShogi noises */
8709         return;
8710     } else if (strncmp(message, "White", 5) == 0 &&
8711                message[5] != '(' &&
8712                StrStr(message, "Black") == NULL) {
8713         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8714         return;
8715     } else if (strncmp(message, "Black", 5) == 0 &&
8716                message[5] != '(') {
8717         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8718         return;
8719     } else if (strcmp(message, "resign") == 0 ||
8720                strcmp(message, "computer resigns") == 0) {
8721         switch (gameMode) {
8722           case MachinePlaysBlack:
8723           case IcsPlayingBlack:
8724             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8725             break;
8726           case MachinePlaysWhite:
8727           case IcsPlayingWhite:
8728             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8729             break;
8730           case TwoMachinesPlay:
8731             if (cps->twoMachinesColor[0] == 'w')
8732               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8733             else
8734               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8735             break;
8736           default:
8737             /* can't happen */
8738             break;
8739         }
8740         return;
8741     } else if (strncmp(message, "opponent mates", 14) == 0) {
8742         switch (gameMode) {
8743           case MachinePlaysBlack:
8744           case IcsPlayingBlack:
8745             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8746             break;
8747           case MachinePlaysWhite:
8748           case IcsPlayingWhite:
8749             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8750             break;
8751           case TwoMachinesPlay:
8752             if (cps->twoMachinesColor[0] == 'w')
8753               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8754             else
8755               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8756             break;
8757           default:
8758             /* can't happen */
8759             break;
8760         }
8761         return;
8762     } else if (strncmp(message, "computer mates", 14) == 0) {
8763         switch (gameMode) {
8764           case MachinePlaysBlack:
8765           case IcsPlayingBlack:
8766             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8767             break;
8768           case MachinePlaysWhite:
8769           case IcsPlayingWhite:
8770             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8771             break;
8772           case TwoMachinesPlay:
8773             if (cps->twoMachinesColor[0] == 'w')
8774               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8775             else
8776               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8777             break;
8778           default:
8779             /* can't happen */
8780             break;
8781         }
8782         return;
8783     } else if (strncmp(message, "checkmate", 9) == 0) {
8784         if (WhiteOnMove(forwardMostMove)) {
8785             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8786         } else {
8787             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8788         }
8789         return;
8790     } else if (strstr(message, "Draw") != NULL ||
8791                strstr(message, "game is a draw") != NULL) {
8792         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8793         return;
8794     } else if (strstr(message, "offer") != NULL &&
8795                strstr(message, "draw") != NULL) {
8796 #if ZIPPY
8797         if (appData.zippyPlay && first.initDone) {
8798             /* Relay offer to ICS */
8799             SendToICS(ics_prefix);
8800             SendToICS("draw\n");
8801         }
8802 #endif
8803         cps->offeredDraw = 2; /* valid until this engine moves twice */
8804         if (gameMode == TwoMachinesPlay) {
8805             if (cps->other->offeredDraw) {
8806                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8807             /* [HGM] in two-machine mode we delay relaying draw offer      */
8808             /* until after we also have move, to see if it is really claim */
8809             }
8810         } else if (gameMode == MachinePlaysWhite ||
8811                    gameMode == MachinePlaysBlack) {
8812           if (userOfferedDraw) {
8813             DisplayInformation(_("Machine accepts your draw offer"));
8814             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8815           } else {
8816             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8817           }
8818         }
8819     }
8820
8821
8822     /*
8823      * Look for thinking output
8824      */
8825     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8826           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8827                                 ) {
8828         int plylev, mvleft, mvtot, curscore, time;
8829         char mvname[MOVE_LEN];
8830         u64 nodes; // [DM]
8831         char plyext;
8832         int ignore = FALSE;
8833         int prefixHint = FALSE;
8834         mvname[0] = NULLCHAR;
8835
8836         switch (gameMode) {
8837           case MachinePlaysBlack:
8838           case IcsPlayingBlack:
8839             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8840             break;
8841           case MachinePlaysWhite:
8842           case IcsPlayingWhite:
8843             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8844             break;
8845           case AnalyzeMode:
8846           case AnalyzeFile:
8847             break;
8848           case IcsObserving: /* [DM] icsEngineAnalyze */
8849             if (!appData.icsEngineAnalyze) ignore = TRUE;
8850             break;
8851           case TwoMachinesPlay:
8852             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8853                 ignore = TRUE;
8854             }
8855             break;
8856           default:
8857             ignore = TRUE;
8858             break;
8859         }
8860
8861         if (!ignore) {
8862             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8863             buf1[0] = NULLCHAR;
8864             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8865                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8866
8867                 if (plyext != ' ' && plyext != '\t') {
8868                     time *= 100;
8869                 }
8870
8871                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8872                 if( cps->scoreIsAbsolute &&
8873                     ( gameMode == MachinePlaysBlack ||
8874                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8875                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8876                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8877                      !WhiteOnMove(currentMove)
8878                     ) )
8879                 {
8880                     curscore = -curscore;
8881                 }
8882
8883                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
8884
8885                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
8886                         char buf[MSG_SIZ];
8887                         FILE *f;
8888                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
8889                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
8890                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
8891                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
8892                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
8893                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
8894                                 fclose(f);
8895                         } else DisplayError(_("failed writing PV"), 0);
8896                 }
8897
8898                 tempStats.depth = plylev;
8899                 tempStats.nodes = nodes;
8900                 tempStats.time = time;
8901                 tempStats.score = curscore;
8902                 tempStats.got_only_move = 0;
8903
8904                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8905                         int ticklen;
8906
8907                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8908                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8909                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8910                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8911                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8912                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8913                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8914                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8915                 }
8916
8917                 /* Buffer overflow protection */
8918                 if (pv[0] != NULLCHAR) {
8919                     if (strlen(pv) >= sizeof(tempStats.movelist)
8920                         && appData.debugMode) {
8921                         fprintf(debugFP,
8922                                 "PV is too long; using the first %u bytes.\n",
8923                                 (unsigned) sizeof(tempStats.movelist) - 1);
8924                     }
8925
8926                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8927                 } else {
8928                     sprintf(tempStats.movelist, " no PV\n");
8929                 }
8930
8931                 if (tempStats.seen_stat) {
8932                     tempStats.ok_to_send = 1;
8933                 }
8934
8935                 if (strchr(tempStats.movelist, '(') != NULL) {
8936                     tempStats.line_is_book = 1;
8937                     tempStats.nr_moves = 0;
8938                     tempStats.moves_left = 0;
8939                 } else {
8940                     tempStats.line_is_book = 0;
8941                 }
8942
8943                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8944                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8945
8946                 SendProgramStatsToFrontend( cps, &tempStats );
8947
8948                 /*
8949                     [AS] Protect the thinkOutput buffer from overflow... this
8950                     is only useful if buf1 hasn't overflowed first!
8951                 */
8952                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8953                          plylev,
8954                          (gameMode == TwoMachinesPlay ?
8955                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8956                          ((double) curscore) / 100.0,
8957                          prefixHint ? lastHint : "",
8958                          prefixHint ? " " : "" );
8959
8960                 if( buf1[0] != NULLCHAR ) {
8961                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8962
8963                     if( strlen(pv) > max_len ) {
8964                         if( appData.debugMode) {
8965                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8966                         }
8967                         pv[max_len+1] = '\0';
8968                     }
8969
8970                     strcat( thinkOutput, pv);
8971                 }
8972
8973                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8974                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8975                     DisplayMove(currentMove - 1);
8976                 }
8977                 return;
8978
8979             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8980                 /* crafty (9.25+) says "(only move) <move>"
8981                  * if there is only 1 legal move
8982                  */
8983                 sscanf(p, "(only move) %s", buf1);
8984                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8985                 sprintf(programStats.movelist, "%s (only move)", buf1);
8986                 programStats.depth = 1;
8987                 programStats.nr_moves = 1;
8988                 programStats.moves_left = 1;
8989                 programStats.nodes = 1;
8990                 programStats.time = 1;
8991                 programStats.got_only_move = 1;
8992
8993                 /* Not really, but we also use this member to
8994                    mean "line isn't going to change" (Crafty
8995                    isn't searching, so stats won't change) */
8996                 programStats.line_is_book = 1;
8997
8998                 SendProgramStatsToFrontend( cps, &programStats );
8999
9000                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9001                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9002                     DisplayMove(currentMove - 1);
9003                 }
9004                 return;
9005             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9006                               &time, &nodes, &plylev, &mvleft,
9007                               &mvtot, mvname) >= 5) {
9008                 /* The stat01: line is from Crafty (9.29+) in response
9009                    to the "." command */
9010                 programStats.seen_stat = 1;
9011                 cps->maybeThinking = TRUE;
9012
9013                 if (programStats.got_only_move || !appData.periodicUpdates)
9014                   return;
9015
9016                 programStats.depth = plylev;
9017                 programStats.time = time;
9018                 programStats.nodes = nodes;
9019                 programStats.moves_left = mvleft;
9020                 programStats.nr_moves = mvtot;
9021                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9022                 programStats.ok_to_send = 1;
9023                 programStats.movelist[0] = '\0';
9024
9025                 SendProgramStatsToFrontend( cps, &programStats );
9026
9027                 return;
9028
9029             } else if (strncmp(message,"++",2) == 0) {
9030                 /* Crafty 9.29+ outputs this */
9031                 programStats.got_fail = 2;
9032                 return;
9033
9034             } else if (strncmp(message,"--",2) == 0) {
9035                 /* Crafty 9.29+ outputs this */
9036                 programStats.got_fail = 1;
9037                 return;
9038
9039             } else if (thinkOutput[0] != NULLCHAR &&
9040                        strncmp(message, "    ", 4) == 0) {
9041                 unsigned message_len;
9042
9043                 p = message;
9044                 while (*p && *p == ' ') p++;
9045
9046                 message_len = strlen( p );
9047
9048                 /* [AS] Avoid buffer overflow */
9049                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9050                     strcat(thinkOutput, " ");
9051                     strcat(thinkOutput, p);
9052                 }
9053
9054                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9055                     strcat(programStats.movelist, " ");
9056                     strcat(programStats.movelist, p);
9057                 }
9058
9059                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9060                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9061                     DisplayMove(currentMove - 1);
9062                 }
9063                 return;
9064             }
9065         }
9066         else {
9067             buf1[0] = NULLCHAR;
9068
9069             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9070                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9071             {
9072                 ChessProgramStats cpstats;
9073
9074                 if (plyext != ' ' && plyext != '\t') {
9075                     time *= 100;
9076                 }
9077
9078                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9079                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9080                     curscore = -curscore;
9081                 }
9082
9083                 cpstats.depth = plylev;
9084                 cpstats.nodes = nodes;
9085                 cpstats.time = time;
9086                 cpstats.score = curscore;
9087                 cpstats.got_only_move = 0;
9088                 cpstats.movelist[0] = '\0';
9089
9090                 if (buf1[0] != NULLCHAR) {
9091                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9092                 }
9093
9094                 cpstats.ok_to_send = 0;
9095                 cpstats.line_is_book = 0;
9096                 cpstats.nr_moves = 0;
9097                 cpstats.moves_left = 0;
9098
9099                 SendProgramStatsToFrontend( cps, &cpstats );
9100             }
9101         }
9102     }
9103 }
9104
9105
9106 /* Parse a game score from the character string "game", and
9107    record it as the history of the current game.  The game
9108    score is NOT assumed to start from the standard position.
9109    The display is not updated in any way.
9110    */
9111 void
9112 ParseGameHistory (char *game)
9113 {
9114     ChessMove moveType;
9115     int fromX, fromY, toX, toY, boardIndex;
9116     char promoChar;
9117     char *p, *q;
9118     char buf[MSG_SIZ];
9119
9120     if (appData.debugMode)
9121       fprintf(debugFP, "Parsing game history: %s\n", game);
9122
9123     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9124     gameInfo.site = StrSave(appData.icsHost);
9125     gameInfo.date = PGNDate();
9126     gameInfo.round = StrSave("-");
9127
9128     /* Parse out names of players */
9129     while (*game == ' ') game++;
9130     p = buf;
9131     while (*game != ' ') *p++ = *game++;
9132     *p = NULLCHAR;
9133     gameInfo.white = StrSave(buf);
9134     while (*game == ' ') game++;
9135     p = buf;
9136     while (*game != ' ' && *game != '\n') *p++ = *game++;
9137     *p = NULLCHAR;
9138     gameInfo.black = StrSave(buf);
9139
9140     /* Parse moves */
9141     boardIndex = blackPlaysFirst ? 1 : 0;
9142     yynewstr(game);
9143     for (;;) {
9144         yyboardindex = boardIndex;
9145         moveType = (ChessMove) Myylex();
9146         switch (moveType) {
9147           case IllegalMove:             /* maybe suicide chess, etc. */
9148   if (appData.debugMode) {
9149     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9150     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9151     setbuf(debugFP, NULL);
9152   }
9153           case WhitePromotion:
9154           case BlackPromotion:
9155           case WhiteNonPromotion:
9156           case BlackNonPromotion:
9157           case NormalMove:
9158           case WhiteCapturesEnPassant:
9159           case BlackCapturesEnPassant:
9160           case WhiteKingSideCastle:
9161           case WhiteQueenSideCastle:
9162           case BlackKingSideCastle:
9163           case BlackQueenSideCastle:
9164           case WhiteKingSideCastleWild:
9165           case WhiteQueenSideCastleWild:
9166           case BlackKingSideCastleWild:
9167           case BlackQueenSideCastleWild:
9168           /* PUSH Fabien */
9169           case WhiteHSideCastleFR:
9170           case WhiteASideCastleFR:
9171           case BlackHSideCastleFR:
9172           case BlackASideCastleFR:
9173           /* POP Fabien */
9174             fromX = currentMoveString[0] - AAA;
9175             fromY = currentMoveString[1] - ONE;
9176             toX = currentMoveString[2] - AAA;
9177             toY = currentMoveString[3] - ONE;
9178             promoChar = currentMoveString[4];
9179             break;
9180           case WhiteDrop:
9181           case BlackDrop:
9182             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9183             fromX = moveType == WhiteDrop ?
9184               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9185             (int) CharToPiece(ToLower(currentMoveString[0]));
9186             fromY = DROP_RANK;
9187             toX = currentMoveString[2] - AAA;
9188             toY = currentMoveString[3] - ONE;
9189             promoChar = NULLCHAR;
9190             break;
9191           case AmbiguousMove:
9192             /* bug? */
9193             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9194   if (appData.debugMode) {
9195     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9196     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9197     setbuf(debugFP, NULL);
9198   }
9199             DisplayError(buf, 0);
9200             return;
9201           case ImpossibleMove:
9202             /* bug? */
9203             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9204   if (appData.debugMode) {
9205     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9206     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9207     setbuf(debugFP, NULL);
9208   }
9209             DisplayError(buf, 0);
9210             return;
9211           case EndOfFile:
9212             if (boardIndex < backwardMostMove) {
9213                 /* Oops, gap.  How did that happen? */
9214                 DisplayError(_("Gap in move list"), 0);
9215                 return;
9216             }
9217             backwardMostMove =  blackPlaysFirst ? 1 : 0;
9218             if (boardIndex > forwardMostMove) {
9219                 forwardMostMove = boardIndex;
9220             }
9221             return;
9222           case ElapsedTime:
9223             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9224                 strcat(parseList[boardIndex-1], " ");
9225                 strcat(parseList[boardIndex-1], yy_text);
9226             }
9227             continue;
9228           case Comment:
9229           case PGNTag:
9230           case NAG:
9231           default:
9232             /* ignore */
9233             continue;
9234           case WhiteWins:
9235           case BlackWins:
9236           case GameIsDrawn:
9237           case GameUnfinished:
9238             if (gameMode == IcsExamining) {
9239                 if (boardIndex < backwardMostMove) {
9240                     /* Oops, gap.  How did that happen? */
9241                     return;
9242                 }
9243                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9244                 return;
9245             }
9246             gameInfo.result = moveType;
9247             p = strchr(yy_text, '{');
9248             if (p == NULL) p = strchr(yy_text, '(');
9249             if (p == NULL) {
9250                 p = yy_text;
9251                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9252             } else {
9253                 q = strchr(p, *p == '{' ? '}' : ')');
9254                 if (q != NULL) *q = NULLCHAR;
9255                 p++;
9256             }
9257             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9258             gameInfo.resultDetails = StrSave(p);
9259             continue;
9260         }
9261         if (boardIndex >= forwardMostMove &&
9262             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9263             backwardMostMove = blackPlaysFirst ? 1 : 0;
9264             return;
9265         }
9266         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9267                                  fromY, fromX, toY, toX, promoChar,
9268                                  parseList[boardIndex]);
9269         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9270         /* currentMoveString is set as a side-effect of yylex */
9271         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9272         strcat(moveList[boardIndex], "\n");
9273         boardIndex++;
9274         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9275         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9276           case MT_NONE:
9277           case MT_STALEMATE:
9278           default:
9279             break;
9280           case MT_CHECK:
9281             if(gameInfo.variant != VariantShogi)
9282                 strcat(parseList[boardIndex - 1], "+");
9283             break;
9284           case MT_CHECKMATE:
9285           case MT_STAINMATE:
9286             strcat(parseList[boardIndex - 1], "#");
9287             break;
9288         }
9289     }
9290 }
9291
9292
9293 /* Apply a move to the given board  */
9294 void
9295 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9296 {
9297   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9298   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9299
9300     /* [HGM] compute & store e.p. status and castling rights for new position */
9301     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9302
9303       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9304       oldEP = (signed char)board[EP_STATUS];
9305       board[EP_STATUS] = EP_NONE;
9306
9307   if (fromY == DROP_RANK) {
9308         /* must be first */
9309         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9310             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9311             return;
9312         }
9313         piece = board[toY][toX] = (ChessSquare) fromX;
9314   } else {
9315       int i;
9316
9317       if( board[toY][toX] != EmptySquare )
9318            board[EP_STATUS] = EP_CAPTURE;
9319
9320       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9321            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9322                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9323       } else
9324       if( board[fromY][fromX] == WhitePawn ) {
9325            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9326                board[EP_STATUS] = EP_PAWN_MOVE;
9327            if( toY-fromY==2) {
9328                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9329                         gameInfo.variant != VariantBerolina || toX < fromX)
9330                       board[EP_STATUS] = toX | berolina;
9331                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9332                         gameInfo.variant != VariantBerolina || toX > fromX)
9333                       board[EP_STATUS] = toX;
9334            }
9335       } else
9336       if( board[fromY][fromX] == BlackPawn ) {
9337            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9338                board[EP_STATUS] = EP_PAWN_MOVE;
9339            if( toY-fromY== -2) {
9340                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9341                         gameInfo.variant != VariantBerolina || toX < fromX)
9342                       board[EP_STATUS] = toX | berolina;
9343                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9344                         gameInfo.variant != VariantBerolina || toX > fromX)
9345                       board[EP_STATUS] = toX;
9346            }
9347        }
9348
9349        for(i=0; i<nrCastlingRights; i++) {
9350            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9351               board[CASTLING][i] == toX   && castlingRank[i] == toY
9352              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9353        }
9354
9355        if(gameInfo.variant == VariantSChess) { // update virginity
9356            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
9357            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
9358            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
9359            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
9360        }
9361
9362      if (fromX == toX && fromY == toY) return;
9363
9364      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9365      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9366      if(gameInfo.variant == VariantKnightmate)
9367          king += (int) WhiteUnicorn - (int) WhiteKing;
9368
9369     /* Code added by Tord: */
9370     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9371     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9372         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9373       board[fromY][fromX] = EmptySquare;
9374       board[toY][toX] = EmptySquare;
9375       if((toX > fromX) != (piece == WhiteRook)) {
9376         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9377       } else {
9378         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9379       }
9380     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9381                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9382       board[fromY][fromX] = EmptySquare;
9383       board[toY][toX] = EmptySquare;
9384       if((toX > fromX) != (piece == BlackRook)) {
9385         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9386       } else {
9387         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9388       }
9389     /* End of code added by Tord */
9390
9391     } else if (board[fromY][fromX] == king
9392         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9393         && toY == fromY && toX > fromX+1) {
9394         board[fromY][fromX] = EmptySquare;
9395         board[toY][toX] = king;
9396         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9397         board[fromY][BOARD_RGHT-1] = EmptySquare;
9398     } else if (board[fromY][fromX] == king
9399         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9400                && toY == fromY && toX < fromX-1) {
9401         board[fromY][fromX] = EmptySquare;
9402         board[toY][toX] = king;
9403         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9404         board[fromY][BOARD_LEFT] = EmptySquare;
9405     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9406                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9407                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9408                ) {
9409         /* white pawn promotion */
9410         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9411         if(gameInfo.variant==VariantBughouse ||
9412            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9413             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9414         board[fromY][fromX] = EmptySquare;
9415     } else if ((fromY >= BOARD_HEIGHT>>1)
9416                && (toX != fromX)
9417                && gameInfo.variant != VariantXiangqi
9418                && gameInfo.variant != VariantBerolina
9419                && (board[fromY][fromX] == WhitePawn)
9420                && (board[toY][toX] == EmptySquare)) {
9421         board[fromY][fromX] = EmptySquare;
9422         board[toY][toX] = WhitePawn;
9423         captured = board[toY - 1][toX];
9424         board[toY - 1][toX] = EmptySquare;
9425     } else if ((fromY == BOARD_HEIGHT-4)
9426                && (toX == fromX)
9427                && gameInfo.variant == VariantBerolina
9428                && (board[fromY][fromX] == WhitePawn)
9429                && (board[toY][toX] == EmptySquare)) {
9430         board[fromY][fromX] = EmptySquare;
9431         board[toY][toX] = WhitePawn;
9432         if(oldEP & EP_BEROLIN_A) {
9433                 captured = board[fromY][fromX-1];
9434                 board[fromY][fromX-1] = EmptySquare;
9435         }else{  captured = board[fromY][fromX+1];
9436                 board[fromY][fromX+1] = EmptySquare;
9437         }
9438     } else if (board[fromY][fromX] == king
9439         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9440                && toY == fromY && toX > fromX+1) {
9441         board[fromY][fromX] = EmptySquare;
9442         board[toY][toX] = king;
9443         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9444         board[fromY][BOARD_RGHT-1] = EmptySquare;
9445     } else if (board[fromY][fromX] == king
9446         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9447                && toY == fromY && toX < fromX-1) {
9448         board[fromY][fromX] = EmptySquare;
9449         board[toY][toX] = king;
9450         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9451         board[fromY][BOARD_LEFT] = EmptySquare;
9452     } else if (fromY == 7 && fromX == 3
9453                && board[fromY][fromX] == BlackKing
9454                && toY == 7 && toX == 5) {
9455         board[fromY][fromX] = EmptySquare;
9456         board[toY][toX] = BlackKing;
9457         board[fromY][7] = EmptySquare;
9458         board[toY][4] = BlackRook;
9459     } else if (fromY == 7 && fromX == 3
9460                && board[fromY][fromX] == BlackKing
9461                && toY == 7 && toX == 1) {
9462         board[fromY][fromX] = EmptySquare;
9463         board[toY][toX] = BlackKing;
9464         board[fromY][0] = EmptySquare;
9465         board[toY][2] = BlackRook;
9466     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9467                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9468                && toY < promoRank && promoChar
9469                ) {
9470         /* black pawn promotion */
9471         board[toY][toX] = CharToPiece(ToLower(promoChar));
9472         if(gameInfo.variant==VariantBughouse ||
9473            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9474             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9475         board[fromY][fromX] = EmptySquare;
9476     } else if ((fromY < BOARD_HEIGHT>>1)
9477                && (toX != fromX)
9478                && gameInfo.variant != VariantXiangqi
9479                && gameInfo.variant != VariantBerolina
9480                && (board[fromY][fromX] == BlackPawn)
9481                && (board[toY][toX] == EmptySquare)) {
9482         board[fromY][fromX] = EmptySquare;
9483         board[toY][toX] = BlackPawn;
9484         captured = board[toY + 1][toX];
9485         board[toY + 1][toX] = EmptySquare;
9486     } else if ((fromY == 3)
9487                && (toX == fromX)
9488                && gameInfo.variant == VariantBerolina
9489                && (board[fromY][fromX] == BlackPawn)
9490                && (board[toY][toX] == EmptySquare)) {
9491         board[fromY][fromX] = EmptySquare;
9492         board[toY][toX] = BlackPawn;
9493         if(oldEP & EP_BEROLIN_A) {
9494                 captured = board[fromY][fromX-1];
9495                 board[fromY][fromX-1] = EmptySquare;
9496         }else{  captured = board[fromY][fromX+1];
9497                 board[fromY][fromX+1] = EmptySquare;
9498         }
9499     } else {
9500         board[toY][toX] = board[fromY][fromX];
9501         board[fromY][fromX] = EmptySquare;
9502     }
9503   }
9504
9505     if (gameInfo.holdingsWidth != 0) {
9506
9507       /* !!A lot more code needs to be written to support holdings  */
9508       /* [HGM] OK, so I have written it. Holdings are stored in the */
9509       /* penultimate board files, so they are automaticlly stored   */
9510       /* in the game history.                                       */
9511       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9512                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9513         /* Delete from holdings, by decreasing count */
9514         /* and erasing image if necessary            */
9515         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9516         if(p < (int) BlackPawn) { /* white drop */
9517              p -= (int)WhitePawn;
9518                  p = PieceToNumber((ChessSquare)p);
9519              if(p >= gameInfo.holdingsSize) p = 0;
9520              if(--board[p][BOARD_WIDTH-2] <= 0)
9521                   board[p][BOARD_WIDTH-1] = EmptySquare;
9522              if((int)board[p][BOARD_WIDTH-2] < 0)
9523                         board[p][BOARD_WIDTH-2] = 0;
9524         } else {                  /* black drop */
9525              p -= (int)BlackPawn;
9526                  p = PieceToNumber((ChessSquare)p);
9527              if(p >= gameInfo.holdingsSize) p = 0;
9528              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9529                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9530              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9531                         board[BOARD_HEIGHT-1-p][1] = 0;
9532         }
9533       }
9534       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9535           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9536         /* [HGM] holdings: Add to holdings, if holdings exist */
9537         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9538                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9539                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9540         }
9541         p = (int) captured;
9542         if (p >= (int) BlackPawn) {
9543           p -= (int)BlackPawn;
9544           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9545                   /* in Shogi restore piece to its original  first */
9546                   captured = (ChessSquare) (DEMOTED captured);
9547                   p = DEMOTED p;
9548           }
9549           p = PieceToNumber((ChessSquare)p);
9550           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9551           board[p][BOARD_WIDTH-2]++;
9552           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9553         } else {
9554           p -= (int)WhitePawn;
9555           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9556                   captured = (ChessSquare) (DEMOTED captured);
9557                   p = DEMOTED p;
9558           }
9559           p = PieceToNumber((ChessSquare)p);
9560           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9561           board[BOARD_HEIGHT-1-p][1]++;
9562           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9563         }
9564       }
9565     } else if (gameInfo.variant == VariantAtomic) {
9566       if (captured != EmptySquare) {
9567         int y, x;
9568         for (y = toY-1; y <= toY+1; y++) {
9569           for (x = toX-1; x <= toX+1; x++) {
9570             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9571                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9572               board[y][x] = EmptySquare;
9573             }
9574           }
9575         }
9576         board[toY][toX] = EmptySquare;
9577       }
9578     }
9579     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9580         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9581     } else
9582     if(promoChar == '+') {
9583         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
9584         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9585     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9586         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9587         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
9588            && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
9589         board[toY][toX] = newPiece;
9590     }
9591     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9592                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9593         // [HGM] superchess: take promotion piece out of holdings
9594         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9595         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9596             if(!--board[k][BOARD_WIDTH-2])
9597                 board[k][BOARD_WIDTH-1] = EmptySquare;
9598         } else {
9599             if(!--board[BOARD_HEIGHT-1-k][1])
9600                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9601         }
9602     }
9603
9604 }
9605
9606 /* Updates forwardMostMove */
9607 void
9608 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
9609 {
9610 //    forwardMostMove++; // [HGM] bare: moved downstream
9611
9612     (void) CoordsToAlgebraic(boards[forwardMostMove],
9613                              PosFlags(forwardMostMove),
9614                              fromY, fromX, toY, toX, promoChar,
9615                              parseList[forwardMostMove]);
9616
9617     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9618         int timeLeft; static int lastLoadFlag=0; int king, piece;
9619         piece = boards[forwardMostMove][fromY][fromX];
9620         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9621         if(gameInfo.variant == VariantKnightmate)
9622             king += (int) WhiteUnicorn - (int) WhiteKing;
9623         if(forwardMostMove == 0) {
9624             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9625                 fprintf(serverMoves, "%s;", UserName());
9626             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9627                 fprintf(serverMoves, "%s;", second.tidy);
9628             fprintf(serverMoves, "%s;", first.tidy);
9629             if(gameMode == MachinePlaysWhite)
9630                 fprintf(serverMoves, "%s;", UserName());
9631             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9632                 fprintf(serverMoves, "%s;", second.tidy);
9633         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9634         lastLoadFlag = loadFlag;
9635         // print base move
9636         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9637         // print castling suffix
9638         if( toY == fromY && piece == king ) {
9639             if(toX-fromX > 1)
9640                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9641             if(fromX-toX >1)
9642                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9643         }
9644         // e.p. suffix
9645         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9646              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9647              boards[forwardMostMove][toY][toX] == EmptySquare
9648              && fromX != toX && fromY != toY)
9649                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9650         // promotion suffix
9651         if(promoChar != NULLCHAR) {
9652             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
9653                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
9654                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
9655             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
9656         }
9657         if(!loadFlag) {
9658                 char buf[MOVE_LEN*2], *p; int len;
9659             fprintf(serverMoves, "/%d/%d",
9660                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9661             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9662             else                      timeLeft = blackTimeRemaining/1000;
9663             fprintf(serverMoves, "/%d", timeLeft);
9664                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
9665                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
9666                 if(p = strchr(buf, '=')) *p = NULLCHAR;
9667                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
9668             fprintf(serverMoves, "/%s", buf);
9669         }
9670         fflush(serverMoves);
9671     }
9672
9673     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
9674         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
9675       return;
9676     }
9677     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9678     if (commentList[forwardMostMove+1] != NULL) {
9679         free(commentList[forwardMostMove+1]);
9680         commentList[forwardMostMove+1] = NULL;
9681     }
9682     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9683     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9684     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9685     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9686     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9687     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9688     adjustedClock = FALSE;
9689     gameInfo.result = GameUnfinished;
9690     if (gameInfo.resultDetails != NULL) {
9691         free(gameInfo.resultDetails);
9692         gameInfo.resultDetails = NULL;
9693     }
9694     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9695                               moveList[forwardMostMove - 1]);
9696     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9697       case MT_NONE:
9698       case MT_STALEMATE:
9699       default:
9700         break;
9701       case MT_CHECK:
9702         if(gameInfo.variant != VariantShogi)
9703             strcat(parseList[forwardMostMove - 1], "+");
9704         break;
9705       case MT_CHECKMATE:
9706       case MT_STAINMATE:
9707         strcat(parseList[forwardMostMove - 1], "#");
9708         break;
9709     }
9710
9711 }
9712
9713 /* Updates currentMove if not pausing */
9714 void
9715 ShowMove (int fromX, int fromY, int toX, int toY)
9716 {
9717     int instant = (gameMode == PlayFromGameFile) ?
9718         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9719     if(appData.noGUI) return;
9720     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9721         if (!instant) {
9722             if (forwardMostMove == currentMove + 1) {
9723                 AnimateMove(boards[forwardMostMove - 1],
9724                             fromX, fromY, toX, toY);
9725             }
9726         }
9727         currentMove = forwardMostMove;
9728     }
9729
9730     if (instant) return;
9731
9732     DisplayMove(currentMove - 1);
9733     DrawPosition(FALSE, boards[currentMove]);
9734     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9735             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
9736                 SetHighlights(fromX, fromY, toX, toY);
9737             }
9738     }
9739     DisplayBothClocks();
9740     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9741 }
9742
9743 void
9744 SendEgtPath (ChessProgramState *cps)
9745 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9746         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9747
9748         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9749
9750         while(*p) {
9751             char c, *q = name+1, *r, *s;
9752
9753             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9754             while(*p && *p != ',') *q++ = *p++;
9755             *q++ = ':'; *q = 0;
9756             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9757                 strcmp(name, ",nalimov:") == 0 ) {
9758                 // take nalimov path from the menu-changeable option first, if it is defined
9759               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9760                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9761             } else
9762             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9763                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9764                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9765                 s = r = StrStr(s, ":") + 1; // beginning of path info
9766                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9767                 c = *r; *r = 0;             // temporarily null-terminate path info
9768                     *--q = 0;               // strip of trailig ':' from name
9769                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9770                 *r = c;
9771                 SendToProgram(buf,cps);     // send egtbpath command for this format
9772             }
9773             if(*p == ',') p++; // read away comma to position for next format name
9774         }
9775 }
9776
9777 void
9778 InitChessProgram (ChessProgramState *cps, int setup)
9779 /* setup needed to setup FRC opening position */
9780 {
9781     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9782     if (appData.noChessProgram) return;
9783     hintRequested = FALSE;
9784     bookRequested = FALSE;
9785
9786     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
9787     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9788     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9789     if(cps->memSize) { /* [HGM] memory */
9790       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9791         SendToProgram(buf, cps);
9792     }
9793     SendEgtPath(cps); /* [HGM] EGT */
9794     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9795       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9796         SendToProgram(buf, cps);
9797     }
9798
9799     SendToProgram(cps->initString, cps);
9800     if (gameInfo.variant != VariantNormal &&
9801         gameInfo.variant != VariantLoadable
9802         /* [HGM] also send variant if board size non-standard */
9803         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9804                                             ) {
9805       char *v = VariantName(gameInfo.variant);
9806       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9807         /* [HGM] in protocol 1 we have to assume all variants valid */
9808         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9809         DisplayFatalError(buf, 0, 1);
9810         return;
9811       }
9812
9813       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9814       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9815       if( gameInfo.variant == VariantXiangqi )
9816            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9817       if( gameInfo.variant == VariantShogi )
9818            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9819       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9820            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9821       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9822           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9823            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9824       if( gameInfo.variant == VariantCourier )
9825            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9826       if( gameInfo.variant == VariantSuper )
9827            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9828       if( gameInfo.variant == VariantGreat )
9829            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9830       if( gameInfo.variant == VariantSChess )
9831            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9832       if( gameInfo.variant == VariantGrand )
9833            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
9834
9835       if(overruled) {
9836         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9837                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9838            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9839            if(StrStr(cps->variants, b) == NULL) {
9840                // specific sized variant not known, check if general sizing allowed
9841                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9842                    if(StrStr(cps->variants, "boardsize") == NULL) {
9843                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9844                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9845                        DisplayFatalError(buf, 0, 1);
9846                        return;
9847                    }
9848                    /* [HGM] here we really should compare with the maximum supported board size */
9849                }
9850            }
9851       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9852       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9853       SendToProgram(buf, cps);
9854     }
9855     currentlyInitializedVariant = gameInfo.variant;
9856
9857     /* [HGM] send opening position in FRC to first engine */
9858     if(setup) {
9859           SendToProgram("force\n", cps);
9860           SendBoard(cps, 0);
9861           /* engine is now in force mode! Set flag to wake it up after first move. */
9862           setboardSpoiledMachineBlack = 1;
9863     }
9864
9865     if (cps->sendICS) {
9866       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9867       SendToProgram(buf, cps);
9868     }
9869     cps->maybeThinking = FALSE;
9870     cps->offeredDraw = 0;
9871     if (!appData.icsActive) {
9872         SendTimeControl(cps, movesPerSession, timeControl,
9873                         timeIncrement, appData.searchDepth,
9874                         searchTime);
9875     }
9876     if (appData.showThinking
9877         // [HGM] thinking: four options require thinking output to be sent
9878         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9879                                 ) {
9880         SendToProgram("post\n", cps);
9881     }
9882     SendToProgram("hard\n", cps);
9883     if (!appData.ponderNextMove) {
9884         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9885            it without being sure what state we are in first.  "hard"
9886            is not a toggle, so that one is OK.
9887          */
9888         SendToProgram("easy\n", cps);
9889     }
9890     if (cps->usePing) {
9891       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9892       SendToProgram(buf, cps);
9893     }
9894     cps->initDone = TRUE;
9895     ClearEngineOutputPane(cps == &second);
9896 }
9897
9898
9899 void
9900 StartChessProgram (ChessProgramState *cps)
9901 {
9902     char buf[MSG_SIZ];
9903     int err;
9904
9905     if (appData.noChessProgram) return;
9906     cps->initDone = FALSE;
9907
9908     if (strcmp(cps->host, "localhost") == 0) {
9909         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9910     } else if (*appData.remoteShell == NULLCHAR) {
9911         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9912     } else {
9913         if (*appData.remoteUser == NULLCHAR) {
9914           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9915                     cps->program);
9916         } else {
9917           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9918                     cps->host, appData.remoteUser, cps->program);
9919         }
9920         err = StartChildProcess(buf, "", &cps->pr);
9921     }
9922
9923     if (err != 0) {
9924       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9925         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
9926         if(cps != &first) return;
9927         appData.noChessProgram = TRUE;
9928         ThawUI();
9929         SetNCPMode();
9930 //      DisplayFatalError(buf, err, 1);
9931 //      cps->pr = NoProc;
9932 //      cps->isr = NULL;
9933         return;
9934     }
9935
9936     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9937     if (cps->protocolVersion > 1) {
9938       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9939       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9940       cps->comboCnt = 0;  //                and values of combo boxes
9941       SendToProgram(buf, cps);
9942     } else {
9943       SendToProgram("xboard\n", cps);
9944     }
9945 }
9946
9947 void
9948 TwoMachinesEventIfReady P((void))
9949 {
9950   static int curMess = 0;
9951   if (first.lastPing != first.lastPong) {
9952     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9953     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9954     return;
9955   }
9956   if (second.lastPing != second.lastPong) {
9957     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9958     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9959     return;
9960   }
9961   DisplayMessage("", ""); curMess = 0;
9962   ThawUI();
9963   TwoMachinesEvent();
9964 }
9965
9966 char *
9967 MakeName (char *template)
9968 {
9969     time_t clock;
9970     struct tm *tm;
9971     static char buf[MSG_SIZ];
9972     char *p = buf;
9973     int i;
9974
9975     clock = time((time_t *)NULL);
9976     tm = localtime(&clock);
9977
9978     while(*p++ = *template++) if(p[-1] == '%') {
9979         switch(*template++) {
9980           case 0:   *p = 0; return buf;
9981           case 'Y': i = tm->tm_year+1900; break;
9982           case 'y': i = tm->tm_year-100; break;
9983           case 'M': i = tm->tm_mon+1; break;
9984           case 'd': i = tm->tm_mday; break;
9985           case 'h': i = tm->tm_hour; break;
9986           case 'm': i = tm->tm_min; break;
9987           case 's': i = tm->tm_sec; break;
9988           default:  i = 0;
9989         }
9990         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
9991     }
9992     return buf;
9993 }
9994
9995 int
9996 CountPlayers (char *p)
9997 {
9998     int n = 0;
9999     while(p = strchr(p, '\n')) p++, n++; // count participants
10000     return n;
10001 }
10002
10003 FILE *
10004 WriteTourneyFile (char *results, FILE *f)
10005 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10006     if(f == NULL) f = fopen(appData.tourneyFile, "w");
10007     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10008         // create a file with tournament description
10009         fprintf(f, "-participants {%s}\n", appData.participants);
10010         fprintf(f, "-seedBase %d\n", appData.seedBase);
10011         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10012         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10013         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10014         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10015         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10016         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10017         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10018         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10019         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10020         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10021         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10022         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10023         if(searchTime > 0)
10024                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10025         else {
10026                 fprintf(f, "-mps %d\n", appData.movesPerSession);
10027                 fprintf(f, "-tc %s\n", appData.timeControl);
10028                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10029         }
10030         fprintf(f, "-results \"%s\"\n", results);
10031     }
10032     return f;
10033 }
10034
10035 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10036
10037 void
10038 Substitute (char *participants, int expunge)
10039 {
10040     int i, changed, changes=0, nPlayers=0;
10041     char *p, *q, *r, buf[MSG_SIZ];
10042     if(participants == NULL) return;
10043     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10044     r = p = participants; q = appData.participants;
10045     while(*p && *p == *q) {
10046         if(*p == '\n') r = p+1, nPlayers++;
10047         p++; q++;
10048     }
10049     if(*p) { // difference
10050         while(*p && *p++ != '\n');
10051         while(*q && *q++ != '\n');
10052       changed = nPlayers;
10053         changes = 1 + (strcmp(p, q) != 0);
10054     }
10055     if(changes == 1) { // a single engine mnemonic was changed
10056         q = r; while(*q) nPlayers += (*q++ == '\n');
10057         p = buf; while(*r && (*p = *r++) != '\n') p++;
10058         *p = NULLCHAR;
10059         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10060         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10061         if(mnemonic[i]) { // The substitute is valid
10062             FILE *f;
10063             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10064                 flock(fileno(f), LOCK_EX);
10065                 ParseArgsFromFile(f);
10066                 fseek(f, 0, SEEK_SET);
10067                 FREE(appData.participants); appData.participants = participants;
10068                 if(expunge) { // erase results of replaced engine
10069                     int len = strlen(appData.results), w, b, dummy;
10070                     for(i=0; i<len; i++) {
10071                         Pairing(i, nPlayers, &w, &b, &dummy);
10072                         if((w == changed || b == changed) && appData.results[i] == '*') {
10073                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10074                             fclose(f);
10075                             return;
10076                         }
10077                     }
10078                     for(i=0; i<len; i++) {
10079                         Pairing(i, nPlayers, &w, &b, &dummy);
10080                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10081                     }
10082                 }
10083                 WriteTourneyFile(appData.results, f);
10084                 fclose(f); // release lock
10085                 return;
10086             }
10087         } else DisplayError(_("No engine with the name you gave is installed"), 0);
10088     }
10089     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10090     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
10091     free(participants);
10092     return;
10093 }
10094
10095 int
10096 CheckPlayers (char *participants)
10097 {
10098         int i;
10099         char buf[MSG_SIZ], *p;
10100         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10101         while(p = strchr(participants, '\n')) {
10102             *p = NULLCHAR;
10103             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10104             if(!mnemonic[i]) {
10105                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10106                 *p = '\n';
10107                 DisplayError(buf, 0);
10108                 return 1;
10109             }
10110             *p = '\n';
10111             participants = p + 1;
10112         }
10113         return 0;
10114 }
10115
10116 int
10117 CreateTourney (char *name)
10118 {
10119         FILE *f;
10120         if(matchMode && strcmp(name, appData.tourneyFile)) {
10121              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10122         }
10123         if(name[0] == NULLCHAR) {
10124             if(appData.participants[0])
10125                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10126             return 0;
10127         }
10128         f = fopen(name, "r");
10129         if(f) { // file exists
10130             ASSIGN(appData.tourneyFile, name);
10131             ParseArgsFromFile(f); // parse it
10132         } else {
10133             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10134             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10135                 DisplayError(_("Not enough participants"), 0);
10136                 return 0;
10137             }
10138             if(CheckPlayers(appData.participants)) return 0;
10139             ASSIGN(appData.tourneyFile, name);
10140             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10141             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10142         }
10143         fclose(f);
10144         appData.noChessProgram = FALSE;
10145         appData.clockMode = TRUE;
10146         SetGNUMode();
10147         return 1;
10148 }
10149
10150 int
10151 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10152 {
10153     char buf[MSG_SIZ], *p, *q;
10154     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10155     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10156     skip = !all && group[0]; // if group requested, we start in skip mode
10157     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10158         p = names; q = buf; header = 0;
10159         while(*p && *p != '\n') *q++ = *p++;
10160         *q = 0;
10161         if(*p == '\n') p++;
10162         if(buf[0] == '#') {
10163             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10164             depth++; // we must be entering a new group
10165             if(all) continue; // suppress printing group headers when complete list requested
10166             header = 1;
10167             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10168         }
10169         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10170         if(engineList[i]) free(engineList[i]);
10171         engineList[i] = strdup(buf);
10172         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10173         if(engineMnemonic[i]) free(engineMnemonic[i]);
10174         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10175             strcat(buf, " (");
10176             sscanf(q + 8, "%s", buf + strlen(buf));
10177             strcat(buf, ")");
10178         }
10179         engineMnemonic[i] = strdup(buf);
10180         i++;
10181     }
10182     engineList[i] = engineMnemonic[i] = NULL;
10183     return i;
10184 }
10185
10186 // following implemented as macro to avoid type limitations
10187 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10188
10189 void
10190 SwapEngines (int n)
10191 {   // swap settings for first engine and other engine (so far only some selected options)
10192     int h;
10193     char *p;
10194     if(n == 0) return;
10195     SWAP(directory, p)
10196     SWAP(chessProgram, p)
10197     SWAP(isUCI, h)
10198     SWAP(hasOwnBookUCI, h)
10199     SWAP(protocolVersion, h)
10200     SWAP(reuse, h)
10201     SWAP(scoreIsAbsolute, h)
10202     SWAP(timeOdds, h)
10203     SWAP(logo, p)
10204     SWAP(pgnName, p)
10205     SWAP(pvSAN, h)
10206     SWAP(engOptions, p)
10207     SWAP(engInitString, p)
10208     SWAP(computerString, p)
10209     SWAP(features, p)
10210     SWAP(fenOverride, p)
10211     SWAP(NPS, h)
10212     SWAP(accumulateTC, h)
10213     SWAP(host, p)
10214 }
10215
10216 int
10217 GetEngineLine (char *s, int n)
10218 {
10219     int i;
10220     char buf[MSG_SIZ];
10221     extern char *icsNames;
10222     if(!s || !*s) return 0;
10223     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
10224     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
10225     if(!mnemonic[i]) return 0;
10226     if(n == 11) return 1; // just testing if there was a match
10227     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
10228     if(n == 1) SwapEngines(n);
10229     ParseArgsFromString(buf);
10230     if(n == 1) SwapEngines(n);
10231     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
10232         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
10233         ParseArgsFromString(buf);
10234     }
10235     return 1;
10236 }
10237
10238 int
10239 SetPlayer (int player, char *p)
10240 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
10241     int i;
10242     char buf[MSG_SIZ], *engineName;
10243     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10244     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10245     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10246     if(mnemonic[i]) {
10247         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10248         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10249         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10250         ParseArgsFromString(buf);
10251     }
10252     free(engineName);
10253     return i;
10254 }
10255
10256 char *recentEngines;
10257
10258 void
10259 RecentEngineEvent (int nr)
10260 {
10261     int n;
10262 //    SwapEngines(1); // bump first to second
10263 //    ReplaceEngine(&second, 1); // and load it there
10264     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10265     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10266     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10267         ReplaceEngine(&first, 0);
10268         FloatToFront(&appData.recentEngineList, command[n]);
10269     }
10270 }
10271
10272 int
10273 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10274 {   // determine players from game number
10275     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10276
10277     if(appData.tourneyType == 0) {
10278         roundsPerCycle = (nPlayers - 1) | 1;
10279         pairingsPerRound = nPlayers / 2;
10280     } else if(appData.tourneyType > 0) {
10281         roundsPerCycle = nPlayers - appData.tourneyType;
10282         pairingsPerRound = appData.tourneyType;
10283     }
10284     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10285     gamesPerCycle = gamesPerRound * roundsPerCycle;
10286     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10287     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10288     curRound = nr / gamesPerRound; nr %= gamesPerRound;
10289     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10290     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10291     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10292
10293     if(appData.cycleSync) *syncInterval = gamesPerCycle;
10294     if(appData.roundSync) *syncInterval = gamesPerRound;
10295
10296     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10297
10298     if(appData.tourneyType == 0) {
10299         if(curPairing == (nPlayers-1)/2 ) {
10300             *whitePlayer = curRound;
10301             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10302         } else {
10303             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10304             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10305             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10306             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10307         }
10308     } else if(appData.tourneyType > 1) {
10309         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10310         *whitePlayer = curRound + appData.tourneyType;
10311     } else if(appData.tourneyType > 0) {
10312         *whitePlayer = curPairing;
10313         *blackPlayer = curRound + appData.tourneyType;
10314     }
10315
10316     // take care of white/black alternation per round. 
10317     // For cycles and games this is already taken care of by default, derived from matchGame!
10318     return curRound & 1;
10319 }
10320
10321 int
10322 NextTourneyGame (int nr, int *swapColors)
10323 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10324     char *p, *q;
10325     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers;
10326     FILE *tf;
10327     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10328     tf = fopen(appData.tourneyFile, "r");
10329     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10330     ParseArgsFromFile(tf); fclose(tf);
10331     InitTimeControls(); // TC might be altered from tourney file
10332
10333     nPlayers = CountPlayers(appData.participants); // count participants
10334     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10335     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10336
10337     if(syncInterval) {
10338         p = q = appData.results;
10339         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10340         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10341             DisplayMessage(_("Waiting for other game(s)"),"");
10342             waitingForGame = TRUE;
10343             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10344             return 0;
10345         }
10346         waitingForGame = FALSE;
10347     }
10348
10349     if(appData.tourneyType < 0) {
10350         if(nr>=0 && !pairingReceived) {
10351             char buf[1<<16];
10352             if(pairing.pr == NoProc) {
10353                 if(!appData.pairingEngine[0]) {
10354                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10355                     return 0;
10356                 }
10357                 StartChessProgram(&pairing); // starts the pairing engine
10358             }
10359             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10360             SendToProgram(buf, &pairing);
10361             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10362             SendToProgram(buf, &pairing);
10363             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10364         }
10365         pairingReceived = 0;                              // ... so we continue here 
10366         *swapColors = 0;
10367         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10368         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10369         matchGame = 1; roundNr = nr / syncInterval + 1;
10370     }
10371
10372     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10373
10374     // redefine engines, engine dir, etc.
10375     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10376     if(first.pr == NoProc) {
10377       SetPlayer(whitePlayer, appData.participants); // find white player amongst it, and parse its engine line
10378       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10379     }
10380     if(second.pr == NoProc) {
10381       SwapEngines(1);
10382       SetPlayer(blackPlayer, appData.participants); // find black player amongst it, and parse its engine line
10383       SwapEngines(1);         // and make that valid for second engine by swapping
10384       InitEngine(&second, 1);
10385     }
10386     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10387     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10388     return 1;
10389 }
10390
10391 void
10392 NextMatchGame ()
10393 {   // performs game initialization that does not invoke engines, and then tries to start the game
10394     int res, firstWhite, swapColors = 0;
10395     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10396     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
10397         char buf[MSG_SIZ];
10398         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
10399         if(strcmp(buf, currentDebugFile)) { // name has changed
10400             FILE *f = fopen(buf, "w");
10401             if(f) { // if opening the new file failed, just keep using the old one
10402                 ASSIGN(currentDebugFile, buf);
10403                 fclose(debugFP);
10404                 debugFP = f;
10405             }
10406             if(appData.serverFileName) {
10407                 if(serverFP) fclose(serverFP);
10408                 serverFP = fopen(appData.serverFileName, "w");
10409                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
10410                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
10411             }
10412         }
10413     }
10414     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10415     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10416     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
10417     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10418     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10419     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10420     Reset(FALSE, first.pr != NoProc);
10421     res = LoadGameOrPosition(matchGame); // setup game
10422     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10423     if(!res) return; // abort when bad game/pos file
10424     TwoMachinesEvent();
10425 }
10426
10427 void
10428 UserAdjudicationEvent (int result)
10429 {
10430     ChessMove gameResult = GameIsDrawn;
10431
10432     if( result > 0 ) {
10433         gameResult = WhiteWins;
10434     }
10435     else if( result < 0 ) {
10436         gameResult = BlackWins;
10437     }
10438
10439     if( gameMode == TwoMachinesPlay ) {
10440         GameEnds( gameResult, "User adjudication", GE_XBOARD );
10441     }
10442 }
10443
10444
10445 // [HGM] save: calculate checksum of game to make games easily identifiable
10446 int
10447 StringCheckSum (char *s)
10448 {
10449         int i = 0;
10450         if(s==NULL) return 0;
10451         while(*s) i = i*259 + *s++;
10452         return i;
10453 }
10454
10455 int
10456 GameCheckSum ()
10457 {
10458         int i, sum=0;
10459         for(i=backwardMostMove; i<forwardMostMove; i++) {
10460                 sum += pvInfoList[i].depth;
10461                 sum += StringCheckSum(parseList[i]);
10462                 sum += StringCheckSum(commentList[i]);
10463                 sum *= 261;
10464         }
10465         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10466         return sum + StringCheckSum(commentList[i]);
10467 } // end of save patch
10468
10469 void
10470 GameEnds (ChessMove result, char *resultDetails, int whosays)
10471 {
10472     GameMode nextGameMode;
10473     int isIcsGame;
10474     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10475
10476     if(endingGame) return; /* [HGM] crash: forbid recursion */
10477     endingGame = 1;
10478     if(twoBoards) { // [HGM] dual: switch back to one board
10479         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10480         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10481     }
10482     if (appData.debugMode) {
10483       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10484               result, resultDetails ? resultDetails : "(null)", whosays);
10485     }
10486
10487     fromX = fromY = -1; // [HGM] abort any move the user is entering.
10488
10489     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10490         /* If we are playing on ICS, the server decides when the
10491            game is over, but the engine can offer to draw, claim
10492            a draw, or resign.
10493          */
10494 #if ZIPPY
10495         if (appData.zippyPlay && first.initDone) {
10496             if (result == GameIsDrawn) {
10497                 /* In case draw still needs to be claimed */
10498                 SendToICS(ics_prefix);
10499                 SendToICS("draw\n");
10500             } else if (StrCaseStr(resultDetails, "resign")) {
10501                 SendToICS(ics_prefix);
10502                 SendToICS("resign\n");
10503             }
10504         }
10505 #endif
10506         endingGame = 0; /* [HGM] crash */
10507         return;
10508     }
10509
10510     /* If we're loading the game from a file, stop */
10511     if (whosays == GE_FILE) {
10512       (void) StopLoadGameTimer();
10513       gameFileFP = NULL;
10514     }
10515
10516     /* Cancel draw offers */
10517     first.offeredDraw = second.offeredDraw = 0;
10518
10519     /* If this is an ICS game, only ICS can really say it's done;
10520        if not, anyone can. */
10521     isIcsGame = (gameMode == IcsPlayingWhite ||
10522                  gameMode == IcsPlayingBlack ||
10523                  gameMode == IcsObserving    ||
10524                  gameMode == IcsExamining);
10525
10526     if (!isIcsGame || whosays == GE_ICS) {
10527         /* OK -- not an ICS game, or ICS said it was done */
10528         StopClocks();
10529         if (!isIcsGame && !appData.noChessProgram)
10530           SetUserThinkingEnables();
10531
10532         /* [HGM] if a machine claims the game end we verify this claim */
10533         if(gameMode == TwoMachinesPlay && appData.testClaims) {
10534             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10535                 char claimer;
10536                 ChessMove trueResult = (ChessMove) -1;
10537
10538                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
10539                                             first.twoMachinesColor[0] :
10540                                             second.twoMachinesColor[0] ;
10541
10542                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10543                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10544                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10545                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10546                 } else
10547                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10548                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10549                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10550                 } else
10551                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10552                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10553                 }
10554
10555                 // now verify win claims, but not in drop games, as we don't understand those yet
10556                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10557                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10558                     (result == WhiteWins && claimer == 'w' ||
10559                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
10560                       if (appData.debugMode) {
10561                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
10562                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10563                       }
10564                       if(result != trueResult) {
10565                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10566                               result = claimer == 'w' ? BlackWins : WhiteWins;
10567                               resultDetails = buf;
10568                       }
10569                 } else
10570                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10571                     && (forwardMostMove <= backwardMostMove ||
10572                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10573                         (claimer=='b')==(forwardMostMove&1))
10574                                                                                   ) {
10575                       /* [HGM] verify: draws that were not flagged are false claims */
10576                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10577                       result = claimer == 'w' ? BlackWins : WhiteWins;
10578                       resultDetails = buf;
10579                 }
10580                 /* (Claiming a loss is accepted no questions asked!) */
10581             }
10582             /* [HGM] bare: don't allow bare King to win */
10583             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10584                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10585                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10586                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10587                && result != GameIsDrawn)
10588             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10589                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10590                         int p = (signed char)boards[forwardMostMove][i][j] - color;
10591                         if(p >= 0 && p <= (int)WhiteKing) k++;
10592                 }
10593                 if (appData.debugMode) {
10594                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10595                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10596                 }
10597                 if(k <= 1) {
10598                         result = GameIsDrawn;
10599                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10600                         resultDetails = buf;
10601                 }
10602             }
10603         }
10604
10605
10606         if(serverMoves != NULL && !loadFlag) { char c = '=';
10607             if(result==WhiteWins) c = '+';
10608             if(result==BlackWins) c = '-';
10609             if(resultDetails != NULL)
10610                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
10611         }
10612         if (resultDetails != NULL) {
10613             gameInfo.result = result;
10614             gameInfo.resultDetails = StrSave(resultDetails);
10615
10616             /* display last move only if game was not loaded from file */
10617             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10618                 DisplayMove(currentMove - 1);
10619
10620             if (forwardMostMove != 0) {
10621                 if (gameMode != PlayFromGameFile && gameMode != EditGame
10622                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10623                                                                 ) {
10624                     if (*appData.saveGameFile != NULLCHAR) {
10625                         SaveGameToFile(appData.saveGameFile, TRUE);
10626                     } else if (appData.autoSaveGames) {
10627                         AutoSaveGame();
10628                     }
10629                     if (*appData.savePositionFile != NULLCHAR) {
10630                         SavePositionToFile(appData.savePositionFile);
10631                     }
10632                 }
10633             }
10634
10635             /* Tell program how game ended in case it is learning */
10636             /* [HGM] Moved this to after saving the PGN, just in case */
10637             /* engine died and we got here through time loss. In that */
10638             /* case we will get a fatal error writing the pipe, which */
10639             /* would otherwise lose us the PGN.                       */
10640             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
10641             /* output during GameEnds should never be fatal anymore   */
10642             if (gameMode == MachinePlaysWhite ||
10643                 gameMode == MachinePlaysBlack ||
10644                 gameMode == TwoMachinesPlay ||
10645                 gameMode == IcsPlayingWhite ||
10646                 gameMode == IcsPlayingBlack ||
10647                 gameMode == BeginningOfGame) {
10648                 char buf[MSG_SIZ];
10649                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10650                         resultDetails);
10651                 if (first.pr != NoProc) {
10652                     SendToProgram(buf, &first);
10653                 }
10654                 if (second.pr != NoProc &&
10655                     gameMode == TwoMachinesPlay) {
10656                     SendToProgram(buf, &second);
10657                 }
10658             }
10659         }
10660
10661         if (appData.icsActive) {
10662             if (appData.quietPlay &&
10663                 (gameMode == IcsPlayingWhite ||
10664                  gameMode == IcsPlayingBlack)) {
10665                 SendToICS(ics_prefix);
10666                 SendToICS("set shout 1\n");
10667             }
10668             nextGameMode = IcsIdle;
10669             ics_user_moved = FALSE;
10670             /* clean up premove.  It's ugly when the game has ended and the
10671              * premove highlights are still on the board.
10672              */
10673             if (gotPremove) {
10674               gotPremove = FALSE;
10675               ClearPremoveHighlights();
10676               DrawPosition(FALSE, boards[currentMove]);
10677             }
10678             if (whosays == GE_ICS) {
10679                 switch (result) {
10680                 case WhiteWins:
10681                     if (gameMode == IcsPlayingWhite)
10682                         PlayIcsWinSound();
10683                     else if(gameMode == IcsPlayingBlack)
10684                         PlayIcsLossSound();
10685                     break;
10686                 case BlackWins:
10687                     if (gameMode == IcsPlayingBlack)
10688                         PlayIcsWinSound();
10689                     else if(gameMode == IcsPlayingWhite)
10690                         PlayIcsLossSound();
10691                     break;
10692                 case GameIsDrawn:
10693                     PlayIcsDrawSound();
10694                     break;
10695                 default:
10696                     PlayIcsUnfinishedSound();
10697                 }
10698             }
10699         } else if (gameMode == EditGame ||
10700                    gameMode == PlayFromGameFile ||
10701                    gameMode == AnalyzeMode ||
10702                    gameMode == AnalyzeFile) {
10703             nextGameMode = gameMode;
10704         } else {
10705             nextGameMode = EndOfGame;
10706         }
10707         pausing = FALSE;
10708         ModeHighlight();
10709     } else {
10710         nextGameMode = gameMode;
10711     }
10712
10713     if (appData.noChessProgram) {
10714         gameMode = nextGameMode;
10715         ModeHighlight();
10716         endingGame = 0; /* [HGM] crash */
10717         return;
10718     }
10719
10720     if (first.reuse) {
10721         /* Put first chess program into idle state */
10722         if (first.pr != NoProc &&
10723             (gameMode == MachinePlaysWhite ||
10724              gameMode == MachinePlaysBlack ||
10725              gameMode == TwoMachinesPlay ||
10726              gameMode == IcsPlayingWhite ||
10727              gameMode == IcsPlayingBlack ||
10728              gameMode == BeginningOfGame)) {
10729             SendToProgram("force\n", &first);
10730             if (first.usePing) {
10731               char buf[MSG_SIZ];
10732               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10733               SendToProgram(buf, &first);
10734             }
10735         }
10736     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10737         /* Kill off first chess program */
10738         if (first.isr != NULL)
10739           RemoveInputSource(first.isr);
10740         first.isr = NULL;
10741
10742         if (first.pr != NoProc) {
10743             ExitAnalyzeMode();
10744             DoSleep( appData.delayBeforeQuit );
10745             SendToProgram("quit\n", &first);
10746             DoSleep( appData.delayAfterQuit );
10747             DestroyChildProcess(first.pr, first.useSigterm);
10748         }
10749         first.pr = NoProc;
10750     }
10751     if (second.reuse) {
10752         /* Put second chess program into idle state */
10753         if (second.pr != NoProc &&
10754             gameMode == TwoMachinesPlay) {
10755             SendToProgram("force\n", &second);
10756             if (second.usePing) {
10757               char buf[MSG_SIZ];
10758               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10759               SendToProgram(buf, &second);
10760             }
10761         }
10762     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10763         /* Kill off second chess program */
10764         if (second.isr != NULL)
10765           RemoveInputSource(second.isr);
10766         second.isr = NULL;
10767
10768         if (second.pr != NoProc) {
10769             DoSleep( appData.delayBeforeQuit );
10770             SendToProgram("quit\n", &second);
10771             DoSleep( appData.delayAfterQuit );
10772             DestroyChildProcess(second.pr, second.useSigterm);
10773         }
10774         second.pr = NoProc;
10775     }
10776
10777     if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10778         char resChar = '=';
10779         switch (result) {
10780         case WhiteWins:
10781           resChar = '+';
10782           if (first.twoMachinesColor[0] == 'w') {
10783             first.matchWins++;
10784           } else {
10785             second.matchWins++;
10786           }
10787           break;
10788         case BlackWins:
10789           resChar = '-';
10790           if (first.twoMachinesColor[0] == 'b') {
10791             first.matchWins++;
10792           } else {
10793             second.matchWins++;
10794           }
10795           break;
10796         case GameUnfinished:
10797           resChar = ' ';
10798         default:
10799           break;
10800         }
10801
10802         if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10803         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10804             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
10805             ReserveGame(nextGame, resChar); // sets nextGame
10806             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10807             else ranking = strdup("busy"); //suppress popup when aborted but not finished
10808         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10809
10810         if (nextGame <= appData.matchGames && !abortMatch) {
10811             gameMode = nextGameMode;
10812             matchGame = nextGame; // this will be overruled in tourney mode!
10813             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10814             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10815             endingGame = 0; /* [HGM] crash */
10816             return;
10817         } else {
10818             gameMode = nextGameMode;
10819             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10820                      first.tidy, second.tidy,
10821                      first.matchWins, second.matchWins,
10822                      appData.matchGames - (first.matchWins + second.matchWins));
10823             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
10824             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
10825             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10826             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10827                 first.twoMachinesColor = "black\n";
10828                 second.twoMachinesColor = "white\n";
10829             } else {
10830                 first.twoMachinesColor = "white\n";
10831                 second.twoMachinesColor = "black\n";
10832             }
10833         }
10834     }
10835     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10836         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10837       ExitAnalyzeMode();
10838     gameMode = nextGameMode;
10839     ModeHighlight();
10840     endingGame = 0;  /* [HGM] crash */
10841     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10842         if(matchMode == TRUE) { // match through command line: exit with or without popup
10843             if(ranking) {
10844                 ToNrEvent(forwardMostMove);
10845                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10846                 else ExitEvent(0);
10847             } else DisplayFatalError(buf, 0, 0);
10848         } else { // match through menu; just stop, with or without popup
10849             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10850             ModeHighlight();
10851             if(ranking){
10852                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10853             } else DisplayNote(buf);
10854       }
10855       if(ranking) free(ranking);
10856     }
10857 }
10858
10859 /* Assumes program was just initialized (initString sent).
10860    Leaves program in force mode. */
10861 void
10862 FeedMovesToProgram (ChessProgramState *cps, int upto)
10863 {
10864     int i;
10865
10866     if (appData.debugMode)
10867       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10868               startedFromSetupPosition ? "position and " : "",
10869               backwardMostMove, upto, cps->which);
10870     if(currentlyInitializedVariant != gameInfo.variant) {
10871       char buf[MSG_SIZ];
10872         // [HGM] variantswitch: make engine aware of new variant
10873         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10874                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10875         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10876         SendToProgram(buf, cps);
10877         currentlyInitializedVariant = gameInfo.variant;
10878     }
10879     SendToProgram("force\n", cps);
10880     if (startedFromSetupPosition) {
10881         SendBoard(cps, backwardMostMove);
10882     if (appData.debugMode) {
10883         fprintf(debugFP, "feedMoves\n");
10884     }
10885     }
10886     for (i = backwardMostMove; i < upto; i++) {
10887         SendMoveToProgram(i, cps);
10888     }
10889 }
10890
10891
10892 int
10893 ResurrectChessProgram ()
10894 {
10895      /* The chess program may have exited.
10896         If so, restart it and feed it all the moves made so far. */
10897     static int doInit = 0;
10898
10899     if (appData.noChessProgram) return 1;
10900
10901     if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10902         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10903         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10904         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10905     } else {
10906         if (first.pr != NoProc) return 1;
10907         StartChessProgram(&first);
10908     }
10909     InitChessProgram(&first, FALSE);
10910     FeedMovesToProgram(&first, currentMove);
10911
10912     if (!first.sendTime) {
10913         /* can't tell gnuchess what its clock should read,
10914            so we bow to its notion. */
10915         ResetClocks();
10916         timeRemaining[0][currentMove] = whiteTimeRemaining;
10917         timeRemaining[1][currentMove] = blackTimeRemaining;
10918     }
10919
10920     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10921                 appData.icsEngineAnalyze) && first.analysisSupport) {
10922       SendToProgram("analyze\n", &first);
10923       first.analyzing = TRUE;
10924     }
10925     return 1;
10926 }
10927
10928 /*
10929  * Button procedures
10930  */
10931 void
10932 Reset (int redraw, int init)
10933 {
10934     int i;
10935
10936     if (appData.debugMode) {
10937         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10938                 redraw, init, gameMode);
10939     }
10940     CleanupTail(); // [HGM] vari: delete any stored variations
10941     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
10942     pausing = pauseExamInvalid = FALSE;
10943     startedFromSetupPosition = blackPlaysFirst = FALSE;
10944     firstMove = TRUE;
10945     whiteFlag = blackFlag = FALSE;
10946     userOfferedDraw = FALSE;
10947     hintRequested = bookRequested = FALSE;
10948     first.maybeThinking = FALSE;
10949     second.maybeThinking = FALSE;
10950     first.bookSuspend = FALSE; // [HGM] book
10951     second.bookSuspend = FALSE;
10952     thinkOutput[0] = NULLCHAR;
10953     lastHint[0] = NULLCHAR;
10954     ClearGameInfo(&gameInfo);
10955     gameInfo.variant = StringToVariant(appData.variant);
10956     ics_user_moved = ics_clock_paused = FALSE;
10957     ics_getting_history = H_FALSE;
10958     ics_gamenum = -1;
10959     white_holding[0] = black_holding[0] = NULLCHAR;
10960     ClearProgramStats();
10961     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10962
10963     ResetFrontEnd();
10964     ClearHighlights();
10965     flipView = appData.flipView;
10966     ClearPremoveHighlights();
10967     gotPremove = FALSE;
10968     alarmSounded = FALSE;
10969
10970     GameEnds(EndOfFile, NULL, GE_PLAYER);
10971     if(appData.serverMovesName != NULL) {
10972         /* [HGM] prepare to make moves file for broadcasting */
10973         clock_t t = clock();
10974         if(serverMoves != NULL) fclose(serverMoves);
10975         serverMoves = fopen(appData.serverMovesName, "r");
10976         if(serverMoves != NULL) {
10977             fclose(serverMoves);
10978             /* delay 15 sec before overwriting, so all clients can see end */
10979             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10980         }
10981         serverMoves = fopen(appData.serverMovesName, "w");
10982     }
10983
10984     ExitAnalyzeMode();
10985     gameMode = BeginningOfGame;
10986     ModeHighlight();
10987     if(appData.icsActive) gameInfo.variant = VariantNormal;
10988     currentMove = forwardMostMove = backwardMostMove = 0;
10989     MarkTargetSquares(1);
10990     InitPosition(redraw);
10991     for (i = 0; i < MAX_MOVES; i++) {
10992         if (commentList[i] != NULL) {
10993             free(commentList[i]);
10994             commentList[i] = NULL;
10995         }
10996     }
10997     ResetClocks();
10998     timeRemaining[0][0] = whiteTimeRemaining;
10999     timeRemaining[1][0] = blackTimeRemaining;
11000
11001     if (first.pr == NoProc) {
11002         StartChessProgram(&first);
11003     }
11004     if (init) {
11005             InitChessProgram(&first, startedFromSetupPosition);
11006     }
11007     DisplayTitle("");
11008     DisplayMessage("", "");
11009     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11010     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11011     ClearMap();        // [HGM] exclude: invalidate map
11012 }
11013
11014 void
11015 AutoPlayGameLoop ()
11016 {
11017     for (;;) {
11018         if (!AutoPlayOneMove())
11019           return;
11020         if (matchMode || appData.timeDelay == 0)
11021           continue;
11022         if (appData.timeDelay < 0)
11023           return;
11024         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11025         break;
11026     }
11027 }
11028
11029 void
11030 AnalyzeNextGame()
11031 {
11032     ReloadGame(1); // next game
11033 }
11034
11035 int
11036 AutoPlayOneMove ()
11037 {
11038     int fromX, fromY, toX, toY;
11039
11040     if (appData.debugMode) {
11041       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11042     }
11043
11044     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11045       return FALSE;
11046
11047     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
11048       pvInfoList[currentMove].depth = programStats.depth;
11049       pvInfoList[currentMove].score = programStats.score;
11050       pvInfoList[currentMove].time  = 0;
11051       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11052     }
11053
11054     if (currentMove >= forwardMostMove) {
11055       if(gameMode == AnalyzeFile) {
11056           if(appData.loadGameIndex == -1) {
11057             GameEnds(EndOfFile, NULL, GE_FILE);
11058           ScheduleDelayedEvent(AnalyzeNextGame, 10);
11059           } else {
11060           ExitAnalyzeMode(); SendToProgram("force\n", &first);
11061         }
11062       }
11063 //      gameMode = EndOfGame;
11064 //      ModeHighlight();
11065
11066       /* [AS] Clear current move marker at the end of a game */
11067       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11068
11069       return FALSE;
11070     }
11071
11072     toX = moveList[currentMove][2] - AAA;
11073     toY = moveList[currentMove][3] - ONE;
11074
11075     if (moveList[currentMove][1] == '@') {
11076         if (appData.highlightLastMove) {
11077             SetHighlights(-1, -1, toX, toY);
11078         }
11079     } else {
11080         fromX = moveList[currentMove][0] - AAA;
11081         fromY = moveList[currentMove][1] - ONE;
11082
11083         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11084
11085         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11086
11087         if (appData.highlightLastMove) {
11088             SetHighlights(fromX, fromY, toX, toY);
11089         }
11090     }
11091     DisplayMove(currentMove);
11092     SendMoveToProgram(currentMove++, &first);
11093     DisplayBothClocks();
11094     DrawPosition(FALSE, boards[currentMove]);
11095     // [HGM] PV info: always display, routine tests if empty
11096     DisplayComment(currentMove - 1, commentList[currentMove]);
11097     return TRUE;
11098 }
11099
11100
11101 int
11102 LoadGameOneMove (ChessMove readAhead)
11103 {
11104     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11105     char promoChar = NULLCHAR;
11106     ChessMove moveType;
11107     char move[MSG_SIZ];
11108     char *p, *q;
11109
11110     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11111         gameMode != AnalyzeMode && gameMode != Training) {
11112         gameFileFP = NULL;
11113         return FALSE;
11114     }
11115
11116     yyboardindex = forwardMostMove;
11117     if (readAhead != EndOfFile) {
11118       moveType = readAhead;
11119     } else {
11120       if (gameFileFP == NULL)
11121           return FALSE;
11122       moveType = (ChessMove) Myylex();
11123     }
11124
11125     done = FALSE;
11126     switch (moveType) {
11127       case Comment:
11128         if (appData.debugMode)
11129           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11130         p = yy_text;
11131
11132         /* append the comment but don't display it */
11133         AppendComment(currentMove, p, FALSE);
11134         return TRUE;
11135
11136       case WhiteCapturesEnPassant:
11137       case BlackCapturesEnPassant:
11138       case WhitePromotion:
11139       case BlackPromotion:
11140       case WhiteNonPromotion:
11141       case BlackNonPromotion:
11142       case NormalMove:
11143       case WhiteKingSideCastle:
11144       case WhiteQueenSideCastle:
11145       case BlackKingSideCastle:
11146       case BlackQueenSideCastle:
11147       case WhiteKingSideCastleWild:
11148       case WhiteQueenSideCastleWild:
11149       case BlackKingSideCastleWild:
11150       case BlackQueenSideCastleWild:
11151       /* PUSH Fabien */
11152       case WhiteHSideCastleFR:
11153       case WhiteASideCastleFR:
11154       case BlackHSideCastleFR:
11155       case BlackASideCastleFR:
11156       /* POP Fabien */
11157         if (appData.debugMode)
11158           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11159         fromX = currentMoveString[0] - AAA;
11160         fromY = currentMoveString[1] - ONE;
11161         toX = currentMoveString[2] - AAA;
11162         toY = currentMoveString[3] - ONE;
11163         promoChar = currentMoveString[4];
11164         break;
11165
11166       case WhiteDrop:
11167       case BlackDrop:
11168         if (appData.debugMode)
11169           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11170         fromX = moveType == WhiteDrop ?
11171           (int) CharToPiece(ToUpper(currentMoveString[0])) :
11172         (int) CharToPiece(ToLower(currentMoveString[0]));
11173         fromY = DROP_RANK;
11174         toX = currentMoveString[2] - AAA;
11175         toY = currentMoveString[3] - ONE;
11176         break;
11177
11178       case WhiteWins:
11179       case BlackWins:
11180       case GameIsDrawn:
11181       case GameUnfinished:
11182         if (appData.debugMode)
11183           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11184         p = strchr(yy_text, '{');
11185         if (p == NULL) p = strchr(yy_text, '(');
11186         if (p == NULL) {
11187             p = yy_text;
11188             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11189         } else {
11190             q = strchr(p, *p == '{' ? '}' : ')');
11191             if (q != NULL) *q = NULLCHAR;
11192             p++;
11193         }
11194         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11195         GameEnds(moveType, p, GE_FILE);
11196         done = TRUE;
11197         if (cmailMsgLoaded) {
11198             ClearHighlights();
11199             flipView = WhiteOnMove(currentMove);
11200             if (moveType == GameUnfinished) flipView = !flipView;
11201             if (appData.debugMode)
11202               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11203         }
11204         break;
11205
11206       case EndOfFile:
11207         if (appData.debugMode)
11208           fprintf(debugFP, "Parser hit end of file\n");
11209         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11210           case MT_NONE:
11211           case MT_CHECK:
11212             break;
11213           case MT_CHECKMATE:
11214           case MT_STAINMATE:
11215             if (WhiteOnMove(currentMove)) {
11216                 GameEnds(BlackWins, "Black mates", GE_FILE);
11217             } else {
11218                 GameEnds(WhiteWins, "White mates", GE_FILE);
11219             }
11220             break;
11221           case MT_STALEMATE:
11222             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11223             break;
11224         }
11225         done = TRUE;
11226         break;
11227
11228       case MoveNumberOne:
11229         if (lastLoadGameStart == GNUChessGame) {
11230             /* GNUChessGames have numbers, but they aren't move numbers */
11231             if (appData.debugMode)
11232               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11233                       yy_text, (int) moveType);
11234             return LoadGameOneMove(EndOfFile); /* tail recursion */
11235         }
11236         /* else fall thru */
11237
11238       case XBoardGame:
11239       case GNUChessGame:
11240       case PGNTag:
11241         /* Reached start of next game in file */
11242         if (appData.debugMode)
11243           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11244         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11245           case MT_NONE:
11246           case MT_CHECK:
11247             break;
11248           case MT_CHECKMATE:
11249           case MT_STAINMATE:
11250             if (WhiteOnMove(currentMove)) {
11251                 GameEnds(BlackWins, "Black mates", GE_FILE);
11252             } else {
11253                 GameEnds(WhiteWins, "White mates", GE_FILE);
11254             }
11255             break;
11256           case MT_STALEMATE:
11257             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11258             break;
11259         }
11260         done = TRUE;
11261         break;
11262
11263       case PositionDiagram:     /* should not happen; ignore */
11264       case ElapsedTime:         /* ignore */
11265       case NAG:                 /* ignore */
11266         if (appData.debugMode)
11267           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11268                   yy_text, (int) moveType);
11269         return LoadGameOneMove(EndOfFile); /* tail recursion */
11270
11271       case IllegalMove:
11272         if (appData.testLegality) {
11273             if (appData.debugMode)
11274               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11275             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11276                     (forwardMostMove / 2) + 1,
11277                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11278             DisplayError(move, 0);
11279             done = TRUE;
11280         } else {
11281             if (appData.debugMode)
11282               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11283                       yy_text, currentMoveString);
11284             fromX = currentMoveString[0] - AAA;
11285             fromY = currentMoveString[1] - ONE;
11286             toX = currentMoveString[2] - AAA;
11287             toY = currentMoveString[3] - ONE;
11288             promoChar = currentMoveString[4];
11289         }
11290         break;
11291
11292       case AmbiguousMove:
11293         if (appData.debugMode)
11294           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11295         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11296                 (forwardMostMove / 2) + 1,
11297                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11298         DisplayError(move, 0);
11299         done = TRUE;
11300         break;
11301
11302       default:
11303       case ImpossibleMove:
11304         if (appData.debugMode)
11305           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11306         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11307                 (forwardMostMove / 2) + 1,
11308                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11309         DisplayError(move, 0);
11310         done = TRUE;
11311         break;
11312     }
11313
11314     if (done) {
11315         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11316             DrawPosition(FALSE, boards[currentMove]);
11317             DisplayBothClocks();
11318             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11319               DisplayComment(currentMove - 1, commentList[currentMove]);
11320         }
11321         (void) StopLoadGameTimer();
11322         gameFileFP = NULL;
11323         cmailOldMove = forwardMostMove;
11324         return FALSE;
11325     } else {
11326         /* currentMoveString is set as a side-effect of yylex */
11327
11328         thinkOutput[0] = NULLCHAR;
11329         MakeMove(fromX, fromY, toX, toY, promoChar);
11330         currentMove = forwardMostMove;
11331         return TRUE;
11332     }
11333 }
11334
11335 /* Load the nth game from the given file */
11336 int
11337 LoadGameFromFile (char *filename, int n, char *title, int useList)
11338 {
11339     FILE *f;
11340     char buf[MSG_SIZ];
11341
11342     if (strcmp(filename, "-") == 0) {
11343         f = stdin;
11344         title = "stdin";
11345     } else {
11346         f = fopen(filename, "rb");
11347         if (f == NULL) {
11348           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
11349             DisplayError(buf, errno);
11350             return FALSE;
11351         }
11352     }
11353     if (fseek(f, 0, 0) == -1) {
11354         /* f is not seekable; probably a pipe */
11355         useList = FALSE;
11356     }
11357     if (useList && n == 0) {
11358         int error = GameListBuild(f);
11359         if (error) {
11360             DisplayError(_("Cannot build game list"), error);
11361         } else if (!ListEmpty(&gameList) &&
11362                    ((ListGame *) gameList.tailPred)->number > 1) {
11363             GameListPopUp(f, title);
11364             return TRUE;
11365         }
11366         GameListDestroy();
11367         n = 1;
11368     }
11369     if (n == 0) n = 1;
11370     return LoadGame(f, n, title, FALSE);
11371 }
11372
11373
11374 void
11375 MakeRegisteredMove ()
11376 {
11377     int fromX, fromY, toX, toY;
11378     char promoChar;
11379     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11380         switch (cmailMoveType[lastLoadGameNumber - 1]) {
11381           case CMAIL_MOVE:
11382           case CMAIL_DRAW:
11383             if (appData.debugMode)
11384               fprintf(debugFP, "Restoring %s for game %d\n",
11385                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11386
11387             thinkOutput[0] = NULLCHAR;
11388             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11389             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11390             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11391             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11392             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11393             promoChar = cmailMove[lastLoadGameNumber - 1][4];
11394             MakeMove(fromX, fromY, toX, toY, promoChar);
11395             ShowMove(fromX, fromY, toX, toY);
11396
11397             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11398               case MT_NONE:
11399               case MT_CHECK:
11400                 break;
11401
11402               case MT_CHECKMATE:
11403               case MT_STAINMATE:
11404                 if (WhiteOnMove(currentMove)) {
11405                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
11406                 } else {
11407                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
11408                 }
11409                 break;
11410
11411               case MT_STALEMATE:
11412                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11413                 break;
11414             }
11415
11416             break;
11417
11418           case CMAIL_RESIGN:
11419             if (WhiteOnMove(currentMove)) {
11420                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11421             } else {
11422                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11423             }
11424             break;
11425
11426           case CMAIL_ACCEPT:
11427             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11428             break;
11429
11430           default:
11431             break;
11432         }
11433     }
11434
11435     return;
11436 }
11437
11438 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11439 int
11440 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11441 {
11442     int retVal;
11443
11444     if (gameNumber > nCmailGames) {
11445         DisplayError(_("No more games in this message"), 0);
11446         return FALSE;
11447     }
11448     if (f == lastLoadGameFP) {
11449         int offset = gameNumber - lastLoadGameNumber;
11450         if (offset == 0) {
11451             cmailMsg[0] = NULLCHAR;
11452             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11453                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11454                 nCmailMovesRegistered--;
11455             }
11456             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11457             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11458                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11459             }
11460         } else {
11461             if (! RegisterMove()) return FALSE;
11462         }
11463     }
11464
11465     retVal = LoadGame(f, gameNumber, title, useList);
11466
11467     /* Make move registered during previous look at this game, if any */
11468     MakeRegisteredMove();
11469
11470     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11471         commentList[currentMove]
11472           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11473         DisplayComment(currentMove - 1, commentList[currentMove]);
11474     }
11475
11476     return retVal;
11477 }
11478
11479 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11480 int
11481 ReloadGame (int offset)
11482 {
11483     int gameNumber = lastLoadGameNumber + offset;
11484     if (lastLoadGameFP == NULL) {
11485         DisplayError(_("No game has been loaded yet"), 0);
11486         return FALSE;
11487     }
11488     if (gameNumber <= 0) {
11489         DisplayError(_("Can't back up any further"), 0);
11490         return FALSE;
11491     }
11492     if (cmailMsgLoaded) {
11493         return CmailLoadGame(lastLoadGameFP, gameNumber,
11494                              lastLoadGameTitle, lastLoadGameUseList);
11495     } else {
11496         return LoadGame(lastLoadGameFP, gameNumber,
11497                         lastLoadGameTitle, lastLoadGameUseList);
11498     }
11499 }
11500
11501 int keys[EmptySquare+1];
11502
11503 int
11504 PositionMatches (Board b1, Board b2)
11505 {
11506     int r, f, sum=0;
11507     switch(appData.searchMode) {
11508         case 1: return CompareWithRights(b1, b2);
11509         case 2:
11510             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11511                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11512             }
11513             return TRUE;
11514         case 3:
11515             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11516               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11517                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11518             }
11519             return sum==0;
11520         case 4:
11521             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11522                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11523             }
11524             return sum==0;
11525     }
11526     return TRUE;
11527 }
11528
11529 #define Q_PROMO  4
11530 #define Q_EP     3
11531 #define Q_BCASTL 2
11532 #define Q_WCASTL 1
11533
11534 int pieceList[256], quickBoard[256];
11535 ChessSquare pieceType[256] = { EmptySquare };
11536 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11537 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11538 int soughtTotal, turn;
11539 Boolean epOK, flipSearch;
11540
11541 typedef struct {
11542     unsigned char piece, to;
11543 } Move;
11544
11545 #define DSIZE (250000)
11546
11547 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11548 Move *moveDatabase = initialSpace;
11549 unsigned int movePtr, dataSize = DSIZE;
11550
11551 int
11552 MakePieceList (Board board, int *counts)
11553 {
11554     int r, f, n=Q_PROMO, total=0;
11555     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11556     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11557         int sq = f + (r<<4);
11558         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
11559             quickBoard[sq] = ++n;
11560             pieceList[n] = sq;
11561             pieceType[n] = board[r][f];
11562             counts[board[r][f]]++;
11563             if(board[r][f] == WhiteKing) pieceList[1] = n; else
11564             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
11565             total++;
11566         }
11567     }
11568     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
11569     return total;
11570 }
11571
11572 void
11573 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
11574 {
11575     int sq = fromX + (fromY<<4);
11576     int piece = quickBoard[sq];
11577     quickBoard[sq] = 0;
11578     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
11579     if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11580         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
11581         moveDatabase[movePtr++].piece = Q_WCASTL;
11582         quickBoard[sq] = piece;
11583         piece = quickBoard[from]; quickBoard[from] = 0;
11584         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11585     } else
11586     if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11587         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
11588         moveDatabase[movePtr++].piece = Q_BCASTL;
11589         quickBoard[sq] = piece;
11590         piece = quickBoard[from]; quickBoard[from] = 0;
11591         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11592     } else
11593     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
11594         quickBoard[(fromY<<4)+toX] = 0;
11595         moveDatabase[movePtr].piece = Q_EP;
11596         moveDatabase[movePtr++].to = (fromY<<4)+toX;
11597         moveDatabase[movePtr].to = sq;
11598     } else
11599     if(promoPiece != pieceType[piece]) {
11600         moveDatabase[movePtr++].piece = Q_PROMO;
11601         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
11602     }
11603     moveDatabase[movePtr].piece = piece;
11604     quickBoard[sq] = piece;
11605     movePtr++;
11606 }
11607
11608 int
11609 PackGame (Board board)
11610 {
11611     Move *newSpace = NULL;
11612     moveDatabase[movePtr].piece = 0; // terminate previous game
11613     if(movePtr > dataSize) {
11614         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
11615         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
11616         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
11617         if(newSpace) {
11618             int i;
11619             Move *p = moveDatabase, *q = newSpace;
11620             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
11621             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
11622             moveDatabase = newSpace;
11623         } else { // calloc failed, we must be out of memory. Too bad...
11624             dataSize = 0; // prevent calloc events for all subsequent games
11625             return 0;     // and signal this one isn't cached
11626         }
11627     }
11628     movePtr++;
11629     MakePieceList(board, counts);
11630     return movePtr;
11631 }
11632
11633 int
11634 QuickCompare (Board board, int *minCounts, int *maxCounts)
11635 {   // compare according to search mode
11636     int r, f;
11637     switch(appData.searchMode)
11638     {
11639       case 1: // exact position match
11640         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
11641         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11642             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11643         }
11644         break;
11645       case 2: // can have extra material on empty squares
11646         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11647             if(board[r][f] == EmptySquare) continue;
11648             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11649         }
11650         break;
11651       case 3: // material with exact Pawn structure
11652         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11653             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
11654             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11655         } // fall through to material comparison
11656       case 4: // exact material
11657         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
11658         break;
11659       case 6: // material range with given imbalance
11660         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
11661         // fall through to range comparison
11662       case 5: // material range
11663         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
11664     }
11665     return TRUE;
11666 }
11667
11668 int
11669 QuickScan (Board board, Move *move)
11670 {   // reconstruct game,and compare all positions in it
11671     int cnt=0, stretch=0, total = MakePieceList(board, counts);
11672     do {
11673         int piece = move->piece;
11674         int to = move->to, from = pieceList[piece];
11675         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
11676           if(!piece) return -1;
11677           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
11678             piece = (++move)->piece;
11679             from = pieceList[piece];
11680             counts[pieceType[piece]]--;
11681             pieceType[piece] = (ChessSquare) move->to;
11682             counts[move->to]++;
11683           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
11684             counts[pieceType[quickBoard[to]]]--;
11685             quickBoard[to] = 0; total--;
11686             move++;
11687             continue;
11688           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
11689             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
11690             from  = pieceList[piece]; // so this must be King
11691             quickBoard[from] = 0;
11692             pieceList[piece] = to;
11693             from = pieceList[(++move)->piece]; // for FRC this has to be done here
11694             quickBoard[from] = 0; // rook
11695             quickBoard[to] = piece;
11696             to = move->to; piece = move->piece;
11697             goto aftercastle;
11698           }
11699         }
11700         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
11701         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
11702         quickBoard[from] = 0;
11703       aftercastle:
11704         quickBoard[to] = piece;
11705         pieceList[piece] = to;
11706         cnt++; turn ^= 3;
11707         if(QuickCompare(soughtBoard, minSought, maxSought) ||
11708            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
11709            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
11710                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
11711           ) {
11712             static int lastCounts[EmptySquare+1];
11713             int i;
11714             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
11715             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
11716         } else stretch = 0;
11717         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
11718         move++;
11719     } while(1);
11720 }
11721
11722 void
11723 InitSearch ()
11724 {
11725     int r, f;
11726     flipSearch = FALSE;
11727     CopyBoard(soughtBoard, boards[currentMove]);
11728     soughtTotal = MakePieceList(soughtBoard, maxSought);
11729     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
11730     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
11731     CopyBoard(reverseBoard, boards[currentMove]);
11732     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11733         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
11734         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
11735         reverseBoard[r][f] = piece;
11736     }
11737     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3; 
11738     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
11739     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
11740                  || (boards[currentMove][CASTLING][2] == NoRights || 
11741                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
11742                  && (boards[currentMove][CASTLING][5] == NoRights || 
11743                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
11744       ) {
11745         flipSearch = TRUE;
11746         CopyBoard(flipBoard, soughtBoard);
11747         CopyBoard(rotateBoard, reverseBoard);
11748         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11749             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
11750             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
11751         }
11752     }
11753     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
11754     if(appData.searchMode >= 5) {
11755         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
11756         MakePieceList(soughtBoard, minSought);
11757         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
11758     }
11759     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
11760         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
11761 }
11762
11763 GameInfo dummyInfo;
11764
11765 int
11766 GameContainsPosition (FILE *f, ListGame *lg)
11767 {
11768     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
11769     int fromX, fromY, toX, toY;
11770     char promoChar;
11771     static int initDone=FALSE;
11772
11773     // weed out games based on numerical tag comparison
11774     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
11775     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
11776     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
11777     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
11778     if(!initDone) {
11779         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
11780         initDone = TRUE;
11781     }
11782     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen);
11783     else CopyBoard(boards[scratch], initialPosition); // default start position
11784     if(lg->moves) {
11785         turn = btm + 1;
11786         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
11787         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
11788     }
11789     if(btm) plyNr++;
11790     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11791     fseek(f, lg->offset, 0);
11792     yynewfile(f);
11793     while(1) {
11794         yyboardindex = scratch;
11795         quickFlag = plyNr+1;
11796         next = Myylex();
11797         quickFlag = 0;
11798         switch(next) {
11799             case PGNTag:
11800                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
11801             default:
11802                 continue;
11803
11804             case XBoardGame:
11805             case GNUChessGame:
11806                 if(plyNr) return -1; // after we have seen moves, this is for new game
11807               continue;
11808
11809             case AmbiguousMove: // we cannot reconstruct the game beyond these two
11810             case ImpossibleMove:
11811             case WhiteWins: // game ends here with these four
11812             case BlackWins:
11813             case GameIsDrawn:
11814             case GameUnfinished:
11815                 return -1;
11816
11817             case IllegalMove:
11818                 if(appData.testLegality) return -1;
11819             case WhiteCapturesEnPassant:
11820             case BlackCapturesEnPassant:
11821             case WhitePromotion:
11822             case BlackPromotion:
11823             case WhiteNonPromotion:
11824             case BlackNonPromotion:
11825             case NormalMove:
11826             case WhiteKingSideCastle:
11827             case WhiteQueenSideCastle:
11828             case BlackKingSideCastle:
11829             case BlackQueenSideCastle:
11830             case WhiteKingSideCastleWild:
11831             case WhiteQueenSideCastleWild:
11832             case BlackKingSideCastleWild:
11833             case BlackQueenSideCastleWild:
11834             case WhiteHSideCastleFR:
11835             case WhiteASideCastleFR:
11836             case BlackHSideCastleFR:
11837             case BlackASideCastleFR:
11838                 fromX = currentMoveString[0] - AAA;
11839                 fromY = currentMoveString[1] - ONE;
11840                 toX = currentMoveString[2] - AAA;
11841                 toY = currentMoveString[3] - ONE;
11842                 promoChar = currentMoveString[4];
11843                 break;
11844             case WhiteDrop:
11845             case BlackDrop:
11846                 fromX = next == WhiteDrop ?
11847                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
11848                   (int) CharToPiece(ToLower(currentMoveString[0]));
11849                 fromY = DROP_RANK;
11850                 toX = currentMoveString[2] - AAA;
11851                 toY = currentMoveString[3] - ONE;
11852                 promoChar = 0;
11853                 break;
11854         }
11855         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
11856         plyNr++;
11857         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
11858         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11859         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
11860         if(appData.findMirror) {
11861             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
11862             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
11863         }
11864     }
11865 }
11866
11867 /* Load the nth game from open file f */
11868 int
11869 LoadGame (FILE *f, int gameNumber, char *title, int useList)
11870 {
11871     ChessMove cm;
11872     char buf[MSG_SIZ];
11873     int gn = gameNumber;
11874     ListGame *lg = NULL;
11875     int numPGNTags = 0;
11876     int err, pos = -1;
11877     GameMode oldGameMode;
11878     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
11879
11880     if (appData.debugMode)
11881         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
11882
11883     if (gameMode == Training )
11884         SetTrainingModeOff();
11885
11886     oldGameMode = gameMode;
11887     if (gameMode != BeginningOfGame) {
11888       Reset(FALSE, TRUE);
11889     }
11890
11891     gameFileFP = f;
11892     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
11893         fclose(lastLoadGameFP);
11894     }
11895
11896     if (useList) {
11897         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
11898
11899         if (lg) {
11900             fseek(f, lg->offset, 0);
11901             GameListHighlight(gameNumber);
11902             pos = lg->position;
11903             gn = 1;
11904         }
11905         else {
11906             if(gameMode == AnalyzeFile && appData.loadGameIndex == -1)
11907               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
11908             else
11909             DisplayError(_("Game number out of range"), 0);
11910             return FALSE;
11911         }
11912     } else {
11913         GameListDestroy();
11914         if (fseek(f, 0, 0) == -1) {
11915             if (f == lastLoadGameFP ?
11916                 gameNumber == lastLoadGameNumber + 1 :
11917                 gameNumber == 1) {
11918                 gn = 1;
11919             } else {
11920                 DisplayError(_("Can't seek on game file"), 0);
11921                 return FALSE;
11922             }
11923         }
11924     }
11925     lastLoadGameFP = f;
11926     lastLoadGameNumber = gameNumber;
11927     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
11928     lastLoadGameUseList = useList;
11929
11930     yynewfile(f);
11931
11932     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
11933       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
11934                 lg->gameInfo.black);
11935             DisplayTitle(buf);
11936     } else if (*title != NULLCHAR) {
11937         if (gameNumber > 1) {
11938           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
11939             DisplayTitle(buf);
11940         } else {
11941             DisplayTitle(title);
11942         }
11943     }
11944
11945     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
11946         gameMode = PlayFromGameFile;
11947         ModeHighlight();
11948     }
11949
11950     currentMove = forwardMostMove = backwardMostMove = 0;
11951     CopyBoard(boards[0], initialPosition);
11952     StopClocks();
11953
11954     /*
11955      * Skip the first gn-1 games in the file.
11956      * Also skip over anything that precedes an identifiable
11957      * start of game marker, to avoid being confused by
11958      * garbage at the start of the file.  Currently
11959      * recognized start of game markers are the move number "1",
11960      * the pattern "gnuchess .* game", the pattern
11961      * "^[#;%] [^ ]* game file", and a PGN tag block.
11962      * A game that starts with one of the latter two patterns
11963      * will also have a move number 1, possibly
11964      * following a position diagram.
11965      * 5-4-02: Let's try being more lenient and allowing a game to
11966      * start with an unnumbered move.  Does that break anything?
11967      */
11968     cm = lastLoadGameStart = EndOfFile;
11969     while (gn > 0) {
11970         yyboardindex = forwardMostMove;
11971         cm = (ChessMove) Myylex();
11972         switch (cm) {
11973           case EndOfFile:
11974             if (cmailMsgLoaded) {
11975                 nCmailGames = CMAIL_MAX_GAMES - gn;
11976             } else {
11977                 Reset(TRUE, TRUE);
11978                 DisplayError(_("Game not found in file"), 0);
11979             }
11980             return FALSE;
11981
11982           case GNUChessGame:
11983           case XBoardGame:
11984             gn--;
11985             lastLoadGameStart = cm;
11986             break;
11987
11988           case MoveNumberOne:
11989             switch (lastLoadGameStart) {
11990               case GNUChessGame:
11991               case XBoardGame:
11992               case PGNTag:
11993                 break;
11994               case MoveNumberOne:
11995               case EndOfFile:
11996                 gn--;           /* count this game */
11997                 lastLoadGameStart = cm;
11998                 break;
11999               default:
12000                 /* impossible */
12001                 break;
12002             }
12003             break;
12004
12005           case PGNTag:
12006             switch (lastLoadGameStart) {
12007               case GNUChessGame:
12008               case PGNTag:
12009               case MoveNumberOne:
12010               case EndOfFile:
12011                 gn--;           /* count this game */
12012                 lastLoadGameStart = cm;
12013                 break;
12014               case XBoardGame:
12015                 lastLoadGameStart = cm; /* game counted already */
12016                 break;
12017               default:
12018                 /* impossible */
12019                 break;
12020             }
12021             if (gn > 0) {
12022                 do {
12023                     yyboardindex = forwardMostMove;
12024                     cm = (ChessMove) Myylex();
12025                 } while (cm == PGNTag || cm == Comment);
12026             }
12027             break;
12028
12029           case WhiteWins:
12030           case BlackWins:
12031           case GameIsDrawn:
12032             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
12033                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
12034                     != CMAIL_OLD_RESULT) {
12035                     nCmailResults ++ ;
12036                     cmailResult[  CMAIL_MAX_GAMES
12037                                 - gn - 1] = CMAIL_OLD_RESULT;
12038                 }
12039             }
12040             break;
12041
12042           case NormalMove:
12043             /* Only a NormalMove can be at the start of a game
12044              * without a position diagram. */
12045             if (lastLoadGameStart == EndOfFile ) {
12046               gn--;
12047               lastLoadGameStart = MoveNumberOne;
12048             }
12049             break;
12050
12051           default:
12052             break;
12053         }
12054     }
12055
12056     if (appData.debugMode)
12057       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
12058
12059     if (cm == XBoardGame) {
12060         /* Skip any header junk before position diagram and/or move 1 */
12061         for (;;) {
12062             yyboardindex = forwardMostMove;
12063             cm = (ChessMove) Myylex();
12064
12065             if (cm == EndOfFile ||
12066                 cm == GNUChessGame || cm == XBoardGame) {
12067                 /* Empty game; pretend end-of-file and handle later */
12068                 cm = EndOfFile;
12069                 break;
12070             }
12071
12072             if (cm == MoveNumberOne || cm == PositionDiagram ||
12073                 cm == PGNTag || cm == Comment)
12074               break;
12075         }
12076     } else if (cm == GNUChessGame) {
12077         if (gameInfo.event != NULL) {
12078             free(gameInfo.event);
12079         }
12080         gameInfo.event = StrSave(yy_text);
12081     }
12082
12083     startedFromSetupPosition = FALSE;
12084     while (cm == PGNTag) {
12085         if (appData.debugMode)
12086           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
12087         err = ParsePGNTag(yy_text, &gameInfo);
12088         if (!err) numPGNTags++;
12089
12090         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
12091         if(gameInfo.variant != oldVariant) {
12092             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
12093             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
12094             InitPosition(TRUE);
12095             oldVariant = gameInfo.variant;
12096             if (appData.debugMode)
12097               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
12098         }
12099
12100
12101         if (gameInfo.fen != NULL) {
12102           Board initial_position;
12103           startedFromSetupPosition = TRUE;
12104           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
12105             Reset(TRUE, TRUE);
12106             DisplayError(_("Bad FEN position in file"), 0);
12107             return FALSE;
12108           }
12109           CopyBoard(boards[0], initial_position);
12110           if (blackPlaysFirst) {
12111             currentMove = forwardMostMove = backwardMostMove = 1;
12112             CopyBoard(boards[1], initial_position);
12113             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12114             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12115             timeRemaining[0][1] = whiteTimeRemaining;
12116             timeRemaining[1][1] = blackTimeRemaining;
12117             if (commentList[0] != NULL) {
12118               commentList[1] = commentList[0];
12119               commentList[0] = NULL;
12120             }
12121           } else {
12122             currentMove = forwardMostMove = backwardMostMove = 0;
12123           }
12124           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12125           {   int i;
12126               initialRulePlies = FENrulePlies;
12127               for( i=0; i< nrCastlingRights; i++ )
12128                   initialRights[i] = initial_position[CASTLING][i];
12129           }
12130           yyboardindex = forwardMostMove;
12131           free(gameInfo.fen);
12132           gameInfo.fen = NULL;
12133         }
12134
12135         yyboardindex = forwardMostMove;
12136         cm = (ChessMove) Myylex();
12137
12138         /* Handle comments interspersed among the tags */
12139         while (cm == Comment) {
12140             char *p;
12141             if (appData.debugMode)
12142               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12143             p = yy_text;
12144             AppendComment(currentMove, p, FALSE);
12145             yyboardindex = forwardMostMove;
12146             cm = (ChessMove) Myylex();
12147         }
12148     }
12149
12150     /* don't rely on existence of Event tag since if game was
12151      * pasted from clipboard the Event tag may not exist
12152      */
12153     if (numPGNTags > 0){
12154         char *tags;
12155         if (gameInfo.variant == VariantNormal) {
12156           VariantClass v = StringToVariant(gameInfo.event);
12157           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12158           if(v < VariantShogi) gameInfo.variant = v;
12159         }
12160         if (!matchMode) {
12161           if( appData.autoDisplayTags ) {
12162             tags = PGNTags(&gameInfo);
12163             TagsPopUp(tags, CmailMsg());
12164             free(tags);
12165           }
12166         }
12167     } else {
12168         /* Make something up, but don't display it now */
12169         SetGameInfo();
12170         TagsPopDown();
12171     }
12172
12173     if (cm == PositionDiagram) {
12174         int i, j;
12175         char *p;
12176         Board initial_position;
12177
12178         if (appData.debugMode)
12179           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12180
12181         if (!startedFromSetupPosition) {
12182             p = yy_text;
12183             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12184               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12185                 switch (*p) {
12186                   case '{':
12187                   case '[':
12188                   case '-':
12189                   case ' ':
12190                   case '\t':
12191                   case '\n':
12192                   case '\r':
12193                     break;
12194                   default:
12195                     initial_position[i][j++] = CharToPiece(*p);
12196                     break;
12197                 }
12198             while (*p == ' ' || *p == '\t' ||
12199                    *p == '\n' || *p == '\r') p++;
12200
12201             if (strncmp(p, "black", strlen("black"))==0)
12202               blackPlaysFirst = TRUE;
12203             else
12204               blackPlaysFirst = FALSE;
12205             startedFromSetupPosition = TRUE;
12206
12207             CopyBoard(boards[0], initial_position);
12208             if (blackPlaysFirst) {
12209                 currentMove = forwardMostMove = backwardMostMove = 1;
12210                 CopyBoard(boards[1], initial_position);
12211                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12212                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12213                 timeRemaining[0][1] = whiteTimeRemaining;
12214                 timeRemaining[1][1] = blackTimeRemaining;
12215                 if (commentList[0] != NULL) {
12216                     commentList[1] = commentList[0];
12217                     commentList[0] = NULL;
12218                 }
12219             } else {
12220                 currentMove = forwardMostMove = backwardMostMove = 0;
12221             }
12222         }
12223         yyboardindex = forwardMostMove;
12224         cm = (ChessMove) Myylex();
12225     }
12226
12227     if (first.pr == NoProc) {
12228         StartChessProgram(&first);
12229     }
12230     InitChessProgram(&first, FALSE);
12231     SendToProgram("force\n", &first);
12232     if (startedFromSetupPosition) {
12233         SendBoard(&first, forwardMostMove);
12234     if (appData.debugMode) {
12235         fprintf(debugFP, "Load Game\n");
12236     }
12237         DisplayBothClocks();
12238     }
12239
12240     /* [HGM] server: flag to write setup moves in broadcast file as one */
12241     loadFlag = appData.suppressLoadMoves;
12242
12243     while (cm == Comment) {
12244         char *p;
12245         if (appData.debugMode)
12246           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12247         p = yy_text;
12248         AppendComment(currentMove, p, FALSE);
12249         yyboardindex = forwardMostMove;
12250         cm = (ChessMove) Myylex();
12251     }
12252
12253     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12254         cm == WhiteWins || cm == BlackWins ||
12255         cm == GameIsDrawn || cm == GameUnfinished) {
12256         DisplayMessage("", _("No moves in game"));
12257         if (cmailMsgLoaded) {
12258             if (appData.debugMode)
12259               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12260             ClearHighlights();
12261             flipView = FALSE;
12262         }
12263         DrawPosition(FALSE, boards[currentMove]);
12264         DisplayBothClocks();
12265         gameMode = EditGame;
12266         ModeHighlight();
12267         gameFileFP = NULL;
12268         cmailOldMove = 0;
12269         return TRUE;
12270     }
12271
12272     // [HGM] PV info: routine tests if comment empty
12273     if (!matchMode && (pausing || appData.timeDelay != 0)) {
12274         DisplayComment(currentMove - 1, commentList[currentMove]);
12275     }
12276     if (!matchMode && appData.timeDelay != 0)
12277       DrawPosition(FALSE, boards[currentMove]);
12278
12279     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12280       programStats.ok_to_send = 1;
12281     }
12282
12283     /* if the first token after the PGN tags is a move
12284      * and not move number 1, retrieve it from the parser
12285      */
12286     if (cm != MoveNumberOne)
12287         LoadGameOneMove(cm);
12288
12289     /* load the remaining moves from the file */
12290     while (LoadGameOneMove(EndOfFile)) {
12291       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12292       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12293     }
12294
12295     /* rewind to the start of the game */
12296     currentMove = backwardMostMove;
12297
12298     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12299
12300     if (oldGameMode == AnalyzeFile ||
12301         oldGameMode == AnalyzeMode) {
12302       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
12303       keepInfo = 1;
12304       AnalyzeFileEvent();
12305       keepInfo = 0;
12306     }
12307
12308     if (!matchMode && pos > 0) {
12309         ToNrEvent(pos); // [HGM] no autoplay if selected on position
12310     } else
12311     if (matchMode || appData.timeDelay == 0) {
12312       ToEndEvent();
12313     } else if (appData.timeDelay > 0) {
12314       AutoPlayGameLoop();
12315     }
12316
12317     if (appData.debugMode)
12318         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
12319
12320     loadFlag = 0; /* [HGM] true game starts */
12321     return TRUE;
12322 }
12323
12324 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
12325 int
12326 ReloadPosition (int offset)
12327 {
12328     int positionNumber = lastLoadPositionNumber + offset;
12329     if (lastLoadPositionFP == NULL) {
12330         DisplayError(_("No position has been loaded yet"), 0);
12331         return FALSE;
12332     }
12333     if (positionNumber <= 0) {
12334         DisplayError(_("Can't back up any further"), 0);
12335         return FALSE;
12336     }
12337     return LoadPosition(lastLoadPositionFP, positionNumber,
12338                         lastLoadPositionTitle);
12339 }
12340
12341 /* Load the nth position from the given file */
12342 int
12343 LoadPositionFromFile (char *filename, int n, char *title)
12344 {
12345     FILE *f;
12346     char buf[MSG_SIZ];
12347
12348     if (strcmp(filename, "-") == 0) {
12349         return LoadPosition(stdin, n, "stdin");
12350     } else {
12351         f = fopen(filename, "rb");
12352         if (f == NULL) {
12353             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12354             DisplayError(buf, errno);
12355             return FALSE;
12356         } else {
12357             return LoadPosition(f, n, title);
12358         }
12359     }
12360 }
12361
12362 /* Load the nth position from the given open file, and close it */
12363 int
12364 LoadPosition (FILE *f, int positionNumber, char *title)
12365 {
12366     char *p, line[MSG_SIZ];
12367     Board initial_position;
12368     int i, j, fenMode, pn;
12369
12370     if (gameMode == Training )
12371         SetTrainingModeOff();
12372
12373     if (gameMode != BeginningOfGame) {
12374         Reset(FALSE, TRUE);
12375     }
12376     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12377         fclose(lastLoadPositionFP);
12378     }
12379     if (positionNumber == 0) positionNumber = 1;
12380     lastLoadPositionFP = f;
12381     lastLoadPositionNumber = positionNumber;
12382     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12383     if (first.pr == NoProc && !appData.noChessProgram) {
12384       StartChessProgram(&first);
12385       InitChessProgram(&first, FALSE);
12386     }
12387     pn = positionNumber;
12388     if (positionNumber < 0) {
12389         /* Negative position number means to seek to that byte offset */
12390         if (fseek(f, -positionNumber, 0) == -1) {
12391             DisplayError(_("Can't seek on position file"), 0);
12392             return FALSE;
12393         };
12394         pn = 1;
12395     } else {
12396         if (fseek(f, 0, 0) == -1) {
12397             if (f == lastLoadPositionFP ?
12398                 positionNumber == lastLoadPositionNumber + 1 :
12399                 positionNumber == 1) {
12400                 pn = 1;
12401             } else {
12402                 DisplayError(_("Can't seek on position file"), 0);
12403                 return FALSE;
12404             }
12405         }
12406     }
12407     /* See if this file is FEN or old-style xboard */
12408     if (fgets(line, MSG_SIZ, f) == NULL) {
12409         DisplayError(_("Position not found in file"), 0);
12410         return FALSE;
12411     }
12412     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12413     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12414
12415     if (pn >= 2) {
12416         if (fenMode || line[0] == '#') pn--;
12417         while (pn > 0) {
12418             /* skip positions before number pn */
12419             if (fgets(line, MSG_SIZ, f) == NULL) {
12420                 Reset(TRUE, TRUE);
12421                 DisplayError(_("Position not found in file"), 0);
12422                 return FALSE;
12423             }
12424             if (fenMode || line[0] == '#') pn--;
12425         }
12426     }
12427
12428     if (fenMode) {
12429         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
12430             DisplayError(_("Bad FEN position in file"), 0);
12431             return FALSE;
12432         }
12433     } else {
12434         (void) fgets(line, MSG_SIZ, f);
12435         (void) fgets(line, MSG_SIZ, f);
12436
12437         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12438             (void) fgets(line, MSG_SIZ, f);
12439             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12440                 if (*p == ' ')
12441                   continue;
12442                 initial_position[i][j++] = CharToPiece(*p);
12443             }
12444         }
12445
12446         blackPlaysFirst = FALSE;
12447         if (!feof(f)) {
12448             (void) fgets(line, MSG_SIZ, f);
12449             if (strncmp(line, "black", strlen("black"))==0)
12450               blackPlaysFirst = TRUE;
12451         }
12452     }
12453     startedFromSetupPosition = TRUE;
12454
12455     CopyBoard(boards[0], initial_position);
12456     if (blackPlaysFirst) {
12457         currentMove = forwardMostMove = backwardMostMove = 1;
12458         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12459         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12460         CopyBoard(boards[1], initial_position);
12461         DisplayMessage("", _("Black to play"));
12462     } else {
12463         currentMove = forwardMostMove = backwardMostMove = 0;
12464         DisplayMessage("", _("White to play"));
12465     }
12466     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12467     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12468         SendToProgram("force\n", &first);
12469         SendBoard(&first, forwardMostMove);
12470     }
12471     if (appData.debugMode) {
12472 int i, j;
12473   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12474   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12475         fprintf(debugFP, "Load Position\n");
12476     }
12477
12478     if (positionNumber > 1) {
12479       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12480         DisplayTitle(line);
12481     } else {
12482         DisplayTitle(title);
12483     }
12484     gameMode = EditGame;
12485     ModeHighlight();
12486     ResetClocks();
12487     timeRemaining[0][1] = whiteTimeRemaining;
12488     timeRemaining[1][1] = blackTimeRemaining;
12489     DrawPosition(FALSE, boards[currentMove]);
12490
12491     return TRUE;
12492 }
12493
12494
12495 void
12496 CopyPlayerNameIntoFileName (char **dest, char *src)
12497 {
12498     while (*src != NULLCHAR && *src != ',') {
12499         if (*src == ' ') {
12500             *(*dest)++ = '_';
12501             src++;
12502         } else {
12503             *(*dest)++ = *src++;
12504         }
12505     }
12506 }
12507
12508 char *
12509 DefaultFileName (char *ext)
12510 {
12511     static char def[MSG_SIZ];
12512     char *p;
12513
12514     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12515         p = def;
12516         CopyPlayerNameIntoFileName(&p, gameInfo.white);
12517         *p++ = '-';
12518         CopyPlayerNameIntoFileName(&p, gameInfo.black);
12519         *p++ = '.';
12520         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12521     } else {
12522         def[0] = NULLCHAR;
12523     }
12524     return def;
12525 }
12526
12527 /* Save the current game to the given file */
12528 int
12529 SaveGameToFile (char *filename, int append)
12530 {
12531     FILE *f;
12532     char buf[MSG_SIZ];
12533     int result, i, t,tot=0;
12534
12535     if (strcmp(filename, "-") == 0) {
12536         return SaveGame(stdout, 0, NULL);
12537     } else {
12538         for(i=0; i<10; i++) { // upto 10 tries
12539              f = fopen(filename, append ? "a" : "w");
12540              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12541              if(f || errno != 13) break;
12542              DoSleep(t = 5 + random()%11); // wait 5-15 msec
12543              tot += t;
12544         }
12545         if (f == NULL) {
12546             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12547             DisplayError(buf, errno);
12548             return FALSE;
12549         } else {
12550             safeStrCpy(buf, lastMsg, MSG_SIZ);
12551             DisplayMessage(_("Waiting for access to save file"), "");
12552             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
12553             DisplayMessage(_("Saving game"), "");
12554             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
12555             result = SaveGame(f, 0, NULL);
12556             DisplayMessage(buf, "");
12557             return result;
12558         }
12559     }
12560 }
12561
12562 char *
12563 SavePart (char *str)
12564 {
12565     static char buf[MSG_SIZ];
12566     char *p;
12567
12568     p = strchr(str, ' ');
12569     if (p == NULL) return str;
12570     strncpy(buf, str, p - str);
12571     buf[p - str] = NULLCHAR;
12572     return buf;
12573 }
12574
12575 #define PGN_MAX_LINE 75
12576
12577 #define PGN_SIDE_WHITE  0
12578 #define PGN_SIDE_BLACK  1
12579
12580 static int
12581 FindFirstMoveOutOfBook (int side)
12582 {
12583     int result = -1;
12584
12585     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12586         int index = backwardMostMove;
12587         int has_book_hit = 0;
12588
12589         if( (index % 2) != side ) {
12590             index++;
12591         }
12592
12593         while( index < forwardMostMove ) {
12594             /* Check to see if engine is in book */
12595             int depth = pvInfoList[index].depth;
12596             int score = pvInfoList[index].score;
12597             int in_book = 0;
12598
12599             if( depth <= 2 ) {
12600                 in_book = 1;
12601             }
12602             else if( score == 0 && depth == 63 ) {
12603                 in_book = 1; /* Zappa */
12604             }
12605             else if( score == 2 && depth == 99 ) {
12606                 in_book = 1; /* Abrok */
12607             }
12608
12609             has_book_hit += in_book;
12610
12611             if( ! in_book ) {
12612                 result = index;
12613
12614                 break;
12615             }
12616
12617             index += 2;
12618         }
12619     }
12620
12621     return result;
12622 }
12623
12624 void
12625 GetOutOfBookInfo (char * buf)
12626 {
12627     int oob[2];
12628     int i;
12629     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12630
12631     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12632     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12633
12634     *buf = '\0';
12635
12636     if( oob[0] >= 0 || oob[1] >= 0 ) {
12637         for( i=0; i<2; i++ ) {
12638             int idx = oob[i];
12639
12640             if( idx >= 0 ) {
12641                 if( i > 0 && oob[0] >= 0 ) {
12642                     strcat( buf, "   " );
12643                 }
12644
12645                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12646                 sprintf( buf+strlen(buf), "%s%.2f",
12647                     pvInfoList[idx].score >= 0 ? "+" : "",
12648                     pvInfoList[idx].score / 100.0 );
12649             }
12650         }
12651     }
12652 }
12653
12654 /* Save game in PGN style and close the file */
12655 int
12656 SaveGamePGN (FILE *f)
12657 {
12658     int i, offset, linelen, newblock;
12659 //    char *movetext;
12660     char numtext[32];
12661     int movelen, numlen, blank;
12662     char move_buffer[100]; /* [AS] Buffer for move+PV info */
12663
12664     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12665
12666     PrintPGNTags(f, &gameInfo);
12667
12668     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
12669
12670     if (backwardMostMove > 0 || startedFromSetupPosition) {
12671         char *fen = PositionToFEN(backwardMostMove, NULL);
12672         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12673         fprintf(f, "\n{--------------\n");
12674         PrintPosition(f, backwardMostMove);
12675         fprintf(f, "--------------}\n");
12676         free(fen);
12677     }
12678     else {
12679         /* [AS] Out of book annotation */
12680         if( appData.saveOutOfBookInfo ) {
12681             char buf[64];
12682
12683             GetOutOfBookInfo( buf );
12684
12685             if( buf[0] != '\0' ) {
12686                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12687             }
12688         }
12689
12690         fprintf(f, "\n");
12691     }
12692
12693     i = backwardMostMove;
12694     linelen = 0;
12695     newblock = TRUE;
12696
12697     while (i < forwardMostMove) {
12698         /* Print comments preceding this move */
12699         if (commentList[i] != NULL) {
12700             if (linelen > 0) fprintf(f, "\n");
12701             fprintf(f, "%s", commentList[i]);
12702             linelen = 0;
12703             newblock = TRUE;
12704         }
12705
12706         /* Format move number */
12707         if ((i % 2) == 0)
12708           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
12709         else
12710           if (newblock)
12711             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
12712           else
12713             numtext[0] = NULLCHAR;
12714
12715         numlen = strlen(numtext);
12716         newblock = FALSE;
12717
12718         /* Print move number */
12719         blank = linelen > 0 && numlen > 0;
12720         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
12721             fprintf(f, "\n");
12722             linelen = 0;
12723             blank = 0;
12724         }
12725         if (blank) {
12726             fprintf(f, " ");
12727             linelen++;
12728         }
12729         fprintf(f, "%s", numtext);
12730         linelen += numlen;
12731
12732         /* Get move */
12733         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
12734         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
12735
12736         /* Print move */
12737         blank = linelen > 0 && movelen > 0;
12738         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12739             fprintf(f, "\n");
12740             linelen = 0;
12741             blank = 0;
12742         }
12743         if (blank) {
12744             fprintf(f, " ");
12745             linelen++;
12746         }
12747         fprintf(f, "%s", move_buffer);
12748         linelen += movelen;
12749
12750         /* [AS] Add PV info if present */
12751         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
12752             /* [HGM] add time */
12753             char buf[MSG_SIZ]; int seconds;
12754
12755             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
12756
12757             if( seconds <= 0)
12758               buf[0] = 0;
12759             else
12760               if( seconds < 30 )
12761                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
12762               else
12763                 {
12764                   seconds = (seconds + 4)/10; // round to full seconds
12765                   if( seconds < 60 )
12766                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
12767                   else
12768                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
12769                 }
12770
12771             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
12772                       pvInfoList[i].score >= 0 ? "+" : "",
12773                       pvInfoList[i].score / 100.0,
12774                       pvInfoList[i].depth,
12775                       buf );
12776
12777             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
12778
12779             /* Print score/depth */
12780             blank = linelen > 0 && movelen > 0;
12781             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12782                 fprintf(f, "\n");
12783                 linelen = 0;
12784                 blank = 0;
12785             }
12786             if (blank) {
12787                 fprintf(f, " ");
12788                 linelen++;
12789             }
12790             fprintf(f, "%s", move_buffer);
12791             linelen += movelen;
12792         }
12793
12794         i++;
12795     }
12796
12797     /* Start a new line */
12798     if (linelen > 0) fprintf(f, "\n");
12799
12800     /* Print comments after last move */
12801     if (commentList[i] != NULL) {
12802         fprintf(f, "%s\n", commentList[i]);
12803     }
12804
12805     /* Print result */
12806     if (gameInfo.resultDetails != NULL &&
12807         gameInfo.resultDetails[0] != NULLCHAR) {
12808         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
12809                 PGNResult(gameInfo.result));
12810     } else {
12811         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12812     }
12813
12814     fclose(f);
12815     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12816     return TRUE;
12817 }
12818
12819 /* Save game in old style and close the file */
12820 int
12821 SaveGameOldStyle (FILE *f)
12822 {
12823     int i, offset;
12824     time_t tm;
12825
12826     tm = time((time_t *) NULL);
12827
12828     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
12829     PrintOpponents(f);
12830
12831     if (backwardMostMove > 0 || startedFromSetupPosition) {
12832         fprintf(f, "\n[--------------\n");
12833         PrintPosition(f, backwardMostMove);
12834         fprintf(f, "--------------]\n");
12835     } else {
12836         fprintf(f, "\n");
12837     }
12838
12839     i = backwardMostMove;
12840     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12841
12842     while (i < forwardMostMove) {
12843         if (commentList[i] != NULL) {
12844             fprintf(f, "[%s]\n", commentList[i]);
12845         }
12846
12847         if ((i % 2) == 1) {
12848             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
12849             i++;
12850         } else {
12851             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
12852             i++;
12853             if (commentList[i] != NULL) {
12854                 fprintf(f, "\n");
12855                 continue;
12856             }
12857             if (i >= forwardMostMove) {
12858                 fprintf(f, "\n");
12859                 break;
12860             }
12861             fprintf(f, "%s\n", parseList[i]);
12862             i++;
12863         }
12864     }
12865
12866     if (commentList[i] != NULL) {
12867         fprintf(f, "[%s]\n", commentList[i]);
12868     }
12869
12870     /* This isn't really the old style, but it's close enough */
12871     if (gameInfo.resultDetails != NULL &&
12872         gameInfo.resultDetails[0] != NULLCHAR) {
12873         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
12874                 gameInfo.resultDetails);
12875     } else {
12876         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12877     }
12878
12879     fclose(f);
12880     return TRUE;
12881 }
12882
12883 /* Save the current game to open file f and close the file */
12884 int
12885 SaveGame (FILE *f, int dummy, char *dummy2)
12886 {
12887     if (gameMode == EditPosition) EditPositionDone(TRUE);
12888     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12889     if (appData.oldSaveStyle)
12890       return SaveGameOldStyle(f);
12891     else
12892       return SaveGamePGN(f);
12893 }
12894
12895 /* Save the current position to the given file */
12896 int
12897 SavePositionToFile (char *filename)
12898 {
12899     FILE *f;
12900     char buf[MSG_SIZ];
12901
12902     if (strcmp(filename, "-") == 0) {
12903         return SavePosition(stdout, 0, NULL);
12904     } else {
12905         f = fopen(filename, "a");
12906         if (f == NULL) {
12907             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12908             DisplayError(buf, errno);
12909             return FALSE;
12910         } else {
12911             safeStrCpy(buf, lastMsg, MSG_SIZ);
12912             DisplayMessage(_("Waiting for access to save file"), "");
12913             flock(fileno(f), LOCK_EX); // [HGM] lock
12914             DisplayMessage(_("Saving position"), "");
12915             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
12916             SavePosition(f, 0, NULL);
12917             DisplayMessage(buf, "");
12918             return TRUE;
12919         }
12920     }
12921 }
12922
12923 /* Save the current position to the given open file and close the file */
12924 int
12925 SavePosition (FILE *f, int dummy, char *dummy2)
12926 {
12927     time_t tm;
12928     char *fen;
12929
12930     if (gameMode == EditPosition) EditPositionDone(TRUE);
12931     if (appData.oldSaveStyle) {
12932         tm = time((time_t *) NULL);
12933
12934         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
12935         PrintOpponents(f);
12936         fprintf(f, "[--------------\n");
12937         PrintPosition(f, currentMove);
12938         fprintf(f, "--------------]\n");
12939     } else {
12940         fen = PositionToFEN(currentMove, NULL);
12941         fprintf(f, "%s\n", fen);
12942         free(fen);
12943     }
12944     fclose(f);
12945     return TRUE;
12946 }
12947
12948 void
12949 ReloadCmailMsgEvent (int unregister)
12950 {
12951 #if !WIN32
12952     static char *inFilename = NULL;
12953     static char *outFilename;
12954     int i;
12955     struct stat inbuf, outbuf;
12956     int status;
12957
12958     /* Any registered moves are unregistered if unregister is set, */
12959     /* i.e. invoked by the signal handler */
12960     if (unregister) {
12961         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12962             cmailMoveRegistered[i] = FALSE;
12963             if (cmailCommentList[i] != NULL) {
12964                 free(cmailCommentList[i]);
12965                 cmailCommentList[i] = NULL;
12966             }
12967         }
12968         nCmailMovesRegistered = 0;
12969     }
12970
12971     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12972         cmailResult[i] = CMAIL_NOT_RESULT;
12973     }
12974     nCmailResults = 0;
12975
12976     if (inFilename == NULL) {
12977         /* Because the filenames are static they only get malloced once  */
12978         /* and they never get freed                                      */
12979         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
12980         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
12981
12982         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
12983         sprintf(outFilename, "%s.out", appData.cmailGameName);
12984     }
12985
12986     status = stat(outFilename, &outbuf);
12987     if (status < 0) {
12988         cmailMailedMove = FALSE;
12989     } else {
12990         status = stat(inFilename, &inbuf);
12991         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
12992     }
12993
12994     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
12995        counts the games, notes how each one terminated, etc.
12996
12997        It would be nice to remove this kludge and instead gather all
12998        the information while building the game list.  (And to keep it
12999        in the game list nodes instead of having a bunch of fixed-size
13000        parallel arrays.)  Note this will require getting each game's
13001        termination from the PGN tags, as the game list builder does
13002        not process the game moves.  --mann
13003        */
13004     cmailMsgLoaded = TRUE;
13005     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
13006
13007     /* Load first game in the file or popup game menu */
13008     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
13009
13010 #endif /* !WIN32 */
13011     return;
13012 }
13013
13014 int
13015 RegisterMove ()
13016 {
13017     FILE *f;
13018     char string[MSG_SIZ];
13019
13020     if (   cmailMailedMove
13021         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
13022         return TRUE;            /* Allow free viewing  */
13023     }
13024
13025     /* Unregister move to ensure that we don't leave RegisterMove        */
13026     /* with the move registered when the conditions for registering no   */
13027     /* longer hold                                                       */
13028     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
13029         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
13030         nCmailMovesRegistered --;
13031
13032         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
13033           {
13034               free(cmailCommentList[lastLoadGameNumber - 1]);
13035               cmailCommentList[lastLoadGameNumber - 1] = NULL;
13036           }
13037     }
13038
13039     if (cmailOldMove == -1) {
13040         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
13041         return FALSE;
13042     }
13043
13044     if (currentMove > cmailOldMove + 1) {
13045         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
13046         return FALSE;
13047     }
13048
13049     if (currentMove < cmailOldMove) {
13050         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
13051         return FALSE;
13052     }
13053
13054     if (forwardMostMove > currentMove) {
13055         /* Silently truncate extra moves */
13056         TruncateGame();
13057     }
13058
13059     if (   (currentMove == cmailOldMove + 1)
13060         || (   (currentMove == cmailOldMove)
13061             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
13062                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
13063         if (gameInfo.result != GameUnfinished) {
13064             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
13065         }
13066
13067         if (commentList[currentMove] != NULL) {
13068             cmailCommentList[lastLoadGameNumber - 1]
13069               = StrSave(commentList[currentMove]);
13070         }
13071         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
13072
13073         if (appData.debugMode)
13074           fprintf(debugFP, "Saving %s for game %d\n",
13075                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
13076
13077         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
13078
13079         f = fopen(string, "w");
13080         if (appData.oldSaveStyle) {
13081             SaveGameOldStyle(f); /* also closes the file */
13082
13083             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
13084             f = fopen(string, "w");
13085             SavePosition(f, 0, NULL); /* also closes the file */
13086         } else {
13087             fprintf(f, "{--------------\n");
13088             PrintPosition(f, currentMove);
13089             fprintf(f, "--------------}\n\n");
13090
13091             SaveGame(f, 0, NULL); /* also closes the file*/
13092         }
13093
13094         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
13095         nCmailMovesRegistered ++;
13096     } else if (nCmailGames == 1) {
13097         DisplayError(_("You have not made a move yet"), 0);
13098         return FALSE;
13099     }
13100
13101     return TRUE;
13102 }
13103
13104 void
13105 MailMoveEvent ()
13106 {
13107 #if !WIN32
13108     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
13109     FILE *commandOutput;
13110     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
13111     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
13112     int nBuffers;
13113     int i;
13114     int archived;
13115     char *arcDir;
13116
13117     if (! cmailMsgLoaded) {
13118         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
13119         return;
13120     }
13121
13122     if (nCmailGames == nCmailResults) {
13123         DisplayError(_("No unfinished games"), 0);
13124         return;
13125     }
13126
13127 #if CMAIL_PROHIBIT_REMAIL
13128     if (cmailMailedMove) {
13129       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);
13130         DisplayError(msg, 0);
13131         return;
13132     }
13133 #endif
13134
13135     if (! (cmailMailedMove || RegisterMove())) return;
13136
13137     if (   cmailMailedMove
13138         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13139       snprintf(string, MSG_SIZ, partCommandString,
13140                appData.debugMode ? " -v" : "", appData.cmailGameName);
13141         commandOutput = popen(string, "r");
13142
13143         if (commandOutput == NULL) {
13144             DisplayError(_("Failed to invoke cmail"), 0);
13145         } else {
13146             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13147                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13148             }
13149             if (nBuffers > 1) {
13150                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13151                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13152                 nBytes = MSG_SIZ - 1;
13153             } else {
13154                 (void) memcpy(msg, buffer, nBytes);
13155             }
13156             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13157
13158             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13159                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
13160
13161                 archived = TRUE;
13162                 for (i = 0; i < nCmailGames; i ++) {
13163                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
13164                         archived = FALSE;
13165                     }
13166                 }
13167                 if (   archived
13168                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13169                         != NULL)) {
13170                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13171                            arcDir,
13172                            appData.cmailGameName,
13173                            gameInfo.date);
13174                     LoadGameFromFile(buffer, 1, buffer, FALSE);
13175                     cmailMsgLoaded = FALSE;
13176                 }
13177             }
13178
13179             DisplayInformation(msg);
13180             pclose(commandOutput);
13181         }
13182     } else {
13183         if ((*cmailMsg) != '\0') {
13184             DisplayInformation(cmailMsg);
13185         }
13186     }
13187
13188     return;
13189 #endif /* !WIN32 */
13190 }
13191
13192 char *
13193 CmailMsg ()
13194 {
13195 #if WIN32
13196     return NULL;
13197 #else
13198     int  prependComma = 0;
13199     char number[5];
13200     char string[MSG_SIZ];       /* Space for game-list */
13201     int  i;
13202
13203     if (!cmailMsgLoaded) return "";
13204
13205     if (cmailMailedMove) {
13206       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13207     } else {
13208         /* Create a list of games left */
13209       snprintf(string, MSG_SIZ, "[");
13210         for (i = 0; i < nCmailGames; i ++) {
13211             if (! (   cmailMoveRegistered[i]
13212                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13213                 if (prependComma) {
13214                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13215                 } else {
13216                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13217                     prependComma = 1;
13218                 }
13219
13220                 strcat(string, number);
13221             }
13222         }
13223         strcat(string, "]");
13224
13225         if (nCmailMovesRegistered + nCmailResults == 0) {
13226             switch (nCmailGames) {
13227               case 1:
13228                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13229                 break;
13230
13231               case 2:
13232                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13233                 break;
13234
13235               default:
13236                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13237                          nCmailGames);
13238                 break;
13239             }
13240         } else {
13241             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13242               case 1:
13243                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13244                          string);
13245                 break;
13246
13247               case 0:
13248                 if (nCmailResults == nCmailGames) {
13249                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13250                 } else {
13251                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13252                 }
13253                 break;
13254
13255               default:
13256                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13257                          string);
13258             }
13259         }
13260     }
13261     return cmailMsg;
13262 #endif /* WIN32 */
13263 }
13264
13265 void
13266 ResetGameEvent ()
13267 {
13268     if (gameMode == Training)
13269       SetTrainingModeOff();
13270
13271     Reset(TRUE, TRUE);
13272     cmailMsgLoaded = FALSE;
13273     if (appData.icsActive) {
13274       SendToICS(ics_prefix);
13275       SendToICS("refresh\n");
13276     }
13277 }
13278
13279 void
13280 ExitEvent (int status)
13281 {
13282     exiting++;
13283     if (exiting > 2) {
13284       /* Give up on clean exit */
13285       exit(status);
13286     }
13287     if (exiting > 1) {
13288       /* Keep trying for clean exit */
13289       return;
13290     }
13291
13292     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
13293
13294     if (telnetISR != NULL) {
13295       RemoveInputSource(telnetISR);
13296     }
13297     if (icsPR != NoProc) {
13298       DestroyChildProcess(icsPR, TRUE);
13299     }
13300
13301     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
13302     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
13303
13304     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
13305     /* make sure this other one finishes before killing it!                  */
13306     if(endingGame) { int count = 0;
13307         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
13308         while(endingGame && count++ < 10) DoSleep(1);
13309         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
13310     }
13311
13312     /* Kill off chess programs */
13313     if (first.pr != NoProc) {
13314         ExitAnalyzeMode();
13315
13316         DoSleep( appData.delayBeforeQuit );
13317         SendToProgram("quit\n", &first);
13318         DoSleep( appData.delayAfterQuit );
13319         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
13320     }
13321     if (second.pr != NoProc) {
13322         DoSleep( appData.delayBeforeQuit );
13323         SendToProgram("quit\n", &second);
13324         DoSleep( appData.delayAfterQuit );
13325         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
13326     }
13327     if (first.isr != NULL) {
13328         RemoveInputSource(first.isr);
13329     }
13330     if (second.isr != NULL) {
13331         RemoveInputSource(second.isr);
13332     }
13333
13334     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13335     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13336
13337     ShutDownFrontEnd();
13338     exit(status);
13339 }
13340
13341 void
13342 PauseEvent ()
13343 {
13344     if (appData.debugMode)
13345         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13346     if (pausing) {
13347         pausing = FALSE;
13348         ModeHighlight();
13349         if (gameMode == MachinePlaysWhite ||
13350             gameMode == MachinePlaysBlack) {
13351             StartClocks();
13352         } else {
13353             DisplayBothClocks();
13354         }
13355         if (gameMode == PlayFromGameFile) {
13356             if (appData.timeDelay >= 0)
13357                 AutoPlayGameLoop();
13358         } else if (gameMode == IcsExamining && pauseExamInvalid) {
13359             Reset(FALSE, TRUE);
13360             SendToICS(ics_prefix);
13361             SendToICS("refresh\n");
13362         } else if (currentMove < forwardMostMove) {
13363             ForwardInner(forwardMostMove);
13364         }
13365         pauseExamInvalid = FALSE;
13366     } else {
13367         switch (gameMode) {
13368           default:
13369             return;
13370           case IcsExamining:
13371             pauseExamForwardMostMove = forwardMostMove;
13372             pauseExamInvalid = FALSE;
13373             /* fall through */
13374           case IcsObserving:
13375           case IcsPlayingWhite:
13376           case IcsPlayingBlack:
13377             pausing = TRUE;
13378             ModeHighlight();
13379             return;
13380           case PlayFromGameFile:
13381             (void) StopLoadGameTimer();
13382             pausing = TRUE;
13383             ModeHighlight();
13384             break;
13385           case BeginningOfGame:
13386             if (appData.icsActive) return;
13387             /* else fall through */
13388           case MachinePlaysWhite:
13389           case MachinePlaysBlack:
13390           case TwoMachinesPlay:
13391             if (forwardMostMove == 0)
13392               return;           /* don't pause if no one has moved */
13393             if ((gameMode == MachinePlaysWhite &&
13394                  !WhiteOnMove(forwardMostMove)) ||
13395                 (gameMode == MachinePlaysBlack &&
13396                  WhiteOnMove(forwardMostMove))) {
13397                 StopClocks();
13398             }
13399           case AnalyzeMode:
13400             pausing = TRUE;
13401             ModeHighlight();
13402             break;
13403         }
13404     }
13405 }
13406
13407 void
13408 EditCommentEvent ()
13409 {
13410     char title[MSG_SIZ];
13411
13412     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13413       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13414     } else {
13415       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13416                WhiteOnMove(currentMove - 1) ? " " : ".. ",
13417                parseList[currentMove - 1]);
13418     }
13419
13420     EditCommentPopUp(currentMove, title, commentList[currentMove]);
13421 }
13422
13423
13424 void
13425 EditTagsEvent ()
13426 {
13427     char *tags = PGNTags(&gameInfo);
13428     bookUp = FALSE;
13429     EditTagsPopUp(tags, NULL);
13430     free(tags);
13431 }
13432
13433 void
13434 ToggleSecond ()
13435 {
13436   if(second.analyzing) {
13437     SendToProgram("exit\n", &second);
13438     second.analyzing = FALSE;
13439   } else {
13440     if (second.pr == NoProc) StartChessProgram(&second);
13441     InitChessProgram(&second, FALSE);
13442     FeedMovesToProgram(&second, currentMove);
13443
13444     SendToProgram("analyze\n", &second);
13445     second.analyzing = TRUE;
13446   }
13447 }
13448
13449 /* Toggle ShowThinking */
13450 void
13451 ToggleShowThinking()
13452 {
13453   appData.showThinking = !appData.showThinking;
13454   ShowThinkingEvent();
13455 }
13456
13457 int
13458 AnalyzeModeEvent ()
13459 {
13460     char buf[MSG_SIZ];
13461
13462     if (!first.analysisSupport) {
13463       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
13464       DisplayError(buf, 0);
13465       return 0;
13466     }
13467     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
13468     if (appData.icsActive) {
13469         if (gameMode != IcsObserving) {
13470           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
13471             DisplayError(buf, 0);
13472             /* secure check */
13473             if (appData.icsEngineAnalyze) {
13474                 if (appData.debugMode)
13475                     fprintf(debugFP, _("Found unexpected active ICS engine analyze \n"));
13476                 ExitAnalyzeMode();
13477                 ModeHighlight();
13478             }
13479             return 0;
13480         }
13481         /* if enable, user wants to disable icsEngineAnalyze */
13482         if (appData.icsEngineAnalyze) {
13483                 ExitAnalyzeMode();
13484                 ModeHighlight();
13485                 return 0;
13486         }
13487         appData.icsEngineAnalyze = TRUE;
13488         if (appData.debugMode)
13489             fprintf(debugFP, _("ICS engine analyze starting... \n"));
13490     }
13491
13492     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
13493     if (appData.noChessProgram || gameMode == AnalyzeMode)
13494       return 0;
13495
13496     if (gameMode != AnalyzeFile) {
13497         if (!appData.icsEngineAnalyze) {
13498                EditGameEvent();
13499                if (gameMode != EditGame) return 0;
13500         }
13501         if (!appData.showThinking) ToggleShowThinking();
13502         ResurrectChessProgram();
13503         SendToProgram("analyze\n", &first);
13504         first.analyzing = TRUE;
13505         /*first.maybeThinking = TRUE;*/
13506         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13507         EngineOutputPopUp();
13508     }
13509     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
13510     pausing = FALSE;
13511     ModeHighlight();
13512     SetGameInfo();
13513
13514     StartAnalysisClock();
13515     GetTimeMark(&lastNodeCountTime);
13516     lastNodeCount = 0;
13517     return 1;
13518 }
13519
13520 void
13521 AnalyzeFileEvent ()
13522 {
13523     if (appData.noChessProgram || gameMode == AnalyzeFile)
13524       return;
13525
13526     if (!first.analysisSupport) {
13527       char buf[MSG_SIZ];
13528       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
13529       DisplayError(buf, 0);
13530       return;
13531     }
13532
13533     if (gameMode != AnalyzeMode) {
13534         EditGameEvent();
13535         if (gameMode != EditGame) return;
13536         if (!appData.showThinking) ToggleShowThinking();
13537         ResurrectChessProgram();
13538         SendToProgram("analyze\n", &first);
13539         first.analyzing = TRUE;
13540         /*first.maybeThinking = TRUE;*/
13541         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13542         EngineOutputPopUp();
13543     }
13544     gameMode = AnalyzeFile;
13545     pausing = FALSE;
13546     ModeHighlight();
13547     SetGameInfo();
13548
13549     StartAnalysisClock();
13550     GetTimeMark(&lastNodeCountTime);
13551     lastNodeCount = 0;
13552     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
13553     AnalysisPeriodicEvent(1);
13554 }
13555
13556 void
13557 MachineWhiteEvent ()
13558 {
13559     char buf[MSG_SIZ];
13560     char *bookHit = NULL;
13561
13562     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
13563       return;
13564
13565
13566     if (gameMode == PlayFromGameFile ||
13567         gameMode == TwoMachinesPlay  ||
13568         gameMode == Training         ||
13569         gameMode == AnalyzeMode      ||
13570         gameMode == EndOfGame)
13571         EditGameEvent();
13572
13573     if (gameMode == EditPosition)
13574         EditPositionDone(TRUE);
13575
13576     if (!WhiteOnMove(currentMove)) {
13577         DisplayError(_("It is not White's turn"), 0);
13578         return;
13579     }
13580
13581     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13582       ExitAnalyzeMode();
13583
13584     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13585         gameMode == AnalyzeFile)
13586         TruncateGame();
13587
13588     ResurrectChessProgram();    /* in case it isn't running */
13589     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
13590         gameMode = MachinePlaysWhite;
13591         ResetClocks();
13592     } else
13593     gameMode = MachinePlaysWhite;
13594     pausing = FALSE;
13595     ModeHighlight();
13596     SetGameInfo();
13597     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13598     DisplayTitle(buf);
13599     if (first.sendName) {
13600       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
13601       SendToProgram(buf, &first);
13602     }
13603     if (first.sendTime) {
13604       if (first.useColors) {
13605         SendToProgram("black\n", &first); /*gnu kludge*/
13606       }
13607       SendTimeRemaining(&first, TRUE);
13608     }
13609     if (first.useColors) {
13610       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
13611     }
13612     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13613     SetMachineThinkingEnables();
13614     first.maybeThinking = TRUE;
13615     StartClocks();
13616     firstMove = FALSE;
13617
13618     if (appData.autoFlipView && !flipView) {
13619       flipView = !flipView;
13620       DrawPosition(FALSE, NULL);
13621       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13622     }
13623
13624     if(bookHit) { // [HGM] book: simulate book reply
13625         static char bookMove[MSG_SIZ]; // a bit generous?
13626
13627         programStats.nodes = programStats.depth = programStats.time =
13628         programStats.score = programStats.got_only_move = 0;
13629         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13630
13631         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13632         strcat(bookMove, bookHit);
13633         HandleMachineMove(bookMove, &first);
13634     }
13635 }
13636
13637 void
13638 MachineBlackEvent ()
13639 {
13640   char buf[MSG_SIZ];
13641   char *bookHit = NULL;
13642
13643     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
13644         return;
13645
13646
13647     if (gameMode == PlayFromGameFile ||
13648         gameMode == TwoMachinesPlay  ||
13649         gameMode == Training         ||
13650         gameMode == AnalyzeMode      ||
13651         gameMode == EndOfGame)
13652         EditGameEvent();
13653
13654     if (gameMode == EditPosition)
13655         EditPositionDone(TRUE);
13656
13657     if (WhiteOnMove(currentMove)) {
13658         DisplayError(_("It is not Black's turn"), 0);
13659         return;
13660     }
13661
13662     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13663       ExitAnalyzeMode();
13664
13665     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13666         gameMode == AnalyzeFile)
13667         TruncateGame();
13668
13669     ResurrectChessProgram();    /* in case it isn't running */
13670     gameMode = MachinePlaysBlack;
13671     pausing = FALSE;
13672     ModeHighlight();
13673     SetGameInfo();
13674     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13675     DisplayTitle(buf);
13676     if (first.sendName) {
13677       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
13678       SendToProgram(buf, &first);
13679     }
13680     if (first.sendTime) {
13681       if (first.useColors) {
13682         SendToProgram("white\n", &first); /*gnu kludge*/
13683       }
13684       SendTimeRemaining(&first, FALSE);
13685     }
13686     if (first.useColors) {
13687       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
13688     }
13689     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13690     SetMachineThinkingEnables();
13691     first.maybeThinking = TRUE;
13692     StartClocks();
13693
13694     if (appData.autoFlipView && flipView) {
13695       flipView = !flipView;
13696       DrawPosition(FALSE, NULL);
13697       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13698     }
13699     if(bookHit) { // [HGM] book: simulate book reply
13700         static char bookMove[MSG_SIZ]; // a bit generous?
13701
13702         programStats.nodes = programStats.depth = programStats.time =
13703         programStats.score = programStats.got_only_move = 0;
13704         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13705
13706         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13707         strcat(bookMove, bookHit);
13708         HandleMachineMove(bookMove, &first);
13709     }
13710 }
13711
13712
13713 void
13714 DisplayTwoMachinesTitle ()
13715 {
13716     char buf[MSG_SIZ];
13717     if (appData.matchGames > 0) {
13718         if(appData.tourneyFile[0]) {
13719           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
13720                    gameInfo.white, _("vs."), gameInfo.black,
13721                    nextGame+1, appData.matchGames+1,
13722                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
13723         } else 
13724         if (first.twoMachinesColor[0] == 'w') {
13725           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13726                    gameInfo.white, _("vs."),  gameInfo.black,
13727                    first.matchWins, second.matchWins,
13728                    matchGame - 1 - (first.matchWins + second.matchWins));
13729         } else {
13730           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13731                    gameInfo.white, _("vs."), gameInfo.black,
13732                    second.matchWins, first.matchWins,
13733                    matchGame - 1 - (first.matchWins + second.matchWins));
13734         }
13735     } else {
13736       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13737     }
13738     DisplayTitle(buf);
13739 }
13740
13741 void
13742 SettingsMenuIfReady ()
13743 {
13744   if (second.lastPing != second.lastPong) {
13745     DisplayMessage("", _("Waiting for second chess program"));
13746     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
13747     return;
13748   }
13749   ThawUI();
13750   DisplayMessage("", "");
13751   SettingsPopUp(&second);
13752 }
13753
13754 int
13755 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
13756 {
13757     char buf[MSG_SIZ];
13758     if (cps->pr == NoProc) {
13759         StartChessProgram(cps);
13760         if (cps->protocolVersion == 1) {
13761           retry();
13762         } else {
13763           /* kludge: allow timeout for initial "feature" command */
13764           FreezeUI();
13765           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
13766           DisplayMessage("", buf);
13767           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
13768         }
13769         return 1;
13770     }
13771     return 0;
13772 }
13773
13774 void
13775 TwoMachinesEvent P((void))
13776 {
13777     int i;
13778     char buf[MSG_SIZ];
13779     ChessProgramState *onmove;
13780     char *bookHit = NULL;
13781     static int stalling = 0;
13782     TimeMark now;
13783     long wait;
13784
13785     if (appData.noChessProgram) return;
13786
13787     switch (gameMode) {
13788       case TwoMachinesPlay:
13789         return;
13790       case MachinePlaysWhite:
13791       case MachinePlaysBlack:
13792         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13793             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13794             return;
13795         }
13796         /* fall through */
13797       case BeginningOfGame:
13798       case PlayFromGameFile:
13799       case EndOfGame:
13800         EditGameEvent();
13801         if (gameMode != EditGame) return;
13802         break;
13803       case EditPosition:
13804         EditPositionDone(TRUE);
13805         break;
13806       case AnalyzeMode:
13807       case AnalyzeFile:
13808         ExitAnalyzeMode();
13809         break;
13810       case EditGame:
13811       default:
13812         break;
13813     }
13814
13815 //    forwardMostMove = currentMove;
13816     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
13817
13818     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
13819
13820     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
13821     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
13822       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13823       return;
13824     }
13825
13826     if(second.protocolVersion >= 2 && !strstr(second.variants, VariantName(gameInfo.variant))) {
13827         DisplayError("second engine does not play this", 0);
13828         return;
13829     }
13830
13831     if(!stalling) {
13832       InitChessProgram(&second, FALSE); // unbalances ping of second engine
13833       SendToProgram("force\n", &second);
13834       stalling = 1;
13835       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13836       return;
13837     }
13838     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
13839     if(appData.matchPause>10000 || appData.matchPause<10)
13840                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
13841     wait = SubtractTimeMarks(&now, &pauseStart);
13842     if(wait < appData.matchPause) {
13843         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
13844         return;
13845     }
13846     // we are now committed to starting the game
13847     stalling = 0;
13848     DisplayMessage("", "");
13849     if (startedFromSetupPosition) {
13850         SendBoard(&second, backwardMostMove);
13851     if (appData.debugMode) {
13852         fprintf(debugFP, "Two Machines\n");
13853     }
13854     }
13855     for (i = backwardMostMove; i < forwardMostMove; i++) {
13856         SendMoveToProgram(i, &second);
13857     }
13858
13859     gameMode = TwoMachinesPlay;
13860     pausing = FALSE;
13861     ModeHighlight(); // [HGM] logo: this triggers display update of logos
13862     SetGameInfo();
13863     DisplayTwoMachinesTitle();
13864     firstMove = TRUE;
13865     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
13866         onmove = &first;
13867     } else {
13868         onmove = &second;
13869     }
13870     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
13871     SendToProgram(first.computerString, &first);
13872     if (first.sendName) {
13873       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
13874       SendToProgram(buf, &first);
13875     }
13876     SendToProgram(second.computerString, &second);
13877     if (second.sendName) {
13878       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
13879       SendToProgram(buf, &second);
13880     }
13881
13882     ResetClocks();
13883     if (!first.sendTime || !second.sendTime) {
13884         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13885         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13886     }
13887     if (onmove->sendTime) {
13888       if (onmove->useColors) {
13889         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
13890       }
13891       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
13892     }
13893     if (onmove->useColors) {
13894       SendToProgram(onmove->twoMachinesColor, onmove);
13895     }
13896     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
13897 //    SendToProgram("go\n", onmove);
13898     onmove->maybeThinking = TRUE;
13899     SetMachineThinkingEnables();
13900
13901     StartClocks();
13902
13903     if(bookHit) { // [HGM] book: simulate book reply
13904         static char bookMove[MSG_SIZ]; // a bit generous?
13905
13906         programStats.nodes = programStats.depth = programStats.time =
13907         programStats.score = programStats.got_only_move = 0;
13908         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13909
13910         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13911         strcat(bookMove, bookHit);
13912         savedMessage = bookMove; // args for deferred call
13913         savedState = onmove;
13914         ScheduleDelayedEvent(DeferredBookMove, 1);
13915     }
13916 }
13917
13918 void
13919 TrainingEvent ()
13920 {
13921     if (gameMode == Training) {
13922       SetTrainingModeOff();
13923       gameMode = PlayFromGameFile;
13924       DisplayMessage("", _("Training mode off"));
13925     } else {
13926       gameMode = Training;
13927       animateTraining = appData.animate;
13928
13929       /* make sure we are not already at the end of the game */
13930       if (currentMove < forwardMostMove) {
13931         SetTrainingModeOn();
13932         DisplayMessage("", _("Training mode on"));
13933       } else {
13934         gameMode = PlayFromGameFile;
13935         DisplayError(_("Already at end of game"), 0);
13936       }
13937     }
13938     ModeHighlight();
13939 }
13940
13941 void
13942 IcsClientEvent ()
13943 {
13944     if (!appData.icsActive) return;
13945     switch (gameMode) {
13946       case IcsPlayingWhite:
13947       case IcsPlayingBlack:
13948       case IcsObserving:
13949       case IcsIdle:
13950       case BeginningOfGame:
13951       case IcsExamining:
13952         return;
13953
13954       case EditGame:
13955         break;
13956
13957       case EditPosition:
13958         EditPositionDone(TRUE);
13959         break;
13960
13961       case AnalyzeMode:
13962       case AnalyzeFile:
13963         ExitAnalyzeMode();
13964         break;
13965
13966       default:
13967         EditGameEvent();
13968         break;
13969     }
13970
13971     gameMode = IcsIdle;
13972     ModeHighlight();
13973     return;
13974 }
13975
13976 void
13977 EditGameEvent ()
13978 {
13979     int i;
13980
13981     switch (gameMode) {
13982       case Training:
13983         SetTrainingModeOff();
13984         break;
13985       case MachinePlaysWhite:
13986       case MachinePlaysBlack:
13987       case BeginningOfGame:
13988         SendToProgram("force\n", &first);
13989         SetUserThinkingEnables();
13990         break;
13991       case PlayFromGameFile:
13992         (void) StopLoadGameTimer();
13993         if (gameFileFP != NULL) {
13994             gameFileFP = NULL;
13995         }
13996         break;
13997       case EditPosition:
13998         EditPositionDone(TRUE);
13999         break;
14000       case AnalyzeMode:
14001       case AnalyzeFile:
14002         ExitAnalyzeMode();
14003         SendToProgram("force\n", &first);
14004         break;
14005       case TwoMachinesPlay:
14006         GameEnds(EndOfFile, NULL, GE_PLAYER);
14007         ResurrectChessProgram();
14008         SetUserThinkingEnables();
14009         break;
14010       case EndOfGame:
14011         ResurrectChessProgram();
14012         break;
14013       case IcsPlayingBlack:
14014       case IcsPlayingWhite:
14015         DisplayError(_("Warning: You are still playing a game"), 0);
14016         break;
14017       case IcsObserving:
14018         DisplayError(_("Warning: You are still observing a game"), 0);
14019         break;
14020       case IcsExamining:
14021         DisplayError(_("Warning: You are still examining a game"), 0);
14022         break;
14023       case IcsIdle:
14024         break;
14025       case EditGame:
14026       default:
14027         return;
14028     }
14029
14030     pausing = FALSE;
14031     StopClocks();
14032     first.offeredDraw = second.offeredDraw = 0;
14033
14034     if (gameMode == PlayFromGameFile) {
14035         whiteTimeRemaining = timeRemaining[0][currentMove];
14036         blackTimeRemaining = timeRemaining[1][currentMove];
14037         DisplayTitle("");
14038     }
14039
14040     if (gameMode == MachinePlaysWhite ||
14041         gameMode == MachinePlaysBlack ||
14042         gameMode == TwoMachinesPlay ||
14043         gameMode == EndOfGame) {
14044         i = forwardMostMove;
14045         while (i > currentMove) {
14046             SendToProgram("undo\n", &first);
14047             i--;
14048         }
14049         if(!adjustedClock) {
14050         whiteTimeRemaining = timeRemaining[0][currentMove];
14051         blackTimeRemaining = timeRemaining[1][currentMove];
14052         DisplayBothClocks();
14053         }
14054         if (whiteFlag || blackFlag) {
14055             whiteFlag = blackFlag = 0;
14056         }
14057         DisplayTitle("");
14058     }
14059
14060     gameMode = EditGame;
14061     ModeHighlight();
14062     SetGameInfo();
14063 }
14064
14065
14066 void
14067 EditPositionEvent ()
14068 {
14069     if (gameMode == EditPosition) {
14070         EditGameEvent();
14071         return;
14072     }
14073
14074     EditGameEvent();
14075     if (gameMode != EditGame) return;
14076
14077     gameMode = EditPosition;
14078     ModeHighlight();
14079     SetGameInfo();
14080     if (currentMove > 0)
14081       CopyBoard(boards[0], boards[currentMove]);
14082
14083     blackPlaysFirst = !WhiteOnMove(currentMove);
14084     ResetClocks();
14085     currentMove = forwardMostMove = backwardMostMove = 0;
14086     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14087     DisplayMove(-1);
14088     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
14089 }
14090
14091 void
14092 ExitAnalyzeMode ()
14093 {
14094     /* [DM] icsEngineAnalyze - possible call from other functions */
14095     if (appData.icsEngineAnalyze) {
14096         appData.icsEngineAnalyze = FALSE;
14097
14098         DisplayMessage("",_("Close ICS engine analyze..."));
14099     }
14100     if (first.analysisSupport && first.analyzing) {
14101       SendToBoth("exit\n");
14102       first.analyzing = second.analyzing = FALSE;
14103     }
14104     thinkOutput[0] = NULLCHAR;
14105 }
14106
14107 void
14108 EditPositionDone (Boolean fakeRights)
14109 {
14110     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
14111
14112     startedFromSetupPosition = TRUE;
14113     InitChessProgram(&first, FALSE);
14114     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
14115       boards[0][EP_STATUS] = EP_NONE;
14116       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
14117       if(boards[0][0][BOARD_WIDTH>>1] == king) {
14118         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
14119         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
14120       } else boards[0][CASTLING][2] = NoRights;
14121       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
14122         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
14123         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
14124       } else boards[0][CASTLING][5] = NoRights;
14125       if(gameInfo.variant == VariantSChess) {
14126         int i;
14127         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
14128           boards[0][VIRGIN][i] = 0;
14129           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
14130           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
14131         }
14132       }
14133     }
14134     SendToProgram("force\n", &first);
14135     if (blackPlaysFirst) {
14136         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
14137         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
14138         currentMove = forwardMostMove = backwardMostMove = 1;
14139         CopyBoard(boards[1], boards[0]);
14140     } else {
14141         currentMove = forwardMostMove = backwardMostMove = 0;
14142     }
14143     SendBoard(&first, forwardMostMove);
14144     if (appData.debugMode) {
14145         fprintf(debugFP, "EditPosDone\n");
14146     }
14147     DisplayTitle("");
14148     DisplayMessage("", "");
14149     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14150     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14151     gameMode = EditGame;
14152     ModeHighlight();
14153     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14154     ClearHighlights(); /* [AS] */
14155 }
14156
14157 /* Pause for `ms' milliseconds */
14158 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14159 void
14160 TimeDelay (long ms)
14161 {
14162     TimeMark m1, m2;
14163
14164     GetTimeMark(&m1);
14165     do {
14166         GetTimeMark(&m2);
14167     } while (SubtractTimeMarks(&m2, &m1) < ms);
14168 }
14169
14170 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14171 void
14172 SendMultiLineToICS (char *buf)
14173 {
14174     char temp[MSG_SIZ+1], *p;
14175     int len;
14176
14177     len = strlen(buf);
14178     if (len > MSG_SIZ)
14179       len = MSG_SIZ;
14180
14181     strncpy(temp, buf, len);
14182     temp[len] = 0;
14183
14184     p = temp;
14185     while (*p) {
14186         if (*p == '\n' || *p == '\r')
14187           *p = ' ';
14188         ++p;
14189     }
14190
14191     strcat(temp, "\n");
14192     SendToICS(temp);
14193     SendToPlayer(temp, strlen(temp));
14194 }
14195
14196 void
14197 SetWhiteToPlayEvent ()
14198 {
14199     if (gameMode == EditPosition) {
14200         blackPlaysFirst = FALSE;
14201         DisplayBothClocks();    /* works because currentMove is 0 */
14202     } else if (gameMode == IcsExamining) {
14203         SendToICS(ics_prefix);
14204         SendToICS("tomove white\n");
14205     }
14206 }
14207
14208 void
14209 SetBlackToPlayEvent ()
14210 {
14211     if (gameMode == EditPosition) {
14212         blackPlaysFirst = TRUE;
14213         currentMove = 1;        /* kludge */
14214         DisplayBothClocks();
14215         currentMove = 0;
14216     } else if (gameMode == IcsExamining) {
14217         SendToICS(ics_prefix);
14218         SendToICS("tomove black\n");
14219     }
14220 }
14221
14222 void
14223 EditPositionMenuEvent (ChessSquare selection, int x, int y)
14224 {
14225     char buf[MSG_SIZ];
14226     ChessSquare piece = boards[0][y][x];
14227
14228     if (gameMode != EditPosition && gameMode != IcsExamining) return;
14229
14230     switch (selection) {
14231       case ClearBoard:
14232         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
14233             SendToICS(ics_prefix);
14234             SendToICS("bsetup clear\n");
14235         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
14236             SendToICS(ics_prefix);
14237             SendToICS("clearboard\n");
14238         } else {
14239             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
14240                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
14241                 for (y = 0; y < BOARD_HEIGHT; y++) {
14242                     if (gameMode == IcsExamining) {
14243                         if (boards[currentMove][y][x] != EmptySquare) {
14244                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
14245                                     AAA + x, ONE + y);
14246                             SendToICS(buf);
14247                         }
14248                     } else {
14249                         boards[0][y][x] = p;
14250                     }
14251                 }
14252             }
14253         }
14254         if (gameMode == EditPosition) {
14255             DrawPosition(FALSE, boards[0]);
14256         }
14257         break;
14258
14259       case WhitePlay:
14260         SetWhiteToPlayEvent();
14261         break;
14262
14263       case BlackPlay:
14264         SetBlackToPlayEvent();
14265         break;
14266
14267       case EmptySquare:
14268         if (gameMode == IcsExamining) {
14269             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14270             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
14271             SendToICS(buf);
14272         } else {
14273             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14274                 if(x == BOARD_LEFT-2) {
14275                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
14276                     boards[0][y][1] = 0;
14277                 } else
14278                 if(x == BOARD_RGHT+1) {
14279                     if(y >= gameInfo.holdingsSize) break;
14280                     boards[0][y][BOARD_WIDTH-2] = 0;
14281                 } else break;
14282             }
14283             boards[0][y][x] = EmptySquare;
14284             DrawPosition(FALSE, boards[0]);
14285         }
14286         break;
14287
14288       case PromotePiece:
14289         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
14290            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
14291             selection = (ChessSquare) (PROMOTED piece);
14292         } else if(piece == EmptySquare) selection = WhiteSilver;
14293         else selection = (ChessSquare)((int)piece - 1);
14294         goto defaultlabel;
14295
14296       case DemotePiece:
14297         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
14298            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
14299             selection = (ChessSquare) (DEMOTED piece);
14300         } else if(piece == EmptySquare) selection = BlackSilver;
14301         else selection = (ChessSquare)((int)piece + 1);
14302         goto defaultlabel;
14303
14304       case WhiteQueen:
14305       case BlackQueen:
14306         if(gameInfo.variant == VariantShatranj ||
14307            gameInfo.variant == VariantXiangqi  ||
14308            gameInfo.variant == VariantCourier  ||
14309            gameInfo.variant == VariantMakruk     )
14310             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
14311         goto defaultlabel;
14312
14313       case WhiteKing:
14314       case BlackKing:
14315         if(gameInfo.variant == VariantXiangqi)
14316             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
14317         if(gameInfo.variant == VariantKnightmate)
14318             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
14319       default:
14320         defaultlabel:
14321         if (gameMode == IcsExamining) {
14322             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14323             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
14324                      PieceToChar(selection), AAA + x, ONE + y);
14325             SendToICS(buf);
14326         } else {
14327             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14328                 int n;
14329                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
14330                     n = PieceToNumber(selection - BlackPawn);
14331                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
14332                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
14333                     boards[0][BOARD_HEIGHT-1-n][1]++;
14334                 } else
14335                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
14336                     n = PieceToNumber(selection);
14337                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
14338                     boards[0][n][BOARD_WIDTH-1] = selection;
14339                     boards[0][n][BOARD_WIDTH-2]++;
14340                 }
14341             } else
14342             boards[0][y][x] = selection;
14343             DrawPosition(TRUE, boards[0]);
14344             ClearHighlights();
14345             fromX = fromY = -1;
14346         }
14347         break;
14348     }
14349 }
14350
14351
14352 void
14353 DropMenuEvent (ChessSquare selection, int x, int y)
14354 {
14355     ChessMove moveType;
14356
14357     switch (gameMode) {
14358       case IcsPlayingWhite:
14359       case MachinePlaysBlack:
14360         if (!WhiteOnMove(currentMove)) {
14361             DisplayMoveError(_("It is Black's turn"));
14362             return;
14363         }
14364         moveType = WhiteDrop;
14365         break;
14366       case IcsPlayingBlack:
14367       case MachinePlaysWhite:
14368         if (WhiteOnMove(currentMove)) {
14369             DisplayMoveError(_("It is White's turn"));
14370             return;
14371         }
14372         moveType = BlackDrop;
14373         break;
14374       case EditGame:
14375         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
14376         break;
14377       default:
14378         return;
14379     }
14380
14381     if (moveType == BlackDrop && selection < BlackPawn) {
14382       selection = (ChessSquare) ((int) selection
14383                                  + (int) BlackPawn - (int) WhitePawn);
14384     }
14385     if (boards[currentMove][y][x] != EmptySquare) {
14386         DisplayMoveError(_("That square is occupied"));
14387         return;
14388     }
14389
14390     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
14391 }
14392
14393 void
14394 AcceptEvent ()
14395 {
14396     /* Accept a pending offer of any kind from opponent */
14397
14398     if (appData.icsActive) {
14399         SendToICS(ics_prefix);
14400         SendToICS("accept\n");
14401     } else if (cmailMsgLoaded) {
14402         if (currentMove == cmailOldMove &&
14403             commentList[cmailOldMove] != NULL &&
14404             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14405                    "Black offers a draw" : "White offers a draw")) {
14406             TruncateGame();
14407             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14408             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14409         } else {
14410             DisplayError(_("There is no pending offer on this move"), 0);
14411             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14412         }
14413     } else {
14414         /* Not used for offers from chess program */
14415     }
14416 }
14417
14418 void
14419 DeclineEvent ()
14420 {
14421     /* Decline a pending offer of any kind from opponent */
14422
14423     if (appData.icsActive) {
14424         SendToICS(ics_prefix);
14425         SendToICS("decline\n");
14426     } else if (cmailMsgLoaded) {
14427         if (currentMove == cmailOldMove &&
14428             commentList[cmailOldMove] != NULL &&
14429             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14430                    "Black offers a draw" : "White offers a draw")) {
14431 #ifdef NOTDEF
14432             AppendComment(cmailOldMove, "Draw declined", TRUE);
14433             DisplayComment(cmailOldMove - 1, "Draw declined");
14434 #endif /*NOTDEF*/
14435         } else {
14436             DisplayError(_("There is no pending offer on this move"), 0);
14437         }
14438     } else {
14439         /* Not used for offers from chess program */
14440     }
14441 }
14442
14443 void
14444 RematchEvent ()
14445 {
14446     /* Issue ICS rematch command */
14447     if (appData.icsActive) {
14448         SendToICS(ics_prefix);
14449         SendToICS("rematch\n");
14450     }
14451 }
14452
14453 void
14454 CallFlagEvent ()
14455 {
14456     /* Call your opponent's flag (claim a win on time) */
14457     if (appData.icsActive) {
14458         SendToICS(ics_prefix);
14459         SendToICS("flag\n");
14460     } else {
14461         switch (gameMode) {
14462           default:
14463             return;
14464           case MachinePlaysWhite:
14465             if (whiteFlag) {
14466                 if (blackFlag)
14467                   GameEnds(GameIsDrawn, "Both players ran out of time",
14468                            GE_PLAYER);
14469                 else
14470                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
14471             } else {
14472                 DisplayError(_("Your opponent is not out of time"), 0);
14473             }
14474             break;
14475           case MachinePlaysBlack:
14476             if (blackFlag) {
14477                 if (whiteFlag)
14478                   GameEnds(GameIsDrawn, "Both players ran out of time",
14479                            GE_PLAYER);
14480                 else
14481                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
14482             } else {
14483                 DisplayError(_("Your opponent is not out of time"), 0);
14484             }
14485             break;
14486         }
14487     }
14488 }
14489
14490 void
14491 ClockClick (int which)
14492 {       // [HGM] code moved to back-end from winboard.c
14493         if(which) { // black clock
14494           if (gameMode == EditPosition || gameMode == IcsExamining) {
14495             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14496             SetBlackToPlayEvent();
14497           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
14498           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
14499           } else if (shiftKey) {
14500             AdjustClock(which, -1);
14501           } else if (gameMode == IcsPlayingWhite ||
14502                      gameMode == MachinePlaysBlack) {
14503             CallFlagEvent();
14504           }
14505         } else { // white clock
14506           if (gameMode == EditPosition || gameMode == IcsExamining) {
14507             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14508             SetWhiteToPlayEvent();
14509           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
14510           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
14511           } else if (shiftKey) {
14512             AdjustClock(which, -1);
14513           } else if (gameMode == IcsPlayingBlack ||
14514                    gameMode == MachinePlaysWhite) {
14515             CallFlagEvent();
14516           }
14517         }
14518 }
14519
14520 void
14521 DrawEvent ()
14522 {
14523     /* Offer draw or accept pending draw offer from opponent */
14524
14525     if (appData.icsActive) {
14526         /* Note: tournament rules require draw offers to be
14527            made after you make your move but before you punch
14528            your clock.  Currently ICS doesn't let you do that;
14529            instead, you immediately punch your clock after making
14530            a move, but you can offer a draw at any time. */
14531
14532         SendToICS(ics_prefix);
14533         SendToICS("draw\n");
14534         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
14535     } else if (cmailMsgLoaded) {
14536         if (currentMove == cmailOldMove &&
14537             commentList[cmailOldMove] != NULL &&
14538             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14539                    "Black offers a draw" : "White offers a draw")) {
14540             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14541             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14542         } else if (currentMove == cmailOldMove + 1) {
14543             char *offer = WhiteOnMove(cmailOldMove) ?
14544               "White offers a draw" : "Black offers a draw";
14545             AppendComment(currentMove, offer, TRUE);
14546             DisplayComment(currentMove - 1, offer);
14547             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
14548         } else {
14549             DisplayError(_("You must make your move before offering a draw"), 0);
14550             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14551         }
14552     } else if (first.offeredDraw) {
14553         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
14554     } else {
14555         if (first.sendDrawOffers) {
14556             SendToProgram("draw\n", &first);
14557             userOfferedDraw = TRUE;
14558         }
14559     }
14560 }
14561
14562 void
14563 AdjournEvent ()
14564 {
14565     /* Offer Adjourn or accept pending Adjourn offer from opponent */
14566
14567     if (appData.icsActive) {
14568         SendToICS(ics_prefix);
14569         SendToICS("adjourn\n");
14570     } else {
14571         /* Currently GNU Chess doesn't offer or accept Adjourns */
14572     }
14573 }
14574
14575
14576 void
14577 AbortEvent ()
14578 {
14579     /* Offer Abort or accept pending Abort offer from opponent */
14580
14581     if (appData.icsActive) {
14582         SendToICS(ics_prefix);
14583         SendToICS("abort\n");
14584     } else {
14585         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
14586     }
14587 }
14588
14589 void
14590 ResignEvent ()
14591 {
14592     /* Resign.  You can do this even if it's not your turn. */
14593
14594     if (appData.icsActive) {
14595         SendToICS(ics_prefix);
14596         SendToICS("resign\n");
14597     } else {
14598         switch (gameMode) {
14599           case MachinePlaysWhite:
14600             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14601             break;
14602           case MachinePlaysBlack:
14603             GameEnds(BlackWins, "White resigns", GE_PLAYER);
14604             break;
14605           case EditGame:
14606             if (cmailMsgLoaded) {
14607                 TruncateGame();
14608                 if (WhiteOnMove(cmailOldMove)) {
14609                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
14610                 } else {
14611                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14612                 }
14613                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
14614             }
14615             break;
14616           default:
14617             break;
14618         }
14619     }
14620 }
14621
14622
14623 void
14624 StopObservingEvent ()
14625 {
14626     /* Stop observing current games */
14627     SendToICS(ics_prefix);
14628     SendToICS("unobserve\n");
14629 }
14630
14631 void
14632 StopExaminingEvent ()
14633 {
14634     /* Stop observing current game */
14635     SendToICS(ics_prefix);
14636     SendToICS("unexamine\n");
14637 }
14638
14639 void
14640 ForwardInner (int target)
14641 {
14642     int limit; int oldSeekGraphUp = seekGraphUp;
14643
14644     if (appData.debugMode)
14645         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
14646                 target, currentMove, forwardMostMove);
14647
14648     if (gameMode == EditPosition)
14649       return;
14650
14651     seekGraphUp = FALSE;
14652     MarkTargetSquares(1);
14653
14654     if (gameMode == PlayFromGameFile && !pausing)
14655       PauseEvent();
14656
14657     if (gameMode == IcsExamining && pausing)
14658       limit = pauseExamForwardMostMove;
14659     else
14660       limit = forwardMostMove;
14661
14662     if (target > limit) target = limit;
14663
14664     if (target > 0 && moveList[target - 1][0]) {
14665         int fromX, fromY, toX, toY;
14666         toX = moveList[target - 1][2] - AAA;
14667         toY = moveList[target - 1][3] - ONE;
14668         if (moveList[target - 1][1] == '@') {
14669             if (appData.highlightLastMove) {
14670                 SetHighlights(-1, -1, toX, toY);
14671             }
14672         } else {
14673             fromX = moveList[target - 1][0] - AAA;
14674             fromY = moveList[target - 1][1] - ONE;
14675             if (target == currentMove + 1) {
14676                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
14677             }
14678             if (appData.highlightLastMove) {
14679                 SetHighlights(fromX, fromY, toX, toY);
14680             }
14681         }
14682     }
14683     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14684         gameMode == Training || gameMode == PlayFromGameFile ||
14685         gameMode == AnalyzeFile) {
14686         while (currentMove < target) {
14687             if(second.analyzing) SendMoveToProgram(currentMove, &second);
14688             SendMoveToProgram(currentMove++, &first);
14689         }
14690     } else {
14691         currentMove = target;
14692     }
14693
14694     if (gameMode == EditGame || gameMode == EndOfGame) {
14695         whiteTimeRemaining = timeRemaining[0][currentMove];
14696         blackTimeRemaining = timeRemaining[1][currentMove];
14697     }
14698     DisplayBothClocks();
14699     DisplayMove(currentMove - 1);
14700     DrawPosition(oldSeekGraphUp, boards[currentMove]);
14701     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14702     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
14703         DisplayComment(currentMove - 1, commentList[currentMove]);
14704     }
14705     ClearMap(); // [HGM] exclude: invalidate map
14706 }
14707
14708
14709 void
14710 ForwardEvent ()
14711 {
14712     if (gameMode == IcsExamining && !pausing) {
14713         SendToICS(ics_prefix);
14714         SendToICS("forward\n");
14715     } else {
14716         ForwardInner(currentMove + 1);
14717     }
14718 }
14719
14720 void
14721 ToEndEvent ()
14722 {
14723     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14724         /* to optimze, we temporarily turn off analysis mode while we feed
14725          * the remaining moves to the engine. Otherwise we get analysis output
14726          * after each move.
14727          */
14728         if (first.analysisSupport) {
14729           SendToProgram("exit\nforce\n", &first);
14730           first.analyzing = FALSE;
14731         }
14732     }
14733
14734     if (gameMode == IcsExamining && !pausing) {
14735         SendToICS(ics_prefix);
14736         SendToICS("forward 999999\n");
14737     } else {
14738         ForwardInner(forwardMostMove);
14739     }
14740
14741     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14742         /* we have fed all the moves, so reactivate analysis mode */
14743         SendToProgram("analyze\n", &first);
14744         first.analyzing = TRUE;
14745         /*first.maybeThinking = TRUE;*/
14746         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14747     }
14748 }
14749
14750 void
14751 BackwardInner (int target)
14752 {
14753     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
14754
14755     if (appData.debugMode)
14756         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
14757                 target, currentMove, forwardMostMove);
14758
14759     if (gameMode == EditPosition) return;
14760     seekGraphUp = FALSE;
14761     MarkTargetSquares(1);
14762     if (currentMove <= backwardMostMove) {
14763         ClearHighlights();
14764         DrawPosition(full_redraw, boards[currentMove]);
14765         return;
14766     }
14767     if (gameMode == PlayFromGameFile && !pausing)
14768       PauseEvent();
14769
14770     if (moveList[target][0]) {
14771         int fromX, fromY, toX, toY;
14772         toX = moveList[target][2] - AAA;
14773         toY = moveList[target][3] - ONE;
14774         if (moveList[target][1] == '@') {
14775             if (appData.highlightLastMove) {
14776                 SetHighlights(-1, -1, toX, toY);
14777             }
14778         } else {
14779             fromX = moveList[target][0] - AAA;
14780             fromY = moveList[target][1] - ONE;
14781             if (target == currentMove - 1) {
14782                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
14783             }
14784             if (appData.highlightLastMove) {
14785                 SetHighlights(fromX, fromY, toX, toY);
14786             }
14787         }
14788     }
14789     if (gameMode == EditGame || gameMode==AnalyzeMode ||
14790         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
14791         while (currentMove > target) {
14792             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
14793                 // null move cannot be undone. Reload program with move history before it.
14794                 int i;
14795                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
14796                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
14797                 }
14798                 SendBoard(&first, i); 
14799               if(second.analyzing) SendBoard(&second, i);
14800                 for(currentMove=i; currentMove<target; currentMove++) {
14801                     SendMoveToProgram(currentMove, &first);
14802                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
14803                 }
14804                 break;
14805             }
14806             SendToBoth("undo\n");
14807             currentMove--;
14808         }
14809     } else {
14810         currentMove = target;
14811     }
14812
14813     if (gameMode == EditGame || gameMode == EndOfGame) {
14814         whiteTimeRemaining = timeRemaining[0][currentMove];
14815         blackTimeRemaining = timeRemaining[1][currentMove];
14816     }
14817     DisplayBothClocks();
14818     DisplayMove(currentMove - 1);
14819     DrawPosition(full_redraw, boards[currentMove]);
14820     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14821     // [HGM] PV info: routine tests if comment empty
14822     DisplayComment(currentMove - 1, commentList[currentMove]);
14823     ClearMap(); // [HGM] exclude: invalidate map
14824 }
14825
14826 void
14827 BackwardEvent ()
14828 {
14829     if (gameMode == IcsExamining && !pausing) {
14830         SendToICS(ics_prefix);
14831         SendToICS("backward\n");
14832     } else {
14833         BackwardInner(currentMove - 1);
14834     }
14835 }
14836
14837 void
14838 ToStartEvent ()
14839 {
14840     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14841         /* to optimize, we temporarily turn off analysis mode while we undo
14842          * all the moves. Otherwise we get analysis output after each undo.
14843          */
14844         if (first.analysisSupport) {
14845           SendToProgram("exit\nforce\n", &first);
14846           first.analyzing = FALSE;
14847         }
14848     }
14849
14850     if (gameMode == IcsExamining && !pausing) {
14851         SendToICS(ics_prefix);
14852         SendToICS("backward 999999\n");
14853     } else {
14854         BackwardInner(backwardMostMove);
14855     }
14856
14857     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14858         /* we have fed all the moves, so reactivate analysis mode */
14859         SendToProgram("analyze\n", &first);
14860         first.analyzing = TRUE;
14861         /*first.maybeThinking = TRUE;*/
14862         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14863     }
14864 }
14865
14866 void
14867 ToNrEvent (int to)
14868 {
14869   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
14870   if (to >= forwardMostMove) to = forwardMostMove;
14871   if (to <= backwardMostMove) to = backwardMostMove;
14872   if (to < currentMove) {
14873     BackwardInner(to);
14874   } else {
14875     ForwardInner(to);
14876   }
14877 }
14878
14879 void
14880 RevertEvent (Boolean annotate)
14881 {
14882     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
14883         return;
14884     }
14885     if (gameMode != IcsExamining) {
14886         DisplayError(_("You are not examining a game"), 0);
14887         return;
14888     }
14889     if (pausing) {
14890         DisplayError(_("You can't revert while pausing"), 0);
14891         return;
14892     }
14893     SendToICS(ics_prefix);
14894     SendToICS("revert\n");
14895 }
14896
14897 void
14898 RetractMoveEvent ()
14899 {
14900     switch (gameMode) {
14901       case MachinePlaysWhite:
14902       case MachinePlaysBlack:
14903         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14904             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
14905             return;
14906         }
14907         if (forwardMostMove < 2) return;
14908         currentMove = forwardMostMove = forwardMostMove - 2;
14909         whiteTimeRemaining = timeRemaining[0][currentMove];
14910         blackTimeRemaining = timeRemaining[1][currentMove];
14911         DisplayBothClocks();
14912         DisplayMove(currentMove - 1);
14913         ClearHighlights();/*!! could figure this out*/
14914         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
14915         SendToProgram("remove\n", &first);
14916         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
14917         break;
14918
14919       case BeginningOfGame:
14920       default:
14921         break;
14922
14923       case IcsPlayingWhite:
14924       case IcsPlayingBlack:
14925         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
14926             SendToICS(ics_prefix);
14927             SendToICS("takeback 2\n");
14928         } else {
14929             SendToICS(ics_prefix);
14930             SendToICS("takeback 1\n");
14931         }
14932         break;
14933     }
14934 }
14935
14936 void
14937 MoveNowEvent ()
14938 {
14939     ChessProgramState *cps;
14940
14941     switch (gameMode) {
14942       case MachinePlaysWhite:
14943         if (!WhiteOnMove(forwardMostMove)) {
14944             DisplayError(_("It is your turn"), 0);
14945             return;
14946         }
14947         cps = &first;
14948         break;
14949       case MachinePlaysBlack:
14950         if (WhiteOnMove(forwardMostMove)) {
14951             DisplayError(_("It is your turn"), 0);
14952             return;
14953         }
14954         cps = &first;
14955         break;
14956       case TwoMachinesPlay:
14957         if (WhiteOnMove(forwardMostMove) ==
14958             (first.twoMachinesColor[0] == 'w')) {
14959             cps = &first;
14960         } else {
14961             cps = &second;
14962         }
14963         break;
14964       case BeginningOfGame:
14965       default:
14966         return;
14967     }
14968     SendToProgram("?\n", cps);
14969 }
14970
14971 void
14972 TruncateGameEvent ()
14973 {
14974     EditGameEvent();
14975     if (gameMode != EditGame) return;
14976     TruncateGame();
14977 }
14978
14979 void
14980 TruncateGame ()
14981 {
14982     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
14983     if (forwardMostMove > currentMove) {
14984         if (gameInfo.resultDetails != NULL) {
14985             free(gameInfo.resultDetails);
14986             gameInfo.resultDetails = NULL;
14987             gameInfo.result = GameUnfinished;
14988         }
14989         forwardMostMove = currentMove;
14990         HistorySet(parseList, backwardMostMove, forwardMostMove,
14991                    currentMove-1);
14992     }
14993 }
14994
14995 void
14996 HintEvent ()
14997 {
14998     if (appData.noChessProgram) return;
14999     switch (gameMode) {
15000       case MachinePlaysWhite:
15001         if (WhiteOnMove(forwardMostMove)) {
15002             DisplayError(_("Wait until your turn"), 0);
15003             return;
15004         }
15005         break;
15006       case BeginningOfGame:
15007       case MachinePlaysBlack:
15008         if (!WhiteOnMove(forwardMostMove)) {
15009             DisplayError(_("Wait until your turn"), 0);
15010             return;
15011         }
15012         break;
15013       default:
15014         DisplayError(_("No hint available"), 0);
15015         return;
15016     }
15017     SendToProgram("hint\n", &first);
15018     hintRequested = TRUE;
15019 }
15020
15021 void
15022 BookEvent ()
15023 {
15024     if (appData.noChessProgram) return;
15025     switch (gameMode) {
15026       case MachinePlaysWhite:
15027         if (WhiteOnMove(forwardMostMove)) {
15028             DisplayError(_("Wait until your turn"), 0);
15029             return;
15030         }
15031         break;
15032       case BeginningOfGame:
15033       case MachinePlaysBlack:
15034         if (!WhiteOnMove(forwardMostMove)) {
15035             DisplayError(_("Wait until your turn"), 0);
15036             return;
15037         }
15038         break;
15039       case EditPosition:
15040         EditPositionDone(TRUE);
15041         break;
15042       case TwoMachinesPlay:
15043         return;
15044       default:
15045         break;
15046     }
15047     SendToProgram("bk\n", &first);
15048     bookOutput[0] = NULLCHAR;
15049     bookRequested = TRUE;
15050 }
15051
15052 void
15053 AboutGameEvent ()
15054 {
15055     char *tags = PGNTags(&gameInfo);
15056     TagsPopUp(tags, CmailMsg());
15057     free(tags);
15058 }
15059
15060 /* end button procedures */
15061
15062 void
15063 PrintPosition (FILE *fp, int move)
15064 {
15065     int i, j;
15066
15067     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15068         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15069             char c = PieceToChar(boards[move][i][j]);
15070             fputc(c == 'x' ? '.' : c, fp);
15071             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
15072         }
15073     }
15074     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
15075       fprintf(fp, "white to play\n");
15076     else
15077       fprintf(fp, "black to play\n");
15078 }
15079
15080 void
15081 PrintOpponents (FILE *fp)
15082 {
15083     if (gameInfo.white != NULL) {
15084         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
15085     } else {
15086         fprintf(fp, "\n");
15087     }
15088 }
15089
15090 /* Find last component of program's own name, using some heuristics */
15091 void
15092 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
15093 {
15094     char *p, *q, c;
15095     int local = (strcmp(host, "localhost") == 0);
15096     while (!local && (p = strchr(prog, ';')) != NULL) {
15097         p++;
15098         while (*p == ' ') p++;
15099         prog = p;
15100     }
15101     if (*prog == '"' || *prog == '\'') {
15102         q = strchr(prog + 1, *prog);
15103     } else {
15104         q = strchr(prog, ' ');
15105     }
15106     if (q == NULL) q = prog + strlen(prog);
15107     p = q;
15108     while (p >= prog && *p != '/' && *p != '\\') p--;
15109     p++;
15110     if(p == prog && *p == '"') p++;
15111     c = *q; *q = 0;
15112     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
15113     memcpy(buf, p, q - p);
15114     buf[q - p] = NULLCHAR;
15115     if (!local) {
15116         strcat(buf, "@");
15117         strcat(buf, host);
15118     }
15119 }
15120
15121 char *
15122 TimeControlTagValue ()
15123 {
15124     char buf[MSG_SIZ];
15125     if (!appData.clockMode) {
15126       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
15127     } else if (movesPerSession > 0) {
15128       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
15129     } else if (timeIncrement == 0) {
15130       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
15131     } else {
15132       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
15133     }
15134     return StrSave(buf);
15135 }
15136
15137 void
15138 SetGameInfo ()
15139 {
15140     /* This routine is used only for certain modes */
15141     VariantClass v = gameInfo.variant;
15142     ChessMove r = GameUnfinished;
15143     char *p = NULL;
15144
15145     if(keepInfo) return;
15146
15147     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
15148         r = gameInfo.result;
15149         p = gameInfo.resultDetails;
15150         gameInfo.resultDetails = NULL;
15151     }
15152     ClearGameInfo(&gameInfo);
15153     gameInfo.variant = v;
15154
15155     switch (gameMode) {
15156       case MachinePlaysWhite:
15157         gameInfo.event = StrSave( appData.pgnEventHeader );
15158         gameInfo.site = StrSave(HostName());
15159         gameInfo.date = PGNDate();
15160         gameInfo.round = StrSave("-");
15161         gameInfo.white = StrSave(first.tidy);
15162         gameInfo.black = StrSave(UserName());
15163         gameInfo.timeControl = TimeControlTagValue();
15164         break;
15165
15166       case MachinePlaysBlack:
15167         gameInfo.event = StrSave( appData.pgnEventHeader );
15168         gameInfo.site = StrSave(HostName());
15169         gameInfo.date = PGNDate();
15170         gameInfo.round = StrSave("-");
15171         gameInfo.white = StrSave(UserName());
15172         gameInfo.black = StrSave(first.tidy);
15173         gameInfo.timeControl = TimeControlTagValue();
15174         break;
15175
15176       case TwoMachinesPlay:
15177         gameInfo.event = StrSave( appData.pgnEventHeader );
15178         gameInfo.site = StrSave(HostName());
15179         gameInfo.date = PGNDate();
15180         if (roundNr > 0) {
15181             char buf[MSG_SIZ];
15182             snprintf(buf, MSG_SIZ, "%d", roundNr);
15183             gameInfo.round = StrSave(buf);
15184         } else {
15185             gameInfo.round = StrSave("-");
15186         }
15187         if (first.twoMachinesColor[0] == 'w') {
15188             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15189             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15190         } else {
15191             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15192             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15193         }
15194         gameInfo.timeControl = TimeControlTagValue();
15195         break;
15196
15197       case EditGame:
15198         gameInfo.event = StrSave("Edited game");
15199         gameInfo.site = StrSave(HostName());
15200         gameInfo.date = PGNDate();
15201         gameInfo.round = StrSave("-");
15202         gameInfo.white = StrSave("-");
15203         gameInfo.black = StrSave("-");
15204         gameInfo.result = r;
15205         gameInfo.resultDetails = p;
15206         break;
15207
15208       case EditPosition:
15209         gameInfo.event = StrSave("Edited position");
15210         gameInfo.site = StrSave(HostName());
15211         gameInfo.date = PGNDate();
15212         gameInfo.round = StrSave("-");
15213         gameInfo.white = StrSave("-");
15214         gameInfo.black = StrSave("-");
15215         break;
15216
15217       case IcsPlayingWhite:
15218       case IcsPlayingBlack:
15219       case IcsObserving:
15220       case IcsExamining:
15221         break;
15222
15223       case PlayFromGameFile:
15224         gameInfo.event = StrSave("Game from non-PGN file");
15225         gameInfo.site = StrSave(HostName());
15226         gameInfo.date = PGNDate();
15227         gameInfo.round = StrSave("-");
15228         gameInfo.white = StrSave("?");
15229         gameInfo.black = StrSave("?");
15230         break;
15231
15232       default:
15233         break;
15234     }
15235 }
15236
15237 void
15238 ReplaceComment (int index, char *text)
15239 {
15240     int len;
15241     char *p;
15242     float score;
15243
15244     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
15245        pvInfoList[index-1].depth == len &&
15246        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
15247        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
15248     while (*text == '\n') text++;
15249     len = strlen(text);
15250     while (len > 0 && text[len - 1] == '\n') len--;
15251
15252     if (commentList[index] != NULL)
15253       free(commentList[index]);
15254
15255     if (len == 0) {
15256         commentList[index] = NULL;
15257         return;
15258     }
15259   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
15260       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
15261       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
15262     commentList[index] = (char *) malloc(len + 2);
15263     strncpy(commentList[index], text, len);
15264     commentList[index][len] = '\n';
15265     commentList[index][len + 1] = NULLCHAR;
15266   } else {
15267     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
15268     char *p;
15269     commentList[index] = (char *) malloc(len + 7);
15270     safeStrCpy(commentList[index], "{\n", 3);
15271     safeStrCpy(commentList[index]+2, text, len+1);
15272     commentList[index][len+2] = NULLCHAR;
15273     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
15274     strcat(commentList[index], "\n}\n");
15275   }
15276 }
15277
15278 void
15279 CrushCRs (char *text)
15280 {
15281   char *p = text;
15282   char *q = text;
15283   char ch;
15284
15285   do {
15286     ch = *p++;
15287     if (ch == '\r') continue;
15288     *q++ = ch;
15289   } while (ch != '\0');
15290 }
15291
15292 void
15293 AppendComment (int index, char *text, Boolean addBraces)
15294 /* addBraces  tells if we should add {} */
15295 {
15296     int oldlen, len;
15297     char *old;
15298
15299 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
15300     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
15301
15302     CrushCRs(text);
15303     while (*text == '\n') text++;
15304     len = strlen(text);
15305     while (len > 0 && text[len - 1] == '\n') len--;
15306     text[len] = NULLCHAR;
15307
15308     if (len == 0) return;
15309
15310     if (commentList[index] != NULL) {
15311       Boolean addClosingBrace = addBraces;
15312         old = commentList[index];
15313         oldlen = strlen(old);
15314         while(commentList[index][oldlen-1] ==  '\n')
15315           commentList[index][--oldlen] = NULLCHAR;
15316         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
15317         safeStrCpy(commentList[index], old, oldlen + len + 6);
15318         free(old);
15319         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
15320         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
15321           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
15322           while (*text == '\n') { text++; len--; }
15323           commentList[index][--oldlen] = NULLCHAR;
15324       }
15325         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
15326         else          strcat(commentList[index], "\n");
15327         strcat(commentList[index], text);
15328         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
15329         else          strcat(commentList[index], "\n");
15330     } else {
15331         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
15332         if(addBraces)
15333           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
15334         else commentList[index][0] = NULLCHAR;
15335         strcat(commentList[index], text);
15336         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
15337         if(addBraces == TRUE) strcat(commentList[index], "}\n");
15338     }
15339 }
15340
15341 static char *
15342 FindStr (char * text, char * sub_text)
15343 {
15344     char * result = strstr( text, sub_text );
15345
15346     if( result != NULL ) {
15347         result += strlen( sub_text );
15348     }
15349
15350     return result;
15351 }
15352
15353 /* [AS] Try to extract PV info from PGN comment */
15354 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
15355 char *
15356 GetInfoFromComment (int index, char * text)
15357 {
15358     char * sep = text, *p;
15359
15360     if( text != NULL && index > 0 ) {
15361         int score = 0;
15362         int depth = 0;
15363         int time = -1, sec = 0, deci;
15364         char * s_eval = FindStr( text, "[%eval " );
15365         char * s_emt = FindStr( text, "[%emt " );
15366
15367         if( s_eval != NULL || s_emt != NULL ) {
15368             /* New style */
15369             char delim;
15370
15371             if( s_eval != NULL ) {
15372                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
15373                     return text;
15374                 }
15375
15376                 if( delim != ']' ) {
15377                     return text;
15378                 }
15379             }
15380
15381             if( s_emt != NULL ) {
15382             }
15383                 return text;
15384         }
15385         else {
15386             /* We expect something like: [+|-]nnn.nn/dd */
15387             int score_lo = 0;
15388
15389             if(*text != '{') return text; // [HGM] braces: must be normal comment
15390
15391             sep = strchr( text, '/' );
15392             if( sep == NULL || sep < (text+4) ) {
15393                 return text;
15394             }
15395
15396             p = text;
15397             if(p[1] == '(') { // comment starts with PV
15398                p = strchr(p, ')'); // locate end of PV
15399                if(p == NULL || sep < p+5) return text;
15400                // at this point we have something like "{(.*) +0.23/6 ..."
15401                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
15402                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
15403                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
15404             }
15405             time = -1; sec = -1; deci = -1;
15406             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
15407                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
15408                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
15409                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
15410                 return text;
15411             }
15412
15413             if( score_lo < 0 || score_lo >= 100 ) {
15414                 return text;
15415             }
15416
15417             if(sec >= 0) time = 600*time + 10*sec; else
15418             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
15419
15420             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
15421
15422             /* [HGM] PV time: now locate end of PV info */
15423             while( *++sep >= '0' && *sep <= '9'); // strip depth
15424             if(time >= 0)
15425             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
15426             if(sec >= 0)
15427             while( *++sep >= '0' && *sep <= '9'); // strip seconds
15428             if(deci >= 0)
15429             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
15430             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
15431         }
15432
15433         if( depth <= 0 ) {
15434             return text;
15435         }
15436
15437         if( time < 0 ) {
15438             time = -1;
15439         }
15440
15441         pvInfoList[index-1].depth = depth;
15442         pvInfoList[index-1].score = score;
15443         pvInfoList[index-1].time  = 10*time; // centi-sec
15444         if(*sep == '}') *sep = 0; else *--sep = '{';
15445         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
15446     }
15447     return sep;
15448 }
15449
15450 void
15451 SendToProgram (char *message, ChessProgramState *cps)
15452 {
15453     int count, outCount, error;
15454     char buf[MSG_SIZ];
15455
15456     if (cps->pr == NoProc) return;
15457     Attention(cps);
15458
15459     if (appData.debugMode) {
15460         TimeMark now;
15461         GetTimeMark(&now);
15462         fprintf(debugFP, "%ld >%-6s: %s",
15463                 SubtractTimeMarks(&now, &programStartTime),
15464                 cps->which, message);
15465         if(serverFP)
15466             fprintf(serverFP, "%ld >%-6s: %s",
15467                 SubtractTimeMarks(&now, &programStartTime),
15468                 cps->which, message), fflush(serverFP);
15469     }
15470
15471     count = strlen(message);
15472     outCount = OutputToProcess(cps->pr, message, count, &error);
15473     if (outCount < count && !exiting
15474                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
15475       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15476       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
15477         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15478             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15479                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15480                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15481                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15482             } else {
15483                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15484                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15485                 gameInfo.result = res;
15486             }
15487             gameInfo.resultDetails = StrSave(buf);
15488         }
15489         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15490         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15491     }
15492 }
15493
15494 void
15495 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
15496 {
15497     char *end_str;
15498     char buf[MSG_SIZ];
15499     ChessProgramState *cps = (ChessProgramState *)closure;
15500
15501     if (isr != cps->isr) return; /* Killed intentionally */
15502     if (count <= 0) {
15503         if (count == 0) {
15504             RemoveInputSource(cps->isr);
15505             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
15506                     _(cps->which), cps->program);
15507             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
15508             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15509                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15510                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15511                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15512                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15513                 } else {
15514                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15515                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15516                     gameInfo.result = res;
15517                 }
15518                 gameInfo.resultDetails = StrSave(buf);
15519             }
15520             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15521             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
15522         } else {
15523             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
15524                     _(cps->which), cps->program);
15525             RemoveInputSource(cps->isr);
15526
15527             /* [AS] Program is misbehaving badly... kill it */
15528             if( count == -2 ) {
15529                 DestroyChildProcess( cps->pr, 9 );
15530                 cps->pr = NoProc;
15531             }
15532
15533             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15534         }
15535         return;
15536     }
15537
15538     if ((end_str = strchr(message, '\r')) != NULL)
15539       *end_str = NULLCHAR;
15540     if ((end_str = strchr(message, '\n')) != NULL)
15541       *end_str = NULLCHAR;
15542
15543     if (appData.debugMode) {
15544         TimeMark now; int print = 1;
15545         char *quote = ""; char c; int i;
15546
15547         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
15548                 char start = message[0];
15549                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
15550                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
15551                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
15552                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
15553                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
15554                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
15555                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
15556                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
15557                    sscanf(message, "hint: %c", &c)!=1 && 
15558                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
15559                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
15560                     print = (appData.engineComments >= 2);
15561                 }
15562                 message[0] = start; // restore original message
15563         }
15564         if(print) {
15565                 GetTimeMark(&now);
15566                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
15567                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15568                         quote,
15569                         message);
15570                 if(serverFP)
15571                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
15572                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15573                         quote,
15574                         message), fflush(serverFP);
15575         }
15576     }
15577
15578     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
15579     if (appData.icsEngineAnalyze) {
15580         if (strstr(message, "whisper") != NULL ||
15581              strstr(message, "kibitz") != NULL ||
15582             strstr(message, "tellics") != NULL) return;
15583     }
15584
15585     HandleMachineMove(message, cps);
15586 }
15587
15588
15589 void
15590 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
15591 {
15592     char buf[MSG_SIZ];
15593     int seconds;
15594
15595     if( timeControl_2 > 0 ) {
15596         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
15597             tc = timeControl_2;
15598         }
15599     }
15600     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
15601     inc /= cps->timeOdds;
15602     st  /= cps->timeOdds;
15603
15604     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
15605
15606     if (st > 0) {
15607       /* Set exact time per move, normally using st command */
15608       if (cps->stKludge) {
15609         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
15610         seconds = st % 60;
15611         if (seconds == 0) {
15612           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
15613         } else {
15614           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
15615         }
15616       } else {
15617         snprintf(buf, MSG_SIZ, "st %d\n", st);
15618       }
15619     } else {
15620       /* Set conventional or incremental time control, using level command */
15621       if (seconds == 0) {
15622         /* Note old gnuchess bug -- minutes:seconds used to not work.
15623            Fixed in later versions, but still avoid :seconds
15624            when seconds is 0. */
15625         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
15626       } else {
15627         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
15628                  seconds, inc/1000.);
15629       }
15630     }
15631     SendToProgram(buf, cps);
15632
15633     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
15634     /* Orthogonally, limit search to given depth */
15635     if (sd > 0) {
15636       if (cps->sdKludge) {
15637         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
15638       } else {
15639         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
15640       }
15641       SendToProgram(buf, cps);
15642     }
15643
15644     if(cps->nps >= 0) { /* [HGM] nps */
15645         if(cps->supportsNPS == FALSE)
15646           cps->nps = -1; // don't use if engine explicitly says not supported!
15647         else {
15648           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
15649           SendToProgram(buf, cps);
15650         }
15651     }
15652 }
15653
15654 ChessProgramState *
15655 WhitePlayer ()
15656 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
15657 {
15658     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
15659        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
15660         return &second;
15661     return &first;
15662 }
15663
15664 void
15665 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
15666 {
15667     char message[MSG_SIZ];
15668     long time, otime;
15669
15670     /* Note: this routine must be called when the clocks are stopped
15671        or when they have *just* been set or switched; otherwise
15672        it will be off by the time since the current tick started.
15673     */
15674     if (machineWhite) {
15675         time = whiteTimeRemaining / 10;
15676         otime = blackTimeRemaining / 10;
15677     } else {
15678         time = blackTimeRemaining / 10;
15679         otime = whiteTimeRemaining / 10;
15680     }
15681     /* [HGM] translate opponent's time by time-odds factor */
15682     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
15683
15684     if (time <= 0) time = 1;
15685     if (otime <= 0) otime = 1;
15686
15687     snprintf(message, MSG_SIZ, "time %ld\n", time);
15688     SendToProgram(message, cps);
15689
15690     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
15691     SendToProgram(message, cps);
15692 }
15693
15694 int
15695 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15696 {
15697   char buf[MSG_SIZ];
15698   int len = strlen(name);
15699   int val;
15700
15701   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15702     (*p) += len + 1;
15703     sscanf(*p, "%d", &val);
15704     *loc = (val != 0);
15705     while (**p && **p != ' ')
15706       (*p)++;
15707     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15708     SendToProgram(buf, cps);
15709     return TRUE;
15710   }
15711   return FALSE;
15712 }
15713
15714 int
15715 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15716 {
15717   char buf[MSG_SIZ];
15718   int len = strlen(name);
15719   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15720     (*p) += len + 1;
15721     sscanf(*p, "%d", loc);
15722     while (**p && **p != ' ') (*p)++;
15723     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15724     SendToProgram(buf, cps);
15725     return TRUE;
15726   }
15727   return FALSE;
15728 }
15729
15730 int
15731 StringFeature (char **p, char *name, char loc[], ChessProgramState *cps)
15732 {
15733   char buf[MSG_SIZ];
15734   int len = strlen(name);
15735   if (strncmp((*p), name, len) == 0
15736       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
15737     (*p) += len + 2;
15738     sscanf(*p, "%[^\"]", loc);
15739     while (**p && **p != '\"') (*p)++;
15740     if (**p == '\"') (*p)++;
15741     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15742     SendToProgram(buf, cps);
15743     return TRUE;
15744   }
15745   return FALSE;
15746 }
15747
15748 int
15749 ParseOption (Option *opt, ChessProgramState *cps)
15750 // [HGM] options: process the string that defines an engine option, and determine
15751 // name, type, default value, and allowed value range
15752 {
15753         char *p, *q, buf[MSG_SIZ];
15754         int n, min = (-1)<<31, max = 1<<31, def;
15755
15756         if(p = strstr(opt->name, " -spin ")) {
15757             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15758             if(max < min) max = min; // enforce consistency
15759             if(def < min) def = min;
15760             if(def > max) def = max;
15761             opt->value = def;
15762             opt->min = min;
15763             opt->max = max;
15764             opt->type = Spin;
15765         } else if((p = strstr(opt->name, " -slider "))) {
15766             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
15767             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15768             if(max < min) max = min; // enforce consistency
15769             if(def < min) def = min;
15770             if(def > max) def = max;
15771             opt->value = def;
15772             opt->min = min;
15773             opt->max = max;
15774             opt->type = Spin; // Slider;
15775         } else if((p = strstr(opt->name, " -string "))) {
15776             opt->textValue = p+9;
15777             opt->type = TextBox;
15778         } else if((p = strstr(opt->name, " -file "))) {
15779             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15780             opt->textValue = p+7;
15781             opt->type = FileName; // FileName;
15782         } else if((p = strstr(opt->name, " -path "))) {
15783             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15784             opt->textValue = p+7;
15785             opt->type = PathName; // PathName;
15786         } else if(p = strstr(opt->name, " -check ")) {
15787             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
15788             opt->value = (def != 0);
15789             opt->type = CheckBox;
15790         } else if(p = strstr(opt->name, " -combo ")) {
15791             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
15792             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
15793             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
15794             opt->value = n = 0;
15795             while(q = StrStr(q, " /// ")) {
15796                 n++; *q = 0;    // count choices, and null-terminate each of them
15797                 q += 5;
15798                 if(*q == '*') { // remember default, which is marked with * prefix
15799                     q++;
15800                     opt->value = n;
15801                 }
15802                 cps->comboList[cps->comboCnt++] = q;
15803             }
15804             cps->comboList[cps->comboCnt++] = NULL;
15805             opt->max = n + 1;
15806             opt->type = ComboBox;
15807         } else if(p = strstr(opt->name, " -button")) {
15808             opt->type = Button;
15809         } else if(p = strstr(opt->name, " -save")) {
15810             opt->type = SaveButton;
15811         } else return FALSE;
15812         *p = 0; // terminate option name
15813         // now look if the command-line options define a setting for this engine option.
15814         if(cps->optionSettings && cps->optionSettings[0])
15815             p = strstr(cps->optionSettings, opt->name); else p = NULL;
15816         if(p && (p == cps->optionSettings || p[-1] == ',')) {
15817           snprintf(buf, MSG_SIZ, "option %s", p);
15818                 if(p = strstr(buf, ",")) *p = 0;
15819                 if(q = strchr(buf, '=')) switch(opt->type) {
15820                     case ComboBox:
15821                         for(n=0; n<opt->max; n++)
15822                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
15823                         break;
15824                     case TextBox:
15825                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
15826                         break;
15827                     case Spin:
15828                     case CheckBox:
15829                         opt->value = atoi(q+1);
15830                     default:
15831                         break;
15832                 }
15833                 strcat(buf, "\n");
15834                 SendToProgram(buf, cps);
15835         }
15836         return TRUE;
15837 }
15838
15839 void
15840 FeatureDone (ChessProgramState *cps, int val)
15841 {
15842   DelayedEventCallback cb = GetDelayedEvent();
15843   if ((cb == InitBackEnd3 && cps == &first) ||
15844       (cb == SettingsMenuIfReady && cps == &second) ||
15845       (cb == LoadEngine) ||
15846       (cb == TwoMachinesEventIfReady)) {
15847     CancelDelayedEvent();
15848     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
15849   }
15850   cps->initDone = val;
15851 }
15852
15853 /* Parse feature command from engine */
15854 void
15855 ParseFeatures (char *args, ChessProgramState *cps)
15856 {
15857   char *p = args;
15858   char *q;
15859   int val;
15860   char buf[MSG_SIZ];
15861
15862   for (;;) {
15863     while (*p == ' ') p++;
15864     if (*p == NULLCHAR) return;
15865
15866     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
15867     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
15868     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
15869     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
15870     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
15871     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
15872     if (BoolFeature(&p, "reuse", &val, cps)) {
15873       /* Engine can disable reuse, but can't enable it if user said no */
15874       if (!val) cps->reuse = FALSE;
15875       continue;
15876     }
15877     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
15878     if (StringFeature(&p, "myname", cps->tidy, cps)) {
15879       if (gameMode == TwoMachinesPlay) {
15880         DisplayTwoMachinesTitle();
15881       } else {
15882         DisplayTitle("");
15883       }
15884       continue;
15885     }
15886     if (StringFeature(&p, "variants", cps->variants, cps)) continue;
15887     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
15888     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
15889     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
15890     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
15891     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
15892     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
15893     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
15894     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
15895     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
15896     if (IntFeature(&p, "done", &val, cps)) {
15897       FeatureDone(cps, val);
15898       continue;
15899     }
15900     /* Added by Tord: */
15901     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
15902     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
15903     /* End of additions by Tord */
15904
15905     /* [HGM] added features: */
15906     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
15907     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
15908     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
15909     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
15910     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
15911     if (StringFeature(&p, "egt", cps->egtFormats, cps)) continue;
15912     if (StringFeature(&p, "option", buf, cps)) {
15913         FREE(cps->option[cps->nrOptions].name);
15914         cps->option[cps->nrOptions].name = malloc(MSG_SIZ);
15915         safeStrCpy(cps->option[cps->nrOptions].name, buf, MSG_SIZ);
15916         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
15917           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
15918             SendToProgram(buf, cps);
15919             continue;
15920         }
15921         if(cps->nrOptions >= MAX_OPTIONS) {
15922             cps->nrOptions--;
15923             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
15924             DisplayError(buf, 0);
15925         }
15926         continue;
15927     }
15928     /* End of additions by HGM */
15929
15930     /* unknown feature: complain and skip */
15931     q = p;
15932     while (*q && *q != '=') q++;
15933     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
15934     SendToProgram(buf, cps);
15935     p = q;
15936     if (*p == '=') {
15937       p++;
15938       if (*p == '\"') {
15939         p++;
15940         while (*p && *p != '\"') p++;
15941         if (*p == '\"') p++;
15942       } else {
15943         while (*p && *p != ' ') p++;
15944       }
15945     }
15946   }
15947
15948 }
15949
15950 void
15951 PeriodicUpdatesEvent (int newState)
15952 {
15953     if (newState == appData.periodicUpdates)
15954       return;
15955
15956     appData.periodicUpdates=newState;
15957
15958     /* Display type changes, so update it now */
15959 //    DisplayAnalysis();
15960
15961     /* Get the ball rolling again... */
15962     if (newState) {
15963         AnalysisPeriodicEvent(1);
15964         StartAnalysisClock();
15965     }
15966 }
15967
15968 void
15969 PonderNextMoveEvent (int newState)
15970 {
15971     if (newState == appData.ponderNextMove) return;
15972     if (gameMode == EditPosition) EditPositionDone(TRUE);
15973     if (newState) {
15974         SendToProgram("hard\n", &first);
15975         if (gameMode == TwoMachinesPlay) {
15976             SendToProgram("hard\n", &second);
15977         }
15978     } else {
15979         SendToProgram("easy\n", &first);
15980         thinkOutput[0] = NULLCHAR;
15981         if (gameMode == TwoMachinesPlay) {
15982             SendToProgram("easy\n", &second);
15983         }
15984     }
15985     appData.ponderNextMove = newState;
15986 }
15987
15988 void
15989 NewSettingEvent (int option, int *feature, char *command, int value)
15990 {
15991     char buf[MSG_SIZ];
15992
15993     if (gameMode == EditPosition) EditPositionDone(TRUE);
15994     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
15995     if(feature == NULL || *feature) SendToProgram(buf, &first);
15996     if (gameMode == TwoMachinesPlay) {
15997         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
15998     }
15999 }
16000
16001 void
16002 ShowThinkingEvent ()
16003 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
16004 {
16005     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
16006     int newState = appData.showThinking
16007         // [HGM] thinking: other features now need thinking output as well
16008         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
16009
16010     if (oldState == newState) return;
16011     oldState = newState;
16012     if (gameMode == EditPosition) EditPositionDone(TRUE);
16013     if (oldState) {
16014         SendToProgram("post\n", &first);
16015         if (gameMode == TwoMachinesPlay) {
16016             SendToProgram("post\n", &second);
16017         }
16018     } else {
16019         SendToProgram("nopost\n", &first);
16020         thinkOutput[0] = NULLCHAR;
16021         if (gameMode == TwoMachinesPlay) {
16022             SendToProgram("nopost\n", &second);
16023         }
16024     }
16025 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
16026 }
16027
16028 void
16029 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
16030 {
16031   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
16032   if (pr == NoProc) return;
16033   AskQuestion(title, question, replyPrefix, pr);
16034 }
16035
16036 void
16037 TypeInEvent (char firstChar)
16038 {
16039     if ((gameMode == BeginningOfGame && !appData.icsActive) || 
16040         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
16041         gameMode == AnalyzeMode || gameMode == EditGame || 
16042         gameMode == EditPosition || gameMode == IcsExamining ||
16043         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
16044         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
16045                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
16046                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
16047         gameMode == Training) PopUpMoveDialog(firstChar);
16048 }
16049
16050 void
16051 TypeInDoneEvent (char *move)
16052 {
16053         Board board;
16054         int n, fromX, fromY, toX, toY;
16055         char promoChar;
16056         ChessMove moveType;
16057
16058         // [HGM] FENedit
16059         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
16060                 EditPositionPasteFEN(move);
16061                 return;
16062         }
16063         // [HGM] movenum: allow move number to be typed in any mode
16064         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
16065           ToNrEvent(2*n-1);
16066           return;
16067         }
16068         // undocumented kludge: allow command-line option to be typed in!
16069         // (potentially fatal, and does not implement the effect of the option.)
16070         // should only be used for options that are values on which future decisions will be made,
16071         // and definitely not on options that would be used during initialization.
16072         if(strstr(move, "!!! -") == move) {
16073             ParseArgsFromString(move+4);
16074             return;
16075         }
16076
16077       if (gameMode != EditGame && currentMove != forwardMostMove && 
16078         gameMode != Training) {
16079         DisplayMoveError(_("Displayed move is not current"));
16080       } else {
16081         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
16082           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
16083         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
16084         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
16085           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
16086           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     
16087         } else {
16088           DisplayMoveError(_("Could not parse move"));
16089         }
16090       }
16091 }
16092
16093 void
16094 DisplayMove (int moveNumber)
16095 {
16096     char message[MSG_SIZ];
16097     char res[MSG_SIZ];
16098     char cpThinkOutput[MSG_SIZ];
16099
16100     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
16101
16102     if (moveNumber == forwardMostMove - 1 ||
16103         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16104
16105         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
16106
16107         if (strchr(cpThinkOutput, '\n')) {
16108             *strchr(cpThinkOutput, '\n') = NULLCHAR;
16109         }
16110     } else {
16111         *cpThinkOutput = NULLCHAR;
16112     }
16113
16114     /* [AS] Hide thinking from human user */
16115     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
16116         *cpThinkOutput = NULLCHAR;
16117         if( thinkOutput[0] != NULLCHAR ) {
16118             int i;
16119
16120             for( i=0; i<=hiddenThinkOutputState; i++ ) {
16121                 cpThinkOutput[i] = '.';
16122             }
16123             cpThinkOutput[i] = NULLCHAR;
16124             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
16125         }
16126     }
16127
16128     if (moveNumber == forwardMostMove - 1 &&
16129         gameInfo.resultDetails != NULL) {
16130         if (gameInfo.resultDetails[0] == NULLCHAR) {
16131           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
16132         } else {
16133           snprintf(res, MSG_SIZ, " {%s} %s",
16134                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
16135         }
16136     } else {
16137         res[0] = NULLCHAR;
16138     }
16139
16140     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16141         DisplayMessage(res, cpThinkOutput);
16142     } else {
16143       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
16144                 WhiteOnMove(moveNumber) ? " " : ".. ",
16145                 parseList[moveNumber], res);
16146         DisplayMessage(message, cpThinkOutput);
16147     }
16148 }
16149
16150 void
16151 DisplayComment (int moveNumber, char *text)
16152 {
16153     char title[MSG_SIZ];
16154
16155     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16156       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
16157     } else {
16158       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
16159               WhiteOnMove(moveNumber) ? " " : ".. ",
16160               parseList[moveNumber]);
16161     }
16162     if (text != NULL && (appData.autoDisplayComment || commentUp))
16163         CommentPopUp(title, text);
16164 }
16165
16166 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
16167  * might be busy thinking or pondering.  It can be omitted if your
16168  * gnuchess is configured to stop thinking immediately on any user
16169  * input.  However, that gnuchess feature depends on the FIONREAD
16170  * ioctl, which does not work properly on some flavors of Unix.
16171  */
16172 void
16173 Attention (ChessProgramState *cps)
16174 {
16175 #if ATTENTION
16176     if (!cps->useSigint) return;
16177     if (appData.noChessProgram || (cps->pr == NoProc)) return;
16178     switch (gameMode) {
16179       case MachinePlaysWhite:
16180       case MachinePlaysBlack:
16181       case TwoMachinesPlay:
16182       case IcsPlayingWhite:
16183       case IcsPlayingBlack:
16184       case AnalyzeMode:
16185       case AnalyzeFile:
16186         /* Skip if we know it isn't thinking */
16187         if (!cps->maybeThinking) return;
16188         if (appData.debugMode)
16189           fprintf(debugFP, "Interrupting %s\n", cps->which);
16190         InterruptChildProcess(cps->pr);
16191         cps->maybeThinking = FALSE;
16192         break;
16193       default:
16194         break;
16195     }
16196 #endif /*ATTENTION*/
16197 }
16198
16199 int
16200 CheckFlags ()
16201 {
16202     if (whiteTimeRemaining <= 0) {
16203         if (!whiteFlag) {
16204             whiteFlag = TRUE;
16205             if (appData.icsActive) {
16206                 if (appData.autoCallFlag &&
16207                     gameMode == IcsPlayingBlack && !blackFlag) {
16208                   SendToICS(ics_prefix);
16209                   SendToICS("flag\n");
16210                 }
16211             } else {
16212                 if (blackFlag) {
16213                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16214                 } else {
16215                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
16216                     if (appData.autoCallFlag) {
16217                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
16218                         return TRUE;
16219                     }
16220                 }
16221             }
16222         }
16223     }
16224     if (blackTimeRemaining <= 0) {
16225         if (!blackFlag) {
16226             blackFlag = TRUE;
16227             if (appData.icsActive) {
16228                 if (appData.autoCallFlag &&
16229                     gameMode == IcsPlayingWhite && !whiteFlag) {
16230                   SendToICS(ics_prefix);
16231                   SendToICS("flag\n");
16232                 }
16233             } else {
16234                 if (whiteFlag) {
16235                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16236                 } else {
16237                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
16238                     if (appData.autoCallFlag) {
16239                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
16240                         return TRUE;
16241                     }
16242                 }
16243             }
16244         }
16245     }
16246     return FALSE;
16247 }
16248
16249 void
16250 CheckTimeControl ()
16251 {
16252     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
16253         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
16254
16255     /*
16256      * add time to clocks when time control is achieved ([HGM] now also used for increment)
16257      */
16258     if ( !WhiteOnMove(forwardMostMove) ) {
16259         /* White made time control */
16260         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
16261         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
16262         /* [HGM] time odds: correct new time quota for time odds! */
16263                                             / WhitePlayer()->timeOdds;
16264         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
16265     } else {
16266         lastBlack -= blackTimeRemaining;
16267         /* Black made time control */
16268         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
16269                                             / WhitePlayer()->other->timeOdds;
16270         lastWhite = whiteTimeRemaining;
16271     }
16272 }
16273
16274 void
16275 DisplayBothClocks ()
16276 {
16277     int wom = gameMode == EditPosition ?
16278       !blackPlaysFirst : WhiteOnMove(currentMove);
16279     DisplayWhiteClock(whiteTimeRemaining, wom);
16280     DisplayBlackClock(blackTimeRemaining, !wom);
16281 }
16282
16283
16284 /* Timekeeping seems to be a portability nightmare.  I think everyone
16285    has ftime(), but I'm really not sure, so I'm including some ifdefs
16286    to use other calls if you don't.  Clocks will be less accurate if
16287    you have neither ftime nor gettimeofday.
16288 */
16289
16290 /* VS 2008 requires the #include outside of the function */
16291 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
16292 #include <sys/timeb.h>
16293 #endif
16294
16295 /* Get the current time as a TimeMark */
16296 void
16297 GetTimeMark (TimeMark *tm)
16298 {
16299 #if HAVE_GETTIMEOFDAY
16300
16301     struct timeval timeVal;
16302     struct timezone timeZone;
16303
16304     gettimeofday(&timeVal, &timeZone);
16305     tm->sec = (long) timeVal.tv_sec;
16306     tm->ms = (int) (timeVal.tv_usec / 1000L);
16307
16308 #else /*!HAVE_GETTIMEOFDAY*/
16309 #if HAVE_FTIME
16310
16311 // include <sys/timeb.h> / moved to just above start of function
16312     struct timeb timeB;
16313
16314     ftime(&timeB);
16315     tm->sec = (long) timeB.time;
16316     tm->ms = (int) timeB.millitm;
16317
16318 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
16319     tm->sec = (long) time(NULL);
16320     tm->ms = 0;
16321 #endif
16322 #endif
16323 }
16324
16325 /* Return the difference in milliseconds between two
16326    time marks.  We assume the difference will fit in a long!
16327 */
16328 long
16329 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
16330 {
16331     return 1000L*(tm2->sec - tm1->sec) +
16332            (long) (tm2->ms - tm1->ms);
16333 }
16334
16335
16336 /*
16337  * Code to manage the game clocks.
16338  *
16339  * In tournament play, black starts the clock and then white makes a move.
16340  * We give the human user a slight advantage if he is playing white---the
16341  * clocks don't run until he makes his first move, so it takes zero time.
16342  * Also, we don't account for network lag, so we could get out of sync
16343  * with GNU Chess's clock -- but then, referees are always right.
16344  */
16345
16346 static TimeMark tickStartTM;
16347 static long intendedTickLength;
16348
16349 long
16350 NextTickLength (long timeRemaining)
16351 {
16352     long nominalTickLength, nextTickLength;
16353
16354     if (timeRemaining > 0L && timeRemaining <= 10000L)
16355       nominalTickLength = 100L;
16356     else
16357       nominalTickLength = 1000L;
16358     nextTickLength = timeRemaining % nominalTickLength;
16359     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
16360
16361     return nextTickLength;
16362 }
16363
16364 /* Adjust clock one minute up or down */
16365 void
16366 AdjustClock (Boolean which, int dir)
16367 {
16368     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
16369     if(which) blackTimeRemaining += 60000*dir;
16370     else      whiteTimeRemaining += 60000*dir;
16371     DisplayBothClocks();
16372     adjustedClock = TRUE;
16373 }
16374
16375 /* Stop clocks and reset to a fresh time control */
16376 void
16377 ResetClocks ()
16378 {
16379     (void) StopClockTimer();
16380     if (appData.icsActive) {
16381         whiteTimeRemaining = blackTimeRemaining = 0;
16382     } else if (searchTime) {
16383         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16384         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16385     } else { /* [HGM] correct new time quote for time odds */
16386         whiteTC = blackTC = fullTimeControlString;
16387         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
16388         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
16389     }
16390     if (whiteFlag || blackFlag) {
16391         DisplayTitle("");
16392         whiteFlag = blackFlag = FALSE;
16393     }
16394     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
16395     DisplayBothClocks();
16396     adjustedClock = FALSE;
16397 }
16398
16399 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
16400
16401 /* Decrement running clock by amount of time that has passed */
16402 void
16403 DecrementClocks ()
16404 {
16405     long timeRemaining;
16406     long lastTickLength, fudge;
16407     TimeMark now;
16408
16409     if (!appData.clockMode) return;
16410     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
16411
16412     GetTimeMark(&now);
16413
16414     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16415
16416     /* Fudge if we woke up a little too soon */
16417     fudge = intendedTickLength - lastTickLength;
16418     if (fudge < 0 || fudge > FUDGE) fudge = 0;
16419
16420     if (WhiteOnMove(forwardMostMove)) {
16421         if(whiteNPS >= 0) lastTickLength = 0;
16422         timeRemaining = whiteTimeRemaining -= lastTickLength;
16423         if(timeRemaining < 0 && !appData.icsActive) {
16424             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
16425             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
16426                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
16427                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
16428             }
16429         }
16430         DisplayWhiteClock(whiteTimeRemaining - fudge,
16431                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16432     } else {
16433         if(blackNPS >= 0) lastTickLength = 0;
16434         timeRemaining = blackTimeRemaining -= lastTickLength;
16435         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
16436             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
16437             if(suddenDeath) {
16438                 blackStartMove = forwardMostMove;
16439                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
16440             }
16441         }
16442         DisplayBlackClock(blackTimeRemaining - fudge,
16443                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16444     }
16445     if (CheckFlags()) return;
16446
16447     if(twoBoards) { // count down secondary board's clocks as well
16448         activePartnerTime -= lastTickLength;
16449         partnerUp = 1;
16450         if(activePartner == 'W')
16451             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
16452         else
16453             DisplayBlackClock(activePartnerTime, TRUE);
16454         partnerUp = 0;
16455     }
16456
16457     tickStartTM = now;
16458     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
16459     StartClockTimer(intendedTickLength);
16460
16461     /* if the time remaining has fallen below the alarm threshold, sound the
16462      * alarm. if the alarm has sounded and (due to a takeback or time control
16463      * with increment) the time remaining has increased to a level above the
16464      * threshold, reset the alarm so it can sound again.
16465      */
16466
16467     if (appData.icsActive && appData.icsAlarm) {
16468
16469         /* make sure we are dealing with the user's clock */
16470         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
16471                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
16472            )) return;
16473
16474         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
16475             alarmSounded = FALSE;
16476         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
16477             PlayAlarmSound();
16478             alarmSounded = TRUE;
16479         }
16480     }
16481 }
16482
16483
16484 /* A player has just moved, so stop the previously running
16485    clock and (if in clock mode) start the other one.
16486    We redisplay both clocks in case we're in ICS mode, because
16487    ICS gives us an update to both clocks after every move.
16488    Note that this routine is called *after* forwardMostMove
16489    is updated, so the last fractional tick must be subtracted
16490    from the color that is *not* on move now.
16491 */
16492 void
16493 SwitchClocks (int newMoveNr)
16494 {
16495     long lastTickLength;
16496     TimeMark now;
16497     int flagged = FALSE;
16498
16499     GetTimeMark(&now);
16500
16501     if (StopClockTimer() && appData.clockMode) {
16502         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16503         if (!WhiteOnMove(forwardMostMove)) {
16504             if(blackNPS >= 0) lastTickLength = 0;
16505             blackTimeRemaining -= lastTickLength;
16506            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16507 //         if(pvInfoList[forwardMostMove].time == -1)
16508                  pvInfoList[forwardMostMove].time =               // use GUI time
16509                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
16510         } else {
16511            if(whiteNPS >= 0) lastTickLength = 0;
16512            whiteTimeRemaining -= lastTickLength;
16513            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16514 //         if(pvInfoList[forwardMostMove].time == -1)
16515                  pvInfoList[forwardMostMove].time =
16516                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
16517         }
16518         flagged = CheckFlags();
16519     }
16520     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
16521     CheckTimeControl();
16522
16523     if (flagged || !appData.clockMode) return;
16524
16525     switch (gameMode) {
16526       case MachinePlaysBlack:
16527       case MachinePlaysWhite:
16528       case BeginningOfGame:
16529         if (pausing) return;
16530         break;
16531
16532       case EditGame:
16533       case PlayFromGameFile:
16534       case IcsExamining:
16535         return;
16536
16537       default:
16538         break;
16539     }
16540
16541     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
16542         if(WhiteOnMove(forwardMostMove))
16543              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16544         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16545     }
16546
16547     tickStartTM = now;
16548     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16549       whiteTimeRemaining : blackTimeRemaining);
16550     StartClockTimer(intendedTickLength);
16551 }
16552
16553
16554 /* Stop both clocks */
16555 void
16556 StopClocks ()
16557 {
16558     long lastTickLength;
16559     TimeMark now;
16560
16561     if (!StopClockTimer()) return;
16562     if (!appData.clockMode) return;
16563
16564     GetTimeMark(&now);
16565
16566     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16567     if (WhiteOnMove(forwardMostMove)) {
16568         if(whiteNPS >= 0) lastTickLength = 0;
16569         whiteTimeRemaining -= lastTickLength;
16570         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
16571     } else {
16572         if(blackNPS >= 0) lastTickLength = 0;
16573         blackTimeRemaining -= lastTickLength;
16574         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
16575     }
16576     CheckFlags();
16577 }
16578
16579 /* Start clock of player on move.  Time may have been reset, so
16580    if clock is already running, stop and restart it. */
16581 void
16582 StartClocks ()
16583 {
16584     (void) StopClockTimer(); /* in case it was running already */
16585     DisplayBothClocks();
16586     if (CheckFlags()) return;
16587
16588     if (!appData.clockMode) return;
16589     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
16590
16591     GetTimeMark(&tickStartTM);
16592     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16593       whiteTimeRemaining : blackTimeRemaining);
16594
16595    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
16596     whiteNPS = blackNPS = -1;
16597     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
16598        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
16599         whiteNPS = first.nps;
16600     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
16601        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
16602         blackNPS = first.nps;
16603     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
16604         whiteNPS = second.nps;
16605     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
16606         blackNPS = second.nps;
16607     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
16608
16609     StartClockTimer(intendedTickLength);
16610 }
16611
16612 char *
16613 TimeString (long ms)
16614 {
16615     long second, minute, hour, day;
16616     char *sign = "";
16617     static char buf[32];
16618
16619     if (ms > 0 && ms <= 9900) {
16620       /* convert milliseconds to tenths, rounding up */
16621       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
16622
16623       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
16624       return buf;
16625     }
16626
16627     /* convert milliseconds to seconds, rounding up */
16628     /* use floating point to avoid strangeness of integer division
16629        with negative dividends on many machines */
16630     second = (long) floor(((double) (ms + 999L)) / 1000.0);
16631
16632     if (second < 0) {
16633         sign = "-";
16634         second = -second;
16635     }
16636
16637     day = second / (60 * 60 * 24);
16638     second = second % (60 * 60 * 24);
16639     hour = second / (60 * 60);
16640     second = second % (60 * 60);
16641     minute = second / 60;
16642     second = second % 60;
16643
16644     if (day > 0)
16645       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
16646               sign, day, hour, minute, second);
16647     else if (hour > 0)
16648       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
16649     else
16650       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
16651
16652     return buf;
16653 }
16654
16655
16656 /*
16657  * This is necessary because some C libraries aren't ANSI C compliant yet.
16658  */
16659 char *
16660 StrStr (char *string, char *match)
16661 {
16662     int i, length;
16663
16664     length = strlen(match);
16665
16666     for (i = strlen(string) - length; i >= 0; i--, string++)
16667       if (!strncmp(match, string, length))
16668         return string;
16669
16670     return NULL;
16671 }
16672
16673 char *
16674 StrCaseStr (char *string, char *match)
16675 {
16676     int i, j, length;
16677
16678     length = strlen(match);
16679
16680     for (i = strlen(string) - length; i >= 0; i--, string++) {
16681         for (j = 0; j < length; j++) {
16682             if (ToLower(match[j]) != ToLower(string[j]))
16683               break;
16684         }
16685         if (j == length) return string;
16686     }
16687
16688     return NULL;
16689 }
16690
16691 #ifndef _amigados
16692 int
16693 StrCaseCmp (char *s1, char *s2)
16694 {
16695     char c1, c2;
16696
16697     for (;;) {
16698         c1 = ToLower(*s1++);
16699         c2 = ToLower(*s2++);
16700         if (c1 > c2) return 1;
16701         if (c1 < c2) return -1;
16702         if (c1 == NULLCHAR) return 0;
16703     }
16704 }
16705
16706
16707 int
16708 ToLower (int c)
16709 {
16710     return isupper(c) ? tolower(c) : c;
16711 }
16712
16713
16714 int
16715 ToUpper (int c)
16716 {
16717     return islower(c) ? toupper(c) : c;
16718 }
16719 #endif /* !_amigados    */
16720
16721 char *
16722 StrSave (char *s)
16723 {
16724   char *ret;
16725
16726   if ((ret = (char *) malloc(strlen(s) + 1)))
16727     {
16728       safeStrCpy(ret, s, strlen(s)+1);
16729     }
16730   return ret;
16731 }
16732
16733 char *
16734 StrSavePtr (char *s, char **savePtr)
16735 {
16736     if (*savePtr) {
16737         free(*savePtr);
16738     }
16739     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
16740       safeStrCpy(*savePtr, s, strlen(s)+1);
16741     }
16742     return(*savePtr);
16743 }
16744
16745 char *
16746 PGNDate ()
16747 {
16748     time_t clock;
16749     struct tm *tm;
16750     char buf[MSG_SIZ];
16751
16752     clock = time((time_t *)NULL);
16753     tm = localtime(&clock);
16754     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
16755             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
16756     return StrSave(buf);
16757 }
16758
16759
16760 char *
16761 PositionToFEN (int move, char *overrideCastling)
16762 {
16763     int i, j, fromX, fromY, toX, toY;
16764     int whiteToPlay;
16765     char buf[MSG_SIZ];
16766     char *p, *q;
16767     int emptycount;
16768     ChessSquare piece;
16769
16770     whiteToPlay = (gameMode == EditPosition) ?
16771       !blackPlaysFirst : (move % 2 == 0);
16772     p = buf;
16773
16774     /* Piece placement data */
16775     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16776         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
16777         emptycount = 0;
16778         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16779             if (boards[move][i][j] == EmptySquare) {
16780                 emptycount++;
16781             } else { ChessSquare piece = boards[move][i][j];
16782                 if (emptycount > 0) {
16783                     if(emptycount<10) /* [HGM] can be >= 10 */
16784                         *p++ = '0' + emptycount;
16785                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16786                     emptycount = 0;
16787                 }
16788                 if(PieceToChar(piece) == '+') {
16789                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
16790                     *p++ = '+';
16791                     piece = (ChessSquare)(DEMOTED piece);
16792                 }
16793                 *p++ = PieceToChar(piece);
16794                 if(p[-1] == '~') {
16795                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
16796                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
16797                     *p++ = '~';
16798                 }
16799             }
16800         }
16801         if (emptycount > 0) {
16802             if(emptycount<10) /* [HGM] can be >= 10 */
16803                 *p++ = '0' + emptycount;
16804             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16805             emptycount = 0;
16806         }
16807         *p++ = '/';
16808     }
16809     *(p - 1) = ' ';
16810
16811     /* [HGM] print Crazyhouse or Shogi holdings */
16812     if( gameInfo.holdingsWidth ) {
16813         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
16814         q = p;
16815         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
16816             piece = boards[move][i][BOARD_WIDTH-1];
16817             if( piece != EmptySquare )
16818               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
16819                   *p++ = PieceToChar(piece);
16820         }
16821         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
16822             piece = boards[move][BOARD_HEIGHT-i-1][0];
16823             if( piece != EmptySquare )
16824               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
16825                   *p++ = PieceToChar(piece);
16826         }
16827
16828         if( q == p ) *p++ = '-';
16829         *p++ = ']';
16830         *p++ = ' ';
16831     }
16832
16833     /* Active color */
16834     *p++ = whiteToPlay ? 'w' : 'b';
16835     *p++ = ' ';
16836
16837   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
16838     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
16839   } else {
16840   if(nrCastlingRights) {
16841      q = p;
16842      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
16843        /* [HGM] write directly from rights */
16844            if(boards[move][CASTLING][2] != NoRights &&
16845               boards[move][CASTLING][0] != NoRights   )
16846                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
16847            if(boards[move][CASTLING][2] != NoRights &&
16848               boards[move][CASTLING][1] != NoRights   )
16849                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
16850            if(boards[move][CASTLING][5] != NoRights &&
16851               boards[move][CASTLING][3] != NoRights   )
16852                 *p++ = boards[move][CASTLING][3] + AAA;
16853            if(boards[move][CASTLING][5] != NoRights &&
16854               boards[move][CASTLING][4] != NoRights   )
16855                 *p++ = boards[move][CASTLING][4] + AAA;
16856      } else {
16857
16858         /* [HGM] write true castling rights */
16859         if( nrCastlingRights == 6 ) {
16860             int q, k=0;
16861             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
16862                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
16863             q = (boards[move][CASTLING][1] == BOARD_LEFT &&
16864                  boards[move][CASTLING][2] != NoRights  );
16865             if(gameInfo.variant == VariantSChess) { // for S-Chess, indicate all vrgin backrank pieces
16866                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_RGHT]; // count white held pieces
16867                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
16868                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
16869                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
16870             }
16871             if(q) *p++ = 'Q';
16872             k = 0;
16873             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
16874                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
16875             q = (boards[move][CASTLING][4] == BOARD_LEFT &&
16876                  boards[move][CASTLING][5] != NoRights  );
16877             if(gameInfo.variant == VariantSChess) {
16878                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_LEFT-1]; // count black held pieces
16879                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
16880                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
16881                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
16882             }
16883             if(q) *p++ = 'q';
16884         }
16885      }
16886      if (q == p) *p++ = '-'; /* No castling rights */
16887      *p++ = ' ';
16888   }
16889
16890   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16891      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16892     /* En passant target square */
16893     if (move > backwardMostMove) {
16894         fromX = moveList[move - 1][0] - AAA;
16895         fromY = moveList[move - 1][1] - ONE;
16896         toX = moveList[move - 1][2] - AAA;
16897         toY = moveList[move - 1][3] - ONE;
16898         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
16899             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
16900             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
16901             fromX == toX) {
16902             /* 2-square pawn move just happened */
16903             *p++ = toX + AAA;
16904             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16905         } else {
16906             *p++ = '-';
16907         }
16908     } else if(move == backwardMostMove) {
16909         // [HGM] perhaps we should always do it like this, and forget the above?
16910         if((signed char)boards[move][EP_STATUS] >= 0) {
16911             *p++ = boards[move][EP_STATUS] + AAA;
16912             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16913         } else {
16914             *p++ = '-';
16915         }
16916     } else {
16917         *p++ = '-';
16918     }
16919     *p++ = ' ';
16920   }
16921   }
16922
16923     /* [HGM] find reversible plies */
16924     {   int i = 0, j=move;
16925
16926         if (appData.debugMode) { int k;
16927             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
16928             for(k=backwardMostMove; k<=forwardMostMove; k++)
16929                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
16930
16931         }
16932
16933         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
16934         if( j == backwardMostMove ) i += initialRulePlies;
16935         sprintf(p, "%d ", i);
16936         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
16937     }
16938     /* Fullmove number */
16939     sprintf(p, "%d", (move / 2) + 1);
16940
16941     return StrSave(buf);
16942 }
16943
16944 Boolean
16945 ParseFEN (Board board, int *blackPlaysFirst, char *fen)
16946 {
16947     int i, j;
16948     char *p, c;
16949     int emptycount, virgin[BOARD_FILES];
16950     ChessSquare piece;
16951
16952     p = fen;
16953
16954     /* [HGM] by default clear Crazyhouse holdings, if present */
16955     if(gameInfo.holdingsWidth) {
16956        for(i=0; i<BOARD_HEIGHT; i++) {
16957            board[i][0]             = EmptySquare; /* black holdings */
16958            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
16959            board[i][1]             = (ChessSquare) 0; /* black counts */
16960            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
16961        }
16962     }
16963
16964     /* Piece placement data */
16965     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16966         j = 0;
16967         for (;;) {
16968             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
16969                 if (*p == '/') p++;
16970                 emptycount = gameInfo.boardWidth - j;
16971                 while (emptycount--)
16972                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16973                 break;
16974 #if(BOARD_FILES >= 10)
16975             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
16976                 p++; emptycount=10;
16977                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16978                 while (emptycount--)
16979                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16980 #endif
16981             } else if (isdigit(*p)) {
16982                 emptycount = *p++ - '0';
16983                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
16984                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16985                 while (emptycount--)
16986                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16987             } else if (*p == '+' || isalpha(*p)) {
16988                 if (j >= gameInfo.boardWidth) return FALSE;
16989                 if(*p=='+') {
16990                     piece = CharToPiece(*++p);
16991                     if(piece == EmptySquare) return FALSE; /* unknown piece */
16992                     piece = (ChessSquare) (PROMOTED piece ); p++;
16993                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
16994                 } else piece = CharToPiece(*p++);
16995
16996                 if(piece==EmptySquare) return FALSE; /* unknown piece */
16997                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
16998                     piece = (ChessSquare) (PROMOTED piece);
16999                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
17000                     p++;
17001                 }
17002                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
17003             } else {
17004                 return FALSE;
17005             }
17006         }
17007     }
17008     while (*p == '/' || *p == ' ') p++;
17009
17010     /* [HGM] look for Crazyhouse holdings here */
17011     while(*p==' ') p++;
17012     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
17013         if(*p == '[') p++;
17014         if(*p == '-' ) p++; /* empty holdings */ else {
17015             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
17016             /* if we would allow FEN reading to set board size, we would   */
17017             /* have to add holdings and shift the board read so far here   */
17018             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
17019                 p++;
17020                 if((int) piece >= (int) BlackPawn ) {
17021                     i = (int)piece - (int)BlackPawn;
17022                     i = PieceToNumber((ChessSquare)i);
17023                     if( i >= gameInfo.holdingsSize ) return FALSE;
17024                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
17025                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
17026                 } else {
17027                     i = (int)piece - (int)WhitePawn;
17028                     i = PieceToNumber((ChessSquare)i);
17029                     if( i >= gameInfo.holdingsSize ) return FALSE;
17030                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
17031                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
17032                 }
17033             }
17034         }
17035         if(*p == ']') p++;
17036     }
17037
17038     while(*p == ' ') p++;
17039
17040     /* Active color */
17041     c = *p++;
17042     if(appData.colorNickNames) {
17043       if( c == appData.colorNickNames[0] ) c = 'w'; else
17044       if( c == appData.colorNickNames[1] ) c = 'b';
17045     }
17046     switch (c) {
17047       case 'w':
17048         *blackPlaysFirst = FALSE;
17049         break;
17050       case 'b':
17051         *blackPlaysFirst = TRUE;
17052         break;
17053       default:
17054         return FALSE;
17055     }
17056
17057     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
17058     /* return the extra info in global variiables             */
17059
17060     /* set defaults in case FEN is incomplete */
17061     board[EP_STATUS] = EP_UNKNOWN;
17062     for(i=0; i<nrCastlingRights; i++ ) {
17063         board[CASTLING][i] =
17064             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
17065     }   /* assume possible unless obviously impossible */
17066     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
17067     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
17068     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
17069                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
17070     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
17071     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
17072     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
17073                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
17074     FENrulePlies = 0;
17075
17076     while(*p==' ') p++;
17077     if(nrCastlingRights) {
17078       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
17079       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
17080           /* castling indicator present, so default becomes no castlings */
17081           for(i=0; i<nrCastlingRights; i++ ) {
17082                  board[CASTLING][i] = NoRights;
17083           }
17084       }
17085       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
17086              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess) &&
17087              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
17088              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
17089         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
17090
17091         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
17092             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
17093             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
17094         }
17095         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
17096             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
17097         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
17098                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
17099         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
17100                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
17101         switch(c) {
17102           case'K':
17103               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
17104               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
17105               board[CASTLING][2] = whiteKingFile;
17106               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
17107               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17108               break;
17109           case'Q':
17110               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
17111               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
17112               board[CASTLING][2] = whiteKingFile;
17113               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
17114               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17115               break;
17116           case'k':
17117               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
17118               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
17119               board[CASTLING][5] = blackKingFile;
17120               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
17121               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17122               break;
17123           case'q':
17124               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
17125               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
17126               board[CASTLING][5] = blackKingFile;
17127               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
17128               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17129           case '-':
17130               break;
17131           default: /* FRC castlings */
17132               if(c >= 'a') { /* black rights */
17133                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
17134                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17135                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
17136                   if(i == BOARD_RGHT) break;
17137                   board[CASTLING][5] = i;
17138                   c -= AAA;
17139                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
17140                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
17141                   if(c > i)
17142                       board[CASTLING][3] = c;
17143                   else
17144                       board[CASTLING][4] = c;
17145               } else { /* white rights */
17146                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
17147                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17148                     if(board[0][i] == WhiteKing) break;
17149                   if(i == BOARD_RGHT) break;
17150                   board[CASTLING][2] = i;
17151                   c -= AAA - 'a' + 'A';
17152                   if(board[0][c] >= WhiteKing) break;
17153                   if(c > i)
17154                       board[CASTLING][0] = c;
17155                   else
17156                       board[CASTLING][1] = c;
17157               }
17158         }
17159       }
17160       for(i=0; i<nrCastlingRights; i++)
17161         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
17162       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = virgin[i];
17163     if (appData.debugMode) {
17164         fprintf(debugFP, "FEN castling rights:");
17165         for(i=0; i<nrCastlingRights; i++)
17166         fprintf(debugFP, " %d", board[CASTLING][i]);
17167         fprintf(debugFP, "\n");
17168     }
17169
17170       while(*p==' ') p++;
17171     }
17172
17173     /* read e.p. field in games that know e.p. capture */
17174     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17175        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
17176       if(*p=='-') {
17177         p++; board[EP_STATUS] = EP_NONE;
17178       } else {
17179          char c = *p++ - AAA;
17180
17181          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
17182          if(*p >= '0' && *p <='9') p++;
17183          board[EP_STATUS] = c;
17184       }
17185     }
17186
17187
17188     if(sscanf(p, "%d", &i) == 1) {
17189         FENrulePlies = i; /* 50-move ply counter */
17190         /* (The move number is still ignored)    */
17191     }
17192
17193     return TRUE;
17194 }
17195
17196 void
17197 EditPositionPasteFEN (char *fen)
17198 {
17199   if (fen != NULL) {
17200     Board initial_position;
17201
17202     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
17203       DisplayError(_("Bad FEN position in clipboard"), 0);
17204       return ;
17205     } else {
17206       int savedBlackPlaysFirst = blackPlaysFirst;
17207       EditPositionEvent();
17208       blackPlaysFirst = savedBlackPlaysFirst;
17209       CopyBoard(boards[0], initial_position);
17210       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
17211       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
17212       DisplayBothClocks();
17213       DrawPosition(FALSE, boards[currentMove]);
17214     }
17215   }
17216 }
17217
17218 static char cseq[12] = "\\   ";
17219
17220 Boolean
17221 set_cont_sequence (char *new_seq)
17222 {
17223     int len;
17224     Boolean ret;
17225
17226     // handle bad attempts to set the sequence
17227         if (!new_seq)
17228                 return 0; // acceptable error - no debug
17229
17230     len = strlen(new_seq);
17231     ret = (len > 0) && (len < sizeof(cseq));
17232     if (ret)
17233       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
17234     else if (appData.debugMode)
17235       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
17236     return ret;
17237 }
17238
17239 /*
17240     reformat a source message so words don't cross the width boundary.  internal
17241     newlines are not removed.  returns the wrapped size (no null character unless
17242     included in source message).  If dest is NULL, only calculate the size required
17243     for the dest buffer.  lp argument indicats line position upon entry, and it's
17244     passed back upon exit.
17245 */
17246 int
17247 wrap (char *dest, char *src, int count, int width, int *lp)
17248 {
17249     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
17250
17251     cseq_len = strlen(cseq);
17252     old_line = line = *lp;
17253     ansi = len = clen = 0;
17254
17255     for (i=0; i < count; i++)
17256     {
17257         if (src[i] == '\033')
17258             ansi = 1;
17259
17260         // if we hit the width, back up
17261         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
17262         {
17263             // store i & len in case the word is too long
17264             old_i = i, old_len = len;
17265
17266             // find the end of the last word
17267             while (i && src[i] != ' ' && src[i] != '\n')
17268             {
17269                 i--;
17270                 len--;
17271             }
17272
17273             // word too long?  restore i & len before splitting it
17274             if ((old_i-i+clen) >= width)
17275             {
17276                 i = old_i;
17277                 len = old_len;
17278             }
17279
17280             // extra space?
17281             if (i && src[i-1] == ' ')
17282                 len--;
17283
17284             if (src[i] != ' ' && src[i] != '\n')
17285             {
17286                 i--;
17287                 if (len)
17288                     len--;
17289             }
17290
17291             // now append the newline and continuation sequence
17292             if (dest)
17293                 dest[len] = '\n';
17294             len++;
17295             if (dest)
17296                 strncpy(dest+len, cseq, cseq_len);
17297             len += cseq_len;
17298             line = cseq_len;
17299             clen = cseq_len;
17300             continue;
17301         }
17302
17303         if (dest)
17304             dest[len] = src[i];
17305         len++;
17306         if (!ansi)
17307             line++;
17308         if (src[i] == '\n')
17309             line = 0;
17310         if (src[i] == 'm')
17311             ansi = 0;
17312     }
17313     if (dest && appData.debugMode)
17314     {
17315         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
17316             count, width, line, len, *lp);
17317         show_bytes(debugFP, src, count);
17318         fprintf(debugFP, "\ndest: ");
17319         show_bytes(debugFP, dest, len);
17320         fprintf(debugFP, "\n");
17321     }
17322     *lp = dest ? line : old_line;
17323
17324     return len;
17325 }
17326
17327 // [HGM] vari: routines for shelving variations
17328 Boolean modeRestore = FALSE;
17329
17330 void
17331 PushInner (int firstMove, int lastMove)
17332 {
17333         int i, j, nrMoves = lastMove - firstMove;
17334
17335         // push current tail of game on stack
17336         savedResult[storedGames] = gameInfo.result;
17337         savedDetails[storedGames] = gameInfo.resultDetails;
17338         gameInfo.resultDetails = NULL;
17339         savedFirst[storedGames] = firstMove;
17340         savedLast [storedGames] = lastMove;
17341         savedFramePtr[storedGames] = framePtr;
17342         framePtr -= nrMoves; // reserve space for the boards
17343         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
17344             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
17345             for(j=0; j<MOVE_LEN; j++)
17346                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
17347             for(j=0; j<2*MOVE_LEN; j++)
17348                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
17349             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
17350             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
17351             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
17352             pvInfoList[firstMove+i-1].depth = 0;
17353             commentList[framePtr+i] = commentList[firstMove+i];
17354             commentList[firstMove+i] = NULL;
17355         }
17356
17357         storedGames++;
17358         forwardMostMove = firstMove; // truncate game so we can start variation
17359 }
17360
17361 void
17362 PushTail (int firstMove, int lastMove)
17363 {
17364         if(appData.icsActive) { // only in local mode
17365                 forwardMostMove = currentMove; // mimic old ICS behavior
17366                 return;
17367         }
17368         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
17369
17370         PushInner(firstMove, lastMove);
17371         if(storedGames == 1) GreyRevert(FALSE);
17372         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
17373 }
17374
17375 void
17376 PopInner (Boolean annotate)
17377 {
17378         int i, j, nrMoves;
17379         char buf[8000], moveBuf[20];
17380
17381         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
17382         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
17383         nrMoves = savedLast[storedGames] - currentMove;
17384         if(annotate) {
17385                 int cnt = 10;
17386                 if(!WhiteOnMove(currentMove))
17387                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
17388                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
17389                 for(i=currentMove; i<forwardMostMove; i++) {
17390                         if(WhiteOnMove(i))
17391                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
17392                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
17393                         strcat(buf, moveBuf);
17394                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
17395                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
17396                 }
17397                 strcat(buf, ")");
17398         }
17399         for(i=1; i<=nrMoves; i++) { // copy last variation back
17400             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
17401             for(j=0; j<MOVE_LEN; j++)
17402                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
17403             for(j=0; j<2*MOVE_LEN; j++)
17404                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
17405             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
17406             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
17407             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
17408             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
17409             commentList[currentMove+i] = commentList[framePtr+i];
17410             commentList[framePtr+i] = NULL;
17411         }
17412         if(annotate) AppendComment(currentMove+1, buf, FALSE);
17413         framePtr = savedFramePtr[storedGames];
17414         gameInfo.result = savedResult[storedGames];
17415         if(gameInfo.resultDetails != NULL) {
17416             free(gameInfo.resultDetails);
17417       }
17418         gameInfo.resultDetails = savedDetails[storedGames];
17419         forwardMostMove = currentMove + nrMoves;
17420 }
17421
17422 Boolean
17423 PopTail (Boolean annotate)
17424 {
17425         if(appData.icsActive) return FALSE; // only in local mode
17426         if(!storedGames) return FALSE; // sanity
17427         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
17428
17429         PopInner(annotate);
17430         if(currentMove < forwardMostMove) ForwardEvent(); else
17431         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
17432
17433         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
17434         return TRUE;
17435 }
17436
17437 void
17438 CleanupTail ()
17439 {       // remove all shelved variations
17440         int i;
17441         for(i=0; i<storedGames; i++) {
17442             if(savedDetails[i])
17443                 free(savedDetails[i]);
17444             savedDetails[i] = NULL;
17445         }
17446         for(i=framePtr; i<MAX_MOVES; i++) {
17447                 if(commentList[i]) free(commentList[i]);
17448                 commentList[i] = NULL;
17449         }
17450         framePtr = MAX_MOVES-1;
17451         storedGames = 0;
17452 }
17453
17454 void
17455 LoadVariation (int index, char *text)
17456 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
17457         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
17458         int level = 0, move;
17459
17460         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
17461         // first find outermost bracketing variation
17462         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
17463             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
17464                 if(*p == '{') wait = '}'; else
17465                 if(*p == '[') wait = ']'; else
17466                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
17467                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
17468             }
17469             if(*p == wait) wait = NULLCHAR; // closing ]} found
17470             p++;
17471         }
17472         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
17473         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
17474         end[1] = NULLCHAR; // clip off comment beyond variation
17475         ToNrEvent(currentMove-1);
17476         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
17477         // kludge: use ParsePV() to append variation to game
17478         move = currentMove;
17479         ParsePV(start, TRUE, TRUE);
17480         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
17481         ClearPremoveHighlights();
17482         CommentPopDown();
17483         ToNrEvent(currentMove+1);
17484 }
17485
17486 void
17487 LoadTheme ()
17488 {
17489     char *p, *q, buf[MSG_SIZ];
17490     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
17491         snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
17492         ParseArgsFromString(buf);
17493         ActivateTheme(TRUE); // also redo colors
17494         return;
17495     }
17496     p = nickName;
17497     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
17498     {
17499         int len;
17500         q = appData.themeNames;
17501         snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
17502       if(appData.useBitmaps) {
17503         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
17504                 appData.liteBackTextureFile, appData.darkBackTextureFile, 
17505                 appData.liteBackTextureMode,
17506                 appData.darkBackTextureMode );
17507       } else {
17508         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
17509                 Col2Text(2),   // lightSquareColor
17510                 Col2Text(3) ); // darkSquareColor
17511       }
17512       if(appData.useBorder) {
17513         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
17514                 appData.border);
17515       } else {
17516         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
17517       }
17518       if(appData.useFont) {
17519         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
17520                 appData.renderPiecesWithFont,
17521                 appData.fontToPieceTable,
17522                 Col2Text(9),    // appData.fontBackColorWhite
17523                 Col2Text(10) ); // appData.fontForeColorBlack
17524       } else {
17525         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
17526                 appData.pieceDirectory);
17527         if(!appData.pieceDirectory[0])
17528           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
17529                 Col2Text(0),   // whitePieceColor
17530                 Col2Text(1) ); // blackPieceColor
17531       }
17532       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
17533                 Col2Text(4),   // highlightSquareColor
17534                 Col2Text(5) ); // premoveHighlightColor
17535         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
17536         if(insert != q) insert[-1] = NULLCHAR;
17537         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
17538         if(q)   free(q);
17539     }
17540     ActivateTheme(FALSE);
17541 }