fixed some more translation strings
[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, 2013 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 void PauseEngine P((ChessProgramState *cps));
228 static int NonStandardBoardSize P((void));
229
230 #ifdef WIN32
231        extern void ConsoleCreate();
232 #endif
233
234 ChessProgramState *WhitePlayer();
235 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
236 int VerifyDisplayMode P(());
237
238 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
239 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
240 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
241 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
242 void ics_update_width P((int new_width));
243 extern char installDir[MSG_SIZ];
244 VariantClass startVariant; /* [HGM] nicks: initial variant */
245 Boolean abortMatch;
246
247 extern int tinyLayout, smallLayout;
248 ChessProgramStats programStats;
249 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
250 int endPV = -1;
251 static int exiting = 0; /* [HGM] moved to top */
252 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
253 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
254 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
255 int partnerHighlight[2];
256 Boolean partnerBoardValid = 0;
257 char partnerStatus[MSG_SIZ];
258 Boolean partnerUp;
259 Boolean originalFlip;
260 Boolean twoBoards = 0;
261 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
262 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
263 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
264 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
265 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
266 int opponentKibitzes;
267 int lastSavedGame; /* [HGM] save: ID of game */
268 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
269 extern int chatCount;
270 int chattingPartner;
271 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
272 char legal[BOARD_RANKS][BOARD_FILES];  /* [HGM] legal target squares */
273 char lastMsg[MSG_SIZ];
274 ChessSquare pieceSweep = EmptySquare;
275 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
276 int promoDefaultAltered;
277 int keepInfo = 0; /* [HGM] to protect PGN tags in auto-step game analysis */
278
279 /* States for ics_getting_history */
280 #define H_FALSE 0
281 #define H_REQUESTED 1
282 #define H_GOT_REQ_HEADER 2
283 #define H_GOT_UNREQ_HEADER 3
284 #define H_GETTING_MOVES 4
285 #define H_GOT_UNWANTED_HEADER 5
286
287 /* whosays values for GameEnds */
288 #define GE_ICS 0
289 #define GE_ENGINE 1
290 #define GE_PLAYER 2
291 #define GE_FILE 3
292 #define GE_XBOARD 4
293 #define GE_ENGINE1 5
294 #define GE_ENGINE2 6
295
296 /* Maximum number of games in a cmail message */
297 #define CMAIL_MAX_GAMES 20
298
299 /* Different types of move when calling RegisterMove */
300 #define CMAIL_MOVE   0
301 #define CMAIL_RESIGN 1
302 #define CMAIL_DRAW   2
303 #define CMAIL_ACCEPT 3
304
305 /* Different types of result to remember for each game */
306 #define CMAIL_NOT_RESULT 0
307 #define CMAIL_OLD_RESULT 1
308 #define CMAIL_NEW_RESULT 2
309
310 /* Telnet protocol constants */
311 #define TN_WILL 0373
312 #define TN_WONT 0374
313 #define TN_DO   0375
314 #define TN_DONT 0376
315 #define TN_IAC  0377
316 #define TN_ECHO 0001
317 #define TN_SGA  0003
318 #define TN_PORT 23
319
320 char*
321 safeStrCpy (char *dst, const char *src, size_t count)
322 { // [HGM] made safe
323   int i;
324   assert( dst != NULL );
325   assert( src != NULL );
326   assert( count > 0 );
327
328   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
329   if(  i == count && dst[count-1] != NULLCHAR)
330     {
331       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
332       if(appData.debugMode)
333         fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
334     }
335
336   return dst;
337 }
338
339 /* Some compiler can't cast u64 to double
340  * This function do the job for us:
341
342  * We use the highest bit for cast, this only
343  * works if the highest bit is not
344  * in use (This should not happen)
345  *
346  * We used this for all compiler
347  */
348 double
349 u64ToDouble (u64 value)
350 {
351   double r;
352   u64 tmp = value & u64Const(0x7fffffffffffffff);
353   r = (double)(s64)tmp;
354   if (value & u64Const(0x8000000000000000))
355        r +=  9.2233720368547758080e18; /* 2^63 */
356  return r;
357 }
358
359 /* Fake up flags for now, as we aren't keeping track of castling
360    availability yet. [HGM] Change of logic: the flag now only
361    indicates the type of castlings allowed by the rule of the game.
362    The actual rights themselves are maintained in the array
363    castlingRights, as part of the game history, and are not probed
364    by this function.
365  */
366 int
367 PosFlags (index)
368 {
369   int flags = F_ALL_CASTLE_OK;
370   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
371   switch (gameInfo.variant) {
372   case VariantSuicide:
373     flags &= ~F_ALL_CASTLE_OK;
374   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
375     flags |= F_IGNORE_CHECK;
376   case VariantLosers:
377     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
378     break;
379   case VariantAtomic:
380     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
381     break;
382   case VariantKriegspiel:
383     flags |= F_KRIEGSPIEL_CAPTURE;
384     break;
385   case VariantCapaRandom:
386   case VariantFischeRandom:
387     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
388   case VariantNoCastle:
389   case VariantShatranj:
390   case VariantCourier:
391   case VariantMakruk:
392   case VariantASEAN:
393   case VariantGrand:
394     flags &= ~F_ALL_CASTLE_OK;
395     break;
396   default:
397     break;
398   }
399   return flags;
400 }
401
402 FILE *gameFileFP, *debugFP, *serverFP;
403 char *currentDebugFile; // [HGM] debug split: to remember name
404
405 /*
406     [AS] Note: sometimes, the sscanf() function is used to parse the input
407     into a fixed-size buffer. Because of this, we must be prepared to
408     receive strings as long as the size of the input buffer, which is currently
409     set to 4K for Windows and 8K for the rest.
410     So, we must either allocate sufficiently large buffers here, or
411     reduce the size of the input buffer in the input reading part.
412 */
413
414 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
415 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
416 char thinkOutput1[MSG_SIZ*10];
417
418 ChessProgramState first, second, pairing;
419
420 /* premove variables */
421 int premoveToX = 0;
422 int premoveToY = 0;
423 int premoveFromX = 0;
424 int premoveFromY = 0;
425 int premovePromoChar = 0;
426 int gotPremove = 0;
427 Boolean alarmSounded;
428 /* end premove variables */
429
430 char *ics_prefix = "$";
431 enum ICS_TYPE ics_type = ICS_GENERIC;
432
433 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
434 int pauseExamForwardMostMove = 0;
435 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
436 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
437 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
438 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
439 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
440 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
441 int whiteFlag = FALSE, blackFlag = FALSE;
442 int userOfferedDraw = FALSE;
443 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
444 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
445 int cmailMoveType[CMAIL_MAX_GAMES];
446 long ics_clock_paused = 0;
447 ProcRef icsPR = NoProc, cmailPR = NoProc;
448 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
449 GameMode gameMode = BeginningOfGame;
450 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
451 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
452 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
453 int hiddenThinkOutputState = 0; /* [AS] */
454 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
455 int adjudicateLossPlies = 6;
456 char white_holding[64], black_holding[64];
457 TimeMark lastNodeCountTime;
458 long lastNodeCount=0;
459 int shiftKey, controlKey; // [HGM] set by mouse handler
460
461 int have_sent_ICS_logon = 0;
462 int movesPerSession;
463 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
464 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack, activePartnerTime;
465 Boolean adjustedClock;
466 long timeControl_2; /* [AS] Allow separate time controls */
467 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC, activePartner; /* [HGM] secondary TC: merge of MPS, TC and inc */
468 long timeRemaining[2][MAX_MOVES];
469 int matchGame = 0, nextGame = 0, roundNr = 0;
470 Boolean waitingForGame = FALSE, startingEngine = FALSE;
471 TimeMark programStartTime, pauseStart;
472 char ics_handle[MSG_SIZ];
473 int have_set_title = 0;
474
475 /* animateTraining preserves the state of appData.animate
476  * when Training mode is activated. This allows the
477  * response to be animated when appData.animate == TRUE and
478  * appData.animateDragging == TRUE.
479  */
480 Boolean animateTraining;
481
482 GameInfo gameInfo;
483
484 AppData appData;
485
486 Board boards[MAX_MOVES];
487 /* [HGM] Following 7 needed for accurate legality tests: */
488 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
489 signed char  initialRights[BOARD_FILES];
490 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
491 int   initialRulePlies, FENrulePlies;
492 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
493 int loadFlag = 0;
494 Boolean shuffleOpenings;
495 int mute; // mute all sounds
496
497 // [HGM] vari: next 12 to save and restore variations
498 #define MAX_VARIATIONS 10
499 int framePtr = MAX_MOVES-1; // points to free stack entry
500 int storedGames = 0;
501 int savedFirst[MAX_VARIATIONS];
502 int savedLast[MAX_VARIATIONS];
503 int savedFramePtr[MAX_VARIATIONS];
504 char *savedDetails[MAX_VARIATIONS];
505 ChessMove savedResult[MAX_VARIATIONS];
506
507 void PushTail P((int firstMove, int lastMove));
508 Boolean PopTail P((Boolean annotate));
509 void PushInner P((int firstMove, int lastMove));
510 void PopInner P((Boolean annotate));
511 void CleanupTail P((void));
512
513 ChessSquare  FIDEArray[2][BOARD_FILES] = {
514     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
515         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
516     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
517         BlackKing, BlackBishop, BlackKnight, BlackRook }
518 };
519
520 ChessSquare twoKingsArray[2][BOARD_FILES] = {
521     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
522         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
523     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
524         BlackKing, BlackKing, BlackKnight, BlackRook }
525 };
526
527 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
528     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
529         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
530     { BlackRook, BlackMan, BlackBishop, BlackQueen,
531         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
532 };
533
534 ChessSquare SpartanArray[2][BOARD_FILES] = {
535     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
536         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
537     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
538         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
539 };
540
541 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
542     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
543         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
544     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
545         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
546 };
547
548 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
549     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
550         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
551     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
552         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
553 };
554
555 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
556     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
557         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
558     { BlackRook, BlackKnight, BlackMan, BlackFerz,
559         BlackKing, BlackMan, BlackKnight, BlackRook }
560 };
561
562 ChessSquare aseanArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
563     { WhiteRook, WhiteKnight, WhiteMan, WhiteFerz,
564         WhiteKing, WhiteMan, WhiteKnight, WhiteRook },
565     { BlackRook, BlackKnight, BlackMan, BlackFerz,
566         BlackKing, BlackMan, BlackKnight, BlackRook }
567 };
568
569
570 #if (BOARD_FILES>=10)
571 ChessSquare ShogiArray[2][BOARD_FILES] = {
572     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
573         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
574     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
575         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
576 };
577
578 ChessSquare XiangqiArray[2][BOARD_FILES] = {
579     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
580         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
581     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
582         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
583 };
584
585 ChessSquare CapablancaArray[2][BOARD_FILES] = {
586     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
587         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
588     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
589         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
590 };
591
592 ChessSquare GreatArray[2][BOARD_FILES] = {
593     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
594         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
595     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
596         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
597 };
598
599 ChessSquare JanusArray[2][BOARD_FILES] = {
600     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
601         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
602     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
603         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
604 };
605
606 ChessSquare GrandArray[2][BOARD_FILES] = {
607     { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
608         WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
609     { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
610         BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
611 };
612
613 #ifdef GOTHIC
614 ChessSquare GothicArray[2][BOARD_FILES] = {
615     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
616         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
617     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
618         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
619 };
620 #else // !GOTHIC
621 #define GothicArray CapablancaArray
622 #endif // !GOTHIC
623
624 #ifdef FALCON
625 ChessSquare FalconArray[2][BOARD_FILES] = {
626     { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
627         WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
628     { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
629         BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
630 };
631 #else // !FALCON
632 #define FalconArray CapablancaArray
633 #endif // !FALCON
634
635 #else // !(BOARD_FILES>=10)
636 #define XiangqiPosition FIDEArray
637 #define CapablancaArray FIDEArray
638 #define GothicArray FIDEArray
639 #define GreatArray FIDEArray
640 #endif // !(BOARD_FILES>=10)
641
642 #if (BOARD_FILES>=12)
643 ChessSquare CourierArray[2][BOARD_FILES] = {
644     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
645         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
646     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
647         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
648 };
649 #else // !(BOARD_FILES>=12)
650 #define CourierArray CapablancaArray
651 #endif // !(BOARD_FILES>=12)
652
653
654 Board initialPosition;
655
656
657 /* Convert str to a rating. Checks for special cases of "----",
658
659    "++++", etc. Also strips ()'s */
660 int
661 string_to_rating (char *str)
662 {
663   while(*str && !isdigit(*str)) ++str;
664   if (!*str)
665     return 0;   /* One of the special "no rating" cases */
666   else
667     return atoi(str);
668 }
669
670 void
671 ClearProgramStats ()
672 {
673     /* Init programStats */
674     programStats.movelist[0] = 0;
675     programStats.depth = 0;
676     programStats.nr_moves = 0;
677     programStats.moves_left = 0;
678     programStats.nodes = 0;
679     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
680     programStats.score = 0;
681     programStats.got_only_move = 0;
682     programStats.got_fail = 0;
683     programStats.line_is_book = 0;
684 }
685
686 void
687 CommonEngineInit ()
688 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
689     if (appData.firstPlaysBlack) {
690         first.twoMachinesColor = "black\n";
691         second.twoMachinesColor = "white\n";
692     } else {
693         first.twoMachinesColor = "white\n";
694         second.twoMachinesColor = "black\n";
695     }
696
697     first.other = &second;
698     second.other = &first;
699
700     { float norm = 1;
701         if(appData.timeOddsMode) {
702             norm = appData.timeOdds[0];
703             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
704         }
705         first.timeOdds  = appData.timeOdds[0]/norm;
706         second.timeOdds = appData.timeOdds[1]/norm;
707     }
708
709     if(programVersion) free(programVersion);
710     if (appData.noChessProgram) {
711         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
712         sprintf(programVersion, "%s", PACKAGE_STRING);
713     } else {
714       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
715       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
716       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
717     }
718 }
719
720 void
721 UnloadEngine (ChessProgramState *cps)
722 {
723         /* Kill off first chess program */
724         if (cps->isr != NULL)
725           RemoveInputSource(cps->isr);
726         cps->isr = NULL;
727
728         if (cps->pr != NoProc) {
729             ExitAnalyzeMode();
730             DoSleep( appData.delayBeforeQuit );
731             SendToProgram("quit\n", cps);
732             DoSleep( appData.delayAfterQuit );
733             DestroyChildProcess(cps->pr, cps->useSigterm);
734         }
735         cps->pr = NoProc;
736         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
737 }
738
739 void
740 ClearOptions (ChessProgramState *cps)
741 {
742     int i;
743     cps->nrOptions = cps->comboCnt = 0;
744     for(i=0; i<MAX_OPTIONS; i++) {
745         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
746         cps->option[i].textValue = 0;
747     }
748 }
749
750 char *engineNames[] = {
751   /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
752      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
753 N_("first"),
754   /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
755      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
756 N_("second")
757 };
758
759 void
760 InitEngine (ChessProgramState *cps, int n)
761 {   // [HGM] all engine initialiation put in a function that does one engine
762
763     ClearOptions(cps);
764
765     cps->which = engineNames[n];
766     cps->maybeThinking = FALSE;
767     cps->pr = NoProc;
768     cps->isr = NULL;
769     cps->sendTime = 2;
770     cps->sendDrawOffers = 1;
771
772     cps->program = appData.chessProgram[n];
773     cps->host = appData.host[n];
774     cps->dir = appData.directory[n];
775     cps->initString = appData.engInitString[n];
776     cps->computerString = appData.computerString[n];
777     cps->useSigint  = TRUE;
778     cps->useSigterm = TRUE;
779     cps->reuse = appData.reuse[n];
780     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
781     cps->useSetboard = FALSE;
782     cps->useSAN = FALSE;
783     cps->usePing = FALSE;
784     cps->lastPing = 0;
785     cps->lastPong = 0;
786     cps->usePlayother = FALSE;
787     cps->useColors = TRUE;
788     cps->useUsermove = FALSE;
789     cps->sendICS = FALSE;
790     cps->sendName = appData.icsActive;
791     cps->sdKludge = FALSE;
792     cps->stKludge = FALSE;
793     if(cps->tidy == NULL) cps->tidy = (char*) malloc(MSG_SIZ);
794     TidyProgramName(cps->program, cps->host, cps->tidy);
795     cps->matchWins = 0;
796     ASSIGN(cps->variants, appData.variant);
797     cps->analysisSupport = 2; /* detect */
798     cps->analyzing = FALSE;
799     cps->initDone = FALSE;
800     cps->reload = FALSE;
801
802     /* New features added by Tord: */
803     cps->useFEN960 = FALSE;
804     cps->useOOCastle = TRUE;
805     /* End of new features added by Tord. */
806     cps->fenOverride  = appData.fenOverride[n];
807
808     /* [HGM] time odds: set factor for each machine */
809     cps->timeOdds  = appData.timeOdds[n];
810
811     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
812     cps->accumulateTC = appData.accumulateTC[n];
813     cps->maxNrOfSessions = 1;
814
815     /* [HGM] debug */
816     cps->debug = FALSE;
817
818     cps->supportsNPS = UNKNOWN;
819     cps->memSize = FALSE;
820     cps->maxCores = FALSE;
821     ASSIGN(cps->egtFormats, "");
822
823     /* [HGM] options */
824     cps->optionSettings  = appData.engOptions[n];
825
826     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
827     cps->isUCI = appData.isUCI[n]; /* [AS] */
828     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
829     cps->highlight = 0;
830
831     if (appData.protocolVersion[n] > PROTOVER
832         || appData.protocolVersion[n] < 1)
833       {
834         char buf[MSG_SIZ];
835         int len;
836
837         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
838                        appData.protocolVersion[n]);
839         if( (len >= MSG_SIZ) && appData.debugMode )
840           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
841
842         DisplayFatalError(buf, 0, 2);
843       }
844     else
845       {
846         cps->protocolVersion = appData.protocolVersion[n];
847       }
848
849     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
850     ParseFeatures(appData.featureDefaults, cps);
851 }
852
853 ChessProgramState *savCps;
854
855 GameMode oldMode;
856
857 void
858 LoadEngine ()
859 {
860     int i;
861     if(WaitForEngine(savCps, LoadEngine)) return;
862     CommonEngineInit(); // recalculate time odds
863     if(gameInfo.variant != StringToVariant(appData.variant)) {
864         // we changed variant when loading the engine; this forces us to reset
865         Reset(TRUE, savCps != &first);
866         oldMode = BeginningOfGame; // to prevent restoring old mode
867     }
868     InitChessProgram(savCps, FALSE);
869     if(gameMode == EditGame) SendToProgram("force\n", savCps); // in EditGame mode engine must be in force mode
870     DisplayMessage("", "");
871     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
872     for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
873     ThawUI();
874     SetGNUMode();
875     if(oldMode == AnalyzeMode) AnalyzeModeEvent();
876 }
877
878 void
879 ReplaceEngine (ChessProgramState *cps, int n)
880 {
881     oldMode = gameMode; // remember mode, so it can be restored after loading sequence is complete
882     keepInfo = 1;
883     if(oldMode != BeginningOfGame) EditGameEvent();
884     keepInfo = 0;
885     UnloadEngine(cps);
886     appData.noChessProgram = FALSE;
887     appData.clockMode = TRUE;
888     InitEngine(cps, n);
889     UpdateLogos(TRUE);
890     if(n) return; // only startup first engine immediately; second can wait
891     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
892     LoadEngine();
893 }
894
895 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
896 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
897
898 static char resetOptions[] =
899         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
900         "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
901         "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 "
902         "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
903
904 void
905 FloatToFront(char **list, char *engineLine)
906 {
907     char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
908     int i=0;
909     if(appData.recentEngines <= 0) return;
910     TidyProgramName(engineLine, "localhost", tidy+1);
911     tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
912     strncpy(buf+1, *list, MSG_SIZ-50);
913     if(p = strstr(buf, tidy)) { // tidy name appears in list
914         q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
915         while(*p++ = *++q); // squeeze out
916     }
917     strcat(tidy, buf+1); // put list behind tidy name
918     p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
919     if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
920     ASSIGN(*list, tidy+1);
921 }
922
923 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
924
925 void
926 Load (ChessProgramState *cps, int i)
927 {
928     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ];
929     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
930         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
931         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
932         ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
933         FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
934         appData.firstProtocolVersion = PROTOVER;
935         ParseArgsFromString(buf);
936         SwapEngines(i);
937         ReplaceEngine(cps, i);
938         FloatToFront(&appData.recentEngineList, engineLine);
939         return;
940     }
941     p = engineName;
942     while(q = strchr(p, SLASH)) p = q+1;
943     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
944     if(engineDir[0] != NULLCHAR) {
945         ASSIGN(appData.directory[i], engineDir); p = engineName;
946     } else if(p != engineName) { // derive directory from engine path, when not given
947         p[-1] = 0;
948         ASSIGN(appData.directory[i], engineName);
949         p[-1] = SLASH;
950         if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
951     } else { ASSIGN(appData.directory[i], "."); }
952     if(params[0]) {
953         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
954         snprintf(command, MSG_SIZ, "%s %s", p, params);
955         p = command;
956     }
957     ASSIGN(appData.chessProgram[i], p);
958     appData.isUCI[i] = isUCI;
959     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
960     appData.hasOwnBookUCI[i] = hasBook;
961     if(!nickName[0]) useNick = FALSE;
962     if(useNick) ASSIGN(appData.pgnName[i], nickName);
963     if(addToList) {
964         int len;
965         char quote;
966         q = firstChessProgramNames;
967         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
968         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
969         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
970                         quote, p, quote, appData.directory[i],
971                         useNick ? " -fn \"" : "",
972                         useNick ? nickName : "",
973                         useNick ? "\"" : "",
974                         v1 ? " -firstProtocolVersion 1" : "",
975                         hasBook ? "" : " -fNoOwnBookUCI",
976                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
977                         storeVariant ? " -variant " : "",
978                         storeVariant ? VariantName(gameInfo.variant) : "");
979         if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
980         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
981         if(insert != q) insert[-1] = NULLCHAR;
982         snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
983         if(q)   free(q);
984         FloatToFront(&appData.recentEngineList, buf);
985     }
986     ReplaceEngine(cps, i);
987 }
988
989 void
990 InitTimeControls ()
991 {
992     int matched, min, sec;
993     /*
994      * Parse timeControl resource
995      */
996     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
997                           appData.movesPerSession)) {
998         char buf[MSG_SIZ];
999         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
1000         DisplayFatalError(buf, 0, 2);
1001     }
1002
1003     /*
1004      * Parse searchTime resource
1005      */
1006     if (*appData.searchTime != NULLCHAR) {
1007         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
1008         if (matched == 1) {
1009             searchTime = min * 60;
1010         } else if (matched == 2) {
1011             searchTime = min * 60 + sec;
1012         } else {
1013             char buf[MSG_SIZ];
1014             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
1015             DisplayFatalError(buf, 0, 2);
1016         }
1017     }
1018 }
1019
1020 void
1021 InitBackEnd1 ()
1022 {
1023
1024     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1025     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1026
1027     GetTimeMark(&programStartTime);
1028     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1029     appData.seedBase = random() + (random()<<15);
1030     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1031
1032     ClearProgramStats();
1033     programStats.ok_to_send = 1;
1034     programStats.seen_stat = 0;
1035
1036     /*
1037      * Initialize game list
1038      */
1039     ListNew(&gameList);
1040
1041
1042     /*
1043      * Internet chess server status
1044      */
1045     if (appData.icsActive) {
1046         appData.matchMode = FALSE;
1047         appData.matchGames = 0;
1048 #if ZIPPY
1049         appData.noChessProgram = !appData.zippyPlay;
1050 #else
1051         appData.zippyPlay = FALSE;
1052         appData.zippyTalk = FALSE;
1053         appData.noChessProgram = TRUE;
1054 #endif
1055         if (*appData.icsHelper != NULLCHAR) {
1056             appData.useTelnet = TRUE;
1057             appData.telnetProgram = appData.icsHelper;
1058         }
1059     } else {
1060         appData.zippyTalk = appData.zippyPlay = FALSE;
1061     }
1062
1063     /* [AS] Initialize pv info list [HGM] and game state */
1064     {
1065         int i, j;
1066
1067         for( i=0; i<=framePtr; i++ ) {
1068             pvInfoList[i].depth = -1;
1069             boards[i][EP_STATUS] = EP_NONE;
1070             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1071         }
1072     }
1073
1074     InitTimeControls();
1075
1076     /* [AS] Adjudication threshold */
1077     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1078
1079     InitEngine(&first, 0);
1080     InitEngine(&second, 1);
1081     CommonEngineInit();
1082
1083     pairing.which = "pairing"; // pairing engine
1084     pairing.pr = NoProc;
1085     pairing.isr = NULL;
1086     pairing.program = appData.pairingEngine;
1087     pairing.host = "localhost";
1088     pairing.dir = ".";
1089
1090     if (appData.icsActive) {
1091         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1092     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1093         appData.clockMode = FALSE;
1094         first.sendTime = second.sendTime = 0;
1095     }
1096
1097 #if ZIPPY
1098     /* Override some settings from environment variables, for backward
1099        compatibility.  Unfortunately it's not feasible to have the env
1100        vars just set defaults, at least in xboard.  Ugh.
1101     */
1102     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1103       ZippyInit();
1104     }
1105 #endif
1106
1107     if (!appData.icsActive) {
1108       char buf[MSG_SIZ];
1109       int len;
1110
1111       /* Check for variants that are supported only in ICS mode,
1112          or not at all.  Some that are accepted here nevertheless
1113          have bugs; see comments below.
1114       */
1115       VariantClass variant = StringToVariant(appData.variant);
1116       switch (variant) {
1117       case VariantBughouse:     /* need four players and two boards */
1118       case VariantKriegspiel:   /* need to hide pieces and move details */
1119         /* case VariantFischeRandom: (Fabien: moved below) */
1120         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1121         if( (len >= MSG_SIZ) && appData.debugMode )
1122           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1123
1124         DisplayFatalError(buf, 0, 2);
1125         return;
1126
1127       case VariantUnknown:
1128       case VariantLoadable:
1129       case Variant29:
1130       case Variant30:
1131       case Variant31:
1132       case Variant32:
1133       case Variant33:
1134       case Variant34:
1135       case Variant35:
1136       case Variant36:
1137       default:
1138         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1139         if( (len >= MSG_SIZ) && appData.debugMode )
1140           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1141
1142         DisplayFatalError(buf, 0, 2);
1143         return;
1144
1145       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1146       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1147       case VariantGothic:     /* [HGM] should work */
1148       case VariantCapablanca: /* [HGM] should work */
1149       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1150       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1151       case VariantKnightmate: /* [HGM] should work */
1152       case VariantCylinder:   /* [HGM] untested */
1153       case VariantFalcon:     /* [HGM] untested */
1154       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1155                                  offboard interposition not understood */
1156       case VariantNormal:     /* definitely works! */
1157       case VariantWildCastle: /* pieces not automatically shuffled */
1158       case VariantNoCastle:   /* pieces not automatically shuffled */
1159       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1160       case VariantLosers:     /* should work except for win condition,
1161                                  and doesn't know captures are mandatory */
1162       case VariantSuicide:    /* should work except for win condition,
1163                                  and doesn't know captures are mandatory */
1164       case VariantGiveaway:   /* should work except for win condition,
1165                                  and doesn't know captures are mandatory */
1166       case VariantTwoKings:   /* should work */
1167       case VariantAtomic:     /* should work except for win condition */
1168       case Variant3Check:     /* should work except for win condition */
1169       case VariantShatranj:   /* should work except for all win conditions */
1170       case VariantMakruk:     /* should work except for draw countdown */
1171       case VariantASEAN :     /* should work except for draw countdown */
1172       case VariantBerolina:   /* might work if TestLegality is off */
1173       case VariantCapaRandom: /* should work */
1174       case VariantJanus:      /* should work */
1175       case VariantSuper:      /* experimental */
1176       case VariantGreat:      /* experimental, requires legality testing to be off */
1177       case VariantSChess:     /* S-Chess, should work */
1178       case VariantGrand:      /* should work */
1179       case VariantSpartan:    /* should work */
1180         break;
1181       }
1182     }
1183
1184 }
1185
1186 int
1187 NextIntegerFromString (char ** str, long * value)
1188 {
1189     int result = -1;
1190     char * s = *str;
1191
1192     while( *s == ' ' || *s == '\t' ) {
1193         s++;
1194     }
1195
1196     *value = 0;
1197
1198     if( *s >= '0' && *s <= '9' ) {
1199         while( *s >= '0' && *s <= '9' ) {
1200             *value = *value * 10 + (*s - '0');
1201             s++;
1202         }
1203
1204         result = 0;
1205     }
1206
1207     *str = s;
1208
1209     return result;
1210 }
1211
1212 int
1213 NextTimeControlFromString (char ** str, long * value)
1214 {
1215     long temp;
1216     int result = NextIntegerFromString( str, &temp );
1217
1218     if( result == 0 ) {
1219         *value = temp * 60; /* Minutes */
1220         if( **str == ':' ) {
1221             (*str)++;
1222             result = NextIntegerFromString( str, &temp );
1223             *value += temp; /* Seconds */
1224         }
1225     }
1226
1227     return result;
1228 }
1229
1230 int
1231 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1232 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1233     int result = -1, type = 0; long temp, temp2;
1234
1235     if(**str != ':') return -1; // old params remain in force!
1236     (*str)++;
1237     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1238     if( NextIntegerFromString( str, &temp ) ) return -1;
1239     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1240
1241     if(**str != '/') {
1242         /* time only: incremental or sudden-death time control */
1243         if(**str == '+') { /* increment follows; read it */
1244             (*str)++;
1245             if(**str == '!') type = *(*str)++; // Bronstein TC
1246             if(result = NextIntegerFromString( str, &temp2)) return -1;
1247             *inc = temp2 * 1000;
1248             if(**str == '.') { // read fraction of increment
1249                 char *start = ++(*str);
1250                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1251                 temp2 *= 1000;
1252                 while(start++ < *str) temp2 /= 10;
1253                 *inc += temp2;
1254             }
1255         } else *inc = 0;
1256         *moves = 0; *tc = temp * 1000; *incType = type;
1257         return 0;
1258     }
1259
1260     (*str)++; /* classical time control */
1261     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1262
1263     if(result == 0) {
1264         *moves = temp;
1265         *tc    = temp2 * 1000;
1266         *inc   = 0;
1267         *incType = type;
1268     }
1269     return result;
1270 }
1271
1272 int
1273 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1274 {   /* [HGM] get time to add from the multi-session time-control string */
1275     int incType, moves=1; /* kludge to force reading of first session */
1276     long time, increment;
1277     char *s = tcString;
1278
1279     if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1280     do {
1281         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1282         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1283         if(movenr == -1) return time;    /* last move before new session     */
1284         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1285         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1286         if(!moves) return increment;     /* current session is incremental   */
1287         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1288     } while(movenr >= -1);               /* try again for next session       */
1289
1290     return 0; // no new time quota on this move
1291 }
1292
1293 int
1294 ParseTimeControl (char *tc, float ti, int mps)
1295 {
1296   long tc1;
1297   long tc2;
1298   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1299   int min, sec=0;
1300
1301   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1302   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1303       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1304   if(ti > 0) {
1305
1306     if(mps)
1307       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1308     else
1309       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1310   } else {
1311     if(mps)
1312       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1313     else
1314       snprintf(buf, MSG_SIZ, ":%s", mytc);
1315   }
1316   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1317
1318   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1319     return FALSE;
1320   }
1321
1322   if( *tc == '/' ) {
1323     /* Parse second time control */
1324     tc++;
1325
1326     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1327       return FALSE;
1328     }
1329
1330     if( tc2 == 0 ) {
1331       return FALSE;
1332     }
1333
1334     timeControl_2 = tc2 * 1000;
1335   }
1336   else {
1337     timeControl_2 = 0;
1338   }
1339
1340   if( tc1 == 0 ) {
1341     return FALSE;
1342   }
1343
1344   timeControl = tc1 * 1000;
1345
1346   if (ti >= 0) {
1347     timeIncrement = ti * 1000;  /* convert to ms */
1348     movesPerSession = 0;
1349   } else {
1350     timeIncrement = 0;
1351     movesPerSession = mps;
1352   }
1353   return TRUE;
1354 }
1355
1356 void
1357 InitBackEnd2 ()
1358 {
1359     if (appData.debugMode) {
1360 #    ifdef __GIT_VERSION
1361       fprintf(debugFP, "Version: %s (%s)\n", programVersion, __GIT_VERSION);
1362 #    else
1363       fprintf(debugFP, "Version: %s\n", programVersion);
1364 #    endif
1365     }
1366     ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1367
1368     set_cont_sequence(appData.wrapContSeq);
1369     if (appData.matchGames > 0) {
1370         appData.matchMode = TRUE;
1371     } else if (appData.matchMode) {
1372         appData.matchGames = 1;
1373     }
1374     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1375         appData.matchGames = appData.sameColorGames;
1376     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1377         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1378         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1379     }
1380     Reset(TRUE, FALSE);
1381     if (appData.noChessProgram || first.protocolVersion == 1) {
1382       InitBackEnd3();
1383     } else {
1384       /* kludge: allow timeout for initial "feature" commands */
1385       FreezeUI();
1386       DisplayMessage("", _("Starting chess program"));
1387       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1388     }
1389 }
1390
1391 int
1392 CalculateIndex (int index, int gameNr)
1393 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1394     int res;
1395     if(index > 0) return index; // fixed nmber
1396     if(index == 0) return 1;
1397     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1398     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1399     return res;
1400 }
1401
1402 int
1403 LoadGameOrPosition (int gameNr)
1404 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1405     if (*appData.loadGameFile != NULLCHAR) {
1406         if (!LoadGameFromFile(appData.loadGameFile,
1407                 CalculateIndex(appData.loadGameIndex, gameNr),
1408                               appData.loadGameFile, FALSE)) {
1409             DisplayFatalError(_("Bad game file"), 0, 1);
1410             return 0;
1411         }
1412     } else if (*appData.loadPositionFile != NULLCHAR) {
1413         if (!LoadPositionFromFile(appData.loadPositionFile,
1414                 CalculateIndex(appData.loadPositionIndex, gameNr),
1415                                   appData.loadPositionFile)) {
1416             DisplayFatalError(_("Bad position file"), 0, 1);
1417             return 0;
1418         }
1419     }
1420     return 1;
1421 }
1422
1423 void
1424 ReserveGame (int gameNr, char resChar)
1425 {
1426     FILE *tf = fopen(appData.tourneyFile, "r+");
1427     char *p, *q, c, buf[MSG_SIZ];
1428     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1429     safeStrCpy(buf, lastMsg, MSG_SIZ);
1430     DisplayMessage(_("Pick new game"), "");
1431     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1432     ParseArgsFromFile(tf);
1433     p = q = appData.results;
1434     if(appData.debugMode) {
1435       char *r = appData.participants;
1436       fprintf(debugFP, "results = '%s'\n", p);
1437       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1438       fprintf(debugFP, "\n");
1439     }
1440     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1441     nextGame = q - p;
1442     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1443     safeStrCpy(q, p, strlen(p) + 2);
1444     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1445     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1446     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1447         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1448         q[nextGame] = '*';
1449     }
1450     fseek(tf, -(strlen(p)+4), SEEK_END);
1451     c = fgetc(tf);
1452     if(c != '"') // depending on DOS or Unix line endings we can be one off
1453          fseek(tf, -(strlen(p)+2), SEEK_END);
1454     else fseek(tf, -(strlen(p)+3), SEEK_END);
1455     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1456     DisplayMessage(buf, "");
1457     free(p); appData.results = q;
1458     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1459        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1460       int round = appData.defaultMatchGames * appData.tourneyType;
1461       if(gameNr < 0 || appData.tourneyType < 1 ||  // gauntlet engine can always stay loaded as first engine
1462          appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1463         UnloadEngine(&first);  // next game belongs to other pairing;
1464         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1465     }
1466     if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1467 }
1468
1469 void
1470 MatchEvent (int mode)
1471 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1472         int dummy;
1473         if(matchMode) { // already in match mode: switch it off
1474             abortMatch = TRUE;
1475             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1476             return;
1477         }
1478 //      if(gameMode != BeginningOfGame) {
1479 //          DisplayError(_("You can only start a match from the initial position."), 0);
1480 //          return;
1481 //      }
1482         abortMatch = FALSE;
1483         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1484         /* Set up machine vs. machine match */
1485         nextGame = 0;
1486         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1487         if(appData.tourneyFile[0]) {
1488             ReserveGame(-1, 0);
1489             if(nextGame > appData.matchGames) {
1490                 char buf[MSG_SIZ];
1491                 if(strchr(appData.results, '*') == NULL) {
1492                     FILE *f;
1493                     appData.tourneyCycles++;
1494                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1495                         fclose(f);
1496                         NextTourneyGame(-1, &dummy);
1497                         ReserveGame(-1, 0);
1498                         if(nextGame <= appData.matchGames) {
1499                             DisplayNote(_("You restarted an already completed tourney.\nOne more cycle will now be added to it.\nGames commence in 10 sec."));
1500                             matchMode = mode;
1501                             ScheduleDelayedEvent(NextMatchGame, 10000);
1502                             return;
1503                         }
1504                     }
1505                 }
1506                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1507                 DisplayError(buf, 0);
1508                 appData.tourneyFile[0] = 0;
1509                 return;
1510             }
1511         } else
1512         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1513             DisplayFatalError(_("Can't have a match with no chess programs"),
1514                               0, 2);
1515             return;
1516         }
1517         matchMode = mode;
1518         matchGame = roundNr = 1;
1519         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1520         NextMatchGame();
1521 }
1522
1523 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1524
1525 void
1526 InitBackEnd3 P((void))
1527 {
1528     GameMode initialMode;
1529     char buf[MSG_SIZ];
1530     int err, len;
1531
1532     InitChessProgram(&first, startedFromSetupPosition);
1533
1534     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1535         free(programVersion);
1536         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1537         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1538         FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1539     }
1540
1541     if (appData.icsActive) {
1542 #ifdef WIN32
1543         /* [DM] Make a console window if needed [HGM] merged ifs */
1544         ConsoleCreate();
1545 #endif
1546         err = establish();
1547         if (err != 0)
1548           {
1549             if (*appData.icsCommPort != NULLCHAR)
1550               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1551                              appData.icsCommPort);
1552             else
1553               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1554                         appData.icsHost, appData.icsPort);
1555
1556             if( (len >= MSG_SIZ) && appData.debugMode )
1557               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1558
1559             DisplayFatalError(buf, err, 1);
1560             return;
1561         }
1562         SetICSMode();
1563         telnetISR =
1564           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1565         fromUserISR =
1566           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1567         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1568             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1569     } else if (appData.noChessProgram) {
1570         SetNCPMode();
1571     } else {
1572         SetGNUMode();
1573     }
1574
1575     if (*appData.cmailGameName != NULLCHAR) {
1576         SetCmailMode();
1577         OpenLoopback(&cmailPR);
1578         cmailISR =
1579           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1580     }
1581
1582     ThawUI();
1583     DisplayMessage("", "");
1584     if (StrCaseCmp(appData.initialMode, "") == 0) {
1585       initialMode = BeginningOfGame;
1586       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1587         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1588         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1589         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1590         ModeHighlight();
1591       }
1592     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1593       initialMode = TwoMachinesPlay;
1594     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1595       initialMode = AnalyzeFile;
1596     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1597       initialMode = AnalyzeMode;
1598     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1599       initialMode = MachinePlaysWhite;
1600     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1601       initialMode = MachinePlaysBlack;
1602     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1603       initialMode = EditGame;
1604     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1605       initialMode = EditPosition;
1606     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1607       initialMode = Training;
1608     } else {
1609       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1610       if( (len >= MSG_SIZ) && appData.debugMode )
1611         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1612
1613       DisplayFatalError(buf, 0, 2);
1614       return;
1615     }
1616
1617     if (appData.matchMode) {
1618         if(appData.tourneyFile[0]) { // start tourney from command line
1619             FILE *f;
1620             if(f = fopen(appData.tourneyFile, "r")) {
1621                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1622                 fclose(f);
1623                 appData.clockMode = TRUE;
1624                 SetGNUMode();
1625             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1626         }
1627         MatchEvent(TRUE);
1628     } else if (*appData.cmailGameName != NULLCHAR) {
1629         /* Set up cmail mode */
1630         ReloadCmailMsgEvent(TRUE);
1631     } else {
1632         /* Set up other modes */
1633         if (initialMode == AnalyzeFile) {
1634           if (*appData.loadGameFile == NULLCHAR) {
1635             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1636             return;
1637           }
1638         }
1639         if (*appData.loadGameFile != NULLCHAR) {
1640             (void) LoadGameFromFile(appData.loadGameFile,
1641                                     appData.loadGameIndex,
1642                                     appData.loadGameFile, TRUE);
1643         } else if (*appData.loadPositionFile != NULLCHAR) {
1644             (void) LoadPositionFromFile(appData.loadPositionFile,
1645                                         appData.loadPositionIndex,
1646                                         appData.loadPositionFile);
1647             /* [HGM] try to make self-starting even after FEN load */
1648             /* to allow automatic setup of fairy variants with wtm */
1649             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1650                 gameMode = BeginningOfGame;
1651                 setboardSpoiledMachineBlack = 1;
1652             }
1653             /* [HGM] loadPos: make that every new game uses the setup */
1654             /* from file as long as we do not switch variant          */
1655             if(!blackPlaysFirst) {
1656                 startedFromPositionFile = TRUE;
1657                 CopyBoard(filePosition, boards[0]);
1658             }
1659         }
1660         if (initialMode == AnalyzeMode) {
1661           if (appData.noChessProgram) {
1662             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1663             return;
1664           }
1665           if (appData.icsActive) {
1666             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1667             return;
1668           }
1669           AnalyzeModeEvent();
1670         } else if (initialMode == AnalyzeFile) {
1671           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1672           ShowThinkingEvent();
1673           AnalyzeFileEvent();
1674           AnalysisPeriodicEvent(1);
1675         } else if (initialMode == MachinePlaysWhite) {
1676           if (appData.noChessProgram) {
1677             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1678                               0, 2);
1679             return;
1680           }
1681           if (appData.icsActive) {
1682             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1683                               0, 2);
1684             return;
1685           }
1686           MachineWhiteEvent();
1687         } else if (initialMode == MachinePlaysBlack) {
1688           if (appData.noChessProgram) {
1689             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1690                               0, 2);
1691             return;
1692           }
1693           if (appData.icsActive) {
1694             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1695                               0, 2);
1696             return;
1697           }
1698           MachineBlackEvent();
1699         } else if (initialMode == TwoMachinesPlay) {
1700           if (appData.noChessProgram) {
1701             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1702                               0, 2);
1703             return;
1704           }
1705           if (appData.icsActive) {
1706             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1707                               0, 2);
1708             return;
1709           }
1710           TwoMachinesEvent();
1711         } else if (initialMode == EditGame) {
1712           EditGameEvent();
1713         } else if (initialMode == EditPosition) {
1714           EditPositionEvent();
1715         } else if (initialMode == Training) {
1716           if (*appData.loadGameFile == NULLCHAR) {
1717             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1718             return;
1719           }
1720           TrainingEvent();
1721         }
1722     }
1723 }
1724
1725 void
1726 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1727 {
1728     DisplayBook(current+1);
1729
1730     MoveHistorySet( movelist, first, last, current, pvInfoList );
1731
1732     EvalGraphSet( first, last, current, pvInfoList );
1733
1734     MakeEngineOutputTitle();
1735 }
1736
1737 /*
1738  * Establish will establish a contact to a remote host.port.
1739  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1740  *  used to talk to the host.
1741  * Returns 0 if okay, error code if not.
1742  */
1743 int
1744 establish ()
1745 {
1746     char buf[MSG_SIZ];
1747
1748     if (*appData.icsCommPort != NULLCHAR) {
1749         /* Talk to the host through a serial comm port */
1750         return OpenCommPort(appData.icsCommPort, &icsPR);
1751
1752     } else if (*appData.gateway != NULLCHAR) {
1753         if (*appData.remoteShell == NULLCHAR) {
1754             /* Use the rcmd protocol to run telnet program on a gateway host */
1755             snprintf(buf, sizeof(buf), "%s %s %s",
1756                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1757             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1758
1759         } else {
1760             /* Use the rsh program to run telnet program on a gateway host */
1761             if (*appData.remoteUser == NULLCHAR) {
1762                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1763                         appData.gateway, appData.telnetProgram,
1764                         appData.icsHost, appData.icsPort);
1765             } else {
1766                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1767                         appData.remoteShell, appData.gateway,
1768                         appData.remoteUser, appData.telnetProgram,
1769                         appData.icsHost, appData.icsPort);
1770             }
1771             return StartChildProcess(buf, "", &icsPR);
1772
1773         }
1774     } else if (appData.useTelnet) {
1775         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1776
1777     } else {
1778         /* TCP socket interface differs somewhat between
1779            Unix and NT; handle details in the front end.
1780            */
1781         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1782     }
1783 }
1784
1785 void
1786 EscapeExpand (char *p, char *q)
1787 {       // [HGM] initstring: routine to shape up string arguments
1788         while(*p++ = *q++) if(p[-1] == '\\')
1789             switch(*q++) {
1790                 case 'n': p[-1] = '\n'; break;
1791                 case 'r': p[-1] = '\r'; break;
1792                 case 't': p[-1] = '\t'; break;
1793                 case '\\': p[-1] = '\\'; break;
1794                 case 0: *p = 0; return;
1795                 default: p[-1] = q[-1]; break;
1796             }
1797 }
1798
1799 void
1800 show_bytes (FILE *fp, char *buf, int count)
1801 {
1802     while (count--) {
1803         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1804             fprintf(fp, "\\%03o", *buf & 0xff);
1805         } else {
1806             putc(*buf, fp);
1807         }
1808         buf++;
1809     }
1810     fflush(fp);
1811 }
1812
1813 /* Returns an errno value */
1814 int
1815 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1816 {
1817     char buf[8192], *p, *q, *buflim;
1818     int left, newcount, outcount;
1819
1820     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1821         *appData.gateway != NULLCHAR) {
1822         if (appData.debugMode) {
1823             fprintf(debugFP, ">ICS: ");
1824             show_bytes(debugFP, message, count);
1825             fprintf(debugFP, "\n");
1826         }
1827         return OutputToProcess(pr, message, count, outError);
1828     }
1829
1830     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1831     p = message;
1832     q = buf;
1833     left = count;
1834     newcount = 0;
1835     while (left) {
1836         if (q >= buflim) {
1837             if (appData.debugMode) {
1838                 fprintf(debugFP, ">ICS: ");
1839                 show_bytes(debugFP, buf, newcount);
1840                 fprintf(debugFP, "\n");
1841             }
1842             outcount = OutputToProcess(pr, buf, newcount, outError);
1843             if (outcount < newcount) return -1; /* to be sure */
1844             q = buf;
1845             newcount = 0;
1846         }
1847         if (*p == '\n') {
1848             *q++ = '\r';
1849             newcount++;
1850         } else if (((unsigned char) *p) == TN_IAC) {
1851             *q++ = (char) TN_IAC;
1852             newcount ++;
1853         }
1854         *q++ = *p++;
1855         newcount++;
1856         left--;
1857     }
1858     if (appData.debugMode) {
1859         fprintf(debugFP, ">ICS: ");
1860         show_bytes(debugFP, buf, newcount);
1861         fprintf(debugFP, "\n");
1862     }
1863     outcount = OutputToProcess(pr, buf, newcount, outError);
1864     if (outcount < newcount) return -1; /* to be sure */
1865     return count;
1866 }
1867
1868 void
1869 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1870 {
1871     int outError, outCount;
1872     static int gotEof = 0;
1873     static FILE *ini;
1874
1875     /* Pass data read from player on to ICS */
1876     if (count > 0) {
1877         gotEof = 0;
1878         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1879         if (outCount < count) {
1880             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1881         }
1882         if(have_sent_ICS_logon == 2) {
1883           if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1884             fprintf(ini, "%s", message);
1885             have_sent_ICS_logon = 3;
1886           } else
1887             have_sent_ICS_logon = 1;
1888         } else if(have_sent_ICS_logon == 3) {
1889             fprintf(ini, "%s", message);
1890             fclose(ini);
1891           have_sent_ICS_logon = 1;
1892         }
1893     } else if (count < 0) {
1894         RemoveInputSource(isr);
1895         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1896     } else if (gotEof++ > 0) {
1897         RemoveInputSource(isr);
1898         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1899     }
1900 }
1901
1902 void
1903 KeepAlive ()
1904 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1905     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1906     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1907     SendToICS("date\n");
1908     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1909 }
1910
1911 /* added routine for printf style output to ics */
1912 void
1913 ics_printf (char *format, ...)
1914 {
1915     char buffer[MSG_SIZ];
1916     va_list args;
1917
1918     va_start(args, format);
1919     vsnprintf(buffer, sizeof(buffer), format, args);
1920     buffer[sizeof(buffer)-1] = '\0';
1921     SendToICS(buffer);
1922     va_end(args);
1923 }
1924
1925 void
1926 SendToICS (char *s)
1927 {
1928     int count, outCount, outError;
1929
1930     if (icsPR == NoProc) return;
1931
1932     count = strlen(s);
1933     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1934     if (outCount < count) {
1935         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1936     }
1937 }
1938
1939 /* This is used for sending logon scripts to the ICS. Sending
1940    without a delay causes problems when using timestamp on ICC
1941    (at least on my machine). */
1942 void
1943 SendToICSDelayed (char *s, long msdelay)
1944 {
1945     int count, outCount, outError;
1946
1947     if (icsPR == NoProc) return;
1948
1949     count = strlen(s);
1950     if (appData.debugMode) {
1951         fprintf(debugFP, ">ICS: ");
1952         show_bytes(debugFP, s, count);
1953         fprintf(debugFP, "\n");
1954     }
1955     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1956                                       msdelay);
1957     if (outCount < count) {
1958         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1959     }
1960 }
1961
1962
1963 /* Remove all highlighting escape sequences in s
1964    Also deletes any suffix starting with '('
1965    */
1966 char *
1967 StripHighlightAndTitle (char *s)
1968 {
1969     static char retbuf[MSG_SIZ];
1970     char *p = retbuf;
1971
1972     while (*s != NULLCHAR) {
1973         while (*s == '\033') {
1974             while (*s != NULLCHAR && !isalpha(*s)) s++;
1975             if (*s != NULLCHAR) s++;
1976         }
1977         while (*s != NULLCHAR && *s != '\033') {
1978             if (*s == '(' || *s == '[') {
1979                 *p = NULLCHAR;
1980                 return retbuf;
1981             }
1982             *p++ = *s++;
1983         }
1984     }
1985     *p = NULLCHAR;
1986     return retbuf;
1987 }
1988
1989 /* Remove all highlighting escape sequences in s */
1990 char *
1991 StripHighlight (char *s)
1992 {
1993     static char retbuf[MSG_SIZ];
1994     char *p = retbuf;
1995
1996     while (*s != NULLCHAR) {
1997         while (*s == '\033') {
1998             while (*s != NULLCHAR && !isalpha(*s)) s++;
1999             if (*s != NULLCHAR) s++;
2000         }
2001         while (*s != NULLCHAR && *s != '\033') {
2002             *p++ = *s++;
2003         }
2004     }
2005     *p = NULLCHAR;
2006     return retbuf;
2007 }
2008
2009 char engineVariant[MSG_SIZ];
2010 char *variantNames[] = VARIANT_NAMES;
2011 char *
2012 VariantName (VariantClass v)
2013 {
2014     if(v == VariantUnknown || *engineVariant) return engineVariant;
2015     return variantNames[v];
2016 }
2017
2018
2019 /* Identify a variant from the strings the chess servers use or the
2020    PGN Variant tag names we use. */
2021 VariantClass
2022 StringToVariant (char *e)
2023 {
2024     char *p;
2025     int wnum = -1;
2026     VariantClass v = VariantNormal;
2027     int i, found = FALSE;
2028     char buf[MSG_SIZ];
2029     int len;
2030
2031     if (!e) return v;
2032
2033     /* [HGM] skip over optional board-size prefixes */
2034     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
2035         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
2036         while( *e++ != '_');
2037     }
2038
2039     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2040         v = VariantNormal;
2041         found = TRUE;
2042     } else
2043     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2044       if (StrCaseStr(e, variantNames[i])) {
2045         v = (VariantClass) i;
2046         found = TRUE;
2047         break;
2048       }
2049     }
2050
2051     if (!found) {
2052       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2053           || StrCaseStr(e, "wild/fr")
2054           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2055         v = VariantFischeRandom;
2056       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2057                  (i = 1, p = StrCaseStr(e, "w"))) {
2058         p += i;
2059         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2060         if (isdigit(*p)) {
2061           wnum = atoi(p);
2062         } else {
2063           wnum = -1;
2064         }
2065         switch (wnum) {
2066         case 0: /* FICS only, actually */
2067         case 1:
2068           /* Castling legal even if K starts on d-file */
2069           v = VariantWildCastle;
2070           break;
2071         case 2:
2072         case 3:
2073         case 4:
2074           /* Castling illegal even if K & R happen to start in
2075              normal positions. */
2076           v = VariantNoCastle;
2077           break;
2078         case 5:
2079         case 7:
2080         case 8:
2081         case 10:
2082         case 11:
2083         case 12:
2084         case 13:
2085         case 14:
2086         case 15:
2087         case 18:
2088         case 19:
2089           /* Castling legal iff K & R start in normal positions */
2090           v = VariantNormal;
2091           break;
2092         case 6:
2093         case 20:
2094         case 21:
2095           /* Special wilds for position setup; unclear what to do here */
2096           v = VariantLoadable;
2097           break;
2098         case 9:
2099           /* Bizarre ICC game */
2100           v = VariantTwoKings;
2101           break;
2102         case 16:
2103           v = VariantKriegspiel;
2104           break;
2105         case 17:
2106           v = VariantLosers;
2107           break;
2108         case 22:
2109           v = VariantFischeRandom;
2110           break;
2111         case 23:
2112           v = VariantCrazyhouse;
2113           break;
2114         case 24:
2115           v = VariantBughouse;
2116           break;
2117         case 25:
2118           v = Variant3Check;
2119           break;
2120         case 26:
2121           /* Not quite the same as FICS suicide! */
2122           v = VariantGiveaway;
2123           break;
2124         case 27:
2125           v = VariantAtomic;
2126           break;
2127         case 28:
2128           v = VariantShatranj;
2129           break;
2130
2131         /* Temporary names for future ICC types.  The name *will* change in
2132            the next xboard/WinBoard release after ICC defines it. */
2133         case 29:
2134           v = Variant29;
2135           break;
2136         case 30:
2137           v = Variant30;
2138           break;
2139         case 31:
2140           v = Variant31;
2141           break;
2142         case 32:
2143           v = Variant32;
2144           break;
2145         case 33:
2146           v = Variant33;
2147           break;
2148         case 34:
2149           v = Variant34;
2150           break;
2151         case 35:
2152           v = Variant35;
2153           break;
2154         case 36:
2155           v = Variant36;
2156           break;
2157         case 37:
2158           v = VariantShogi;
2159           break;
2160         case 38:
2161           v = VariantXiangqi;
2162           break;
2163         case 39:
2164           v = VariantCourier;
2165           break;
2166         case 40:
2167           v = VariantGothic;
2168           break;
2169         case 41:
2170           v = VariantCapablanca;
2171           break;
2172         case 42:
2173           v = VariantKnightmate;
2174           break;
2175         case 43:
2176           v = VariantFairy;
2177           break;
2178         case 44:
2179           v = VariantCylinder;
2180           break;
2181         case 45:
2182           v = VariantFalcon;
2183           break;
2184         case 46:
2185           v = VariantCapaRandom;
2186           break;
2187         case 47:
2188           v = VariantBerolina;
2189           break;
2190         case 48:
2191           v = VariantJanus;
2192           break;
2193         case 49:
2194           v = VariantSuper;
2195           break;
2196         case 50:
2197           v = VariantGreat;
2198           break;
2199         case -1:
2200           /* Found "wild" or "w" in the string but no number;
2201              must assume it's normal chess. */
2202           v = VariantNormal;
2203           break;
2204         default:
2205           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2206           if( (len >= MSG_SIZ) && appData.debugMode )
2207             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2208
2209           DisplayError(buf, 0);
2210           v = VariantUnknown;
2211           break;
2212         }
2213       }
2214     }
2215     if (appData.debugMode) {
2216       fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
2217               e, wnum, VariantName(v));
2218     }
2219     return v;
2220 }
2221
2222 static int leftover_start = 0, leftover_len = 0;
2223 char star_match[STAR_MATCH_N][MSG_SIZ];
2224
2225 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2226    advance *index beyond it, and set leftover_start to the new value of
2227    *index; else return FALSE.  If pattern contains the character '*', it
2228    matches any sequence of characters not containing '\r', '\n', or the
2229    character following the '*' (if any), and the matched sequence(s) are
2230    copied into star_match.
2231    */
2232 int
2233 looking_at ( char *buf, int *index, char *pattern)
2234 {
2235     char *bufp = &buf[*index], *patternp = pattern;
2236     int star_count = 0;
2237     char *matchp = star_match[0];
2238
2239     for (;;) {
2240         if (*patternp == NULLCHAR) {
2241             *index = leftover_start = bufp - buf;
2242             *matchp = NULLCHAR;
2243             return TRUE;
2244         }
2245         if (*bufp == NULLCHAR) return FALSE;
2246         if (*patternp == '*') {
2247             if (*bufp == *(patternp + 1)) {
2248                 *matchp = NULLCHAR;
2249                 matchp = star_match[++star_count];
2250                 patternp += 2;
2251                 bufp++;
2252                 continue;
2253             } else if (*bufp == '\n' || *bufp == '\r') {
2254                 patternp++;
2255                 if (*patternp == NULLCHAR)
2256                   continue;
2257                 else
2258                   return FALSE;
2259             } else {
2260                 *matchp++ = *bufp++;
2261                 continue;
2262             }
2263         }
2264         if (*patternp != *bufp) return FALSE;
2265         patternp++;
2266         bufp++;
2267     }
2268 }
2269
2270 void
2271 SendToPlayer (char *data, int length)
2272 {
2273     int error, outCount;
2274     outCount = OutputToProcess(NoProc, data, length, &error);
2275     if (outCount < length) {
2276         DisplayFatalError(_("Error writing to display"), error, 1);
2277     }
2278 }
2279
2280 void
2281 PackHolding (char packed[], char *holding)
2282 {
2283     char *p = holding;
2284     char *q = packed;
2285     int runlength = 0;
2286     int curr = 9999;
2287     do {
2288         if (*p == curr) {
2289             runlength++;
2290         } else {
2291             switch (runlength) {
2292               case 0:
2293                 break;
2294               case 1:
2295                 *q++ = curr;
2296                 break;
2297               case 2:
2298                 *q++ = curr;
2299                 *q++ = curr;
2300                 break;
2301               default:
2302                 sprintf(q, "%d", runlength);
2303                 while (*q) q++;
2304                 *q++ = curr;
2305                 break;
2306             }
2307             runlength = 1;
2308             curr = *p;
2309         }
2310     } while (*p++);
2311     *q = NULLCHAR;
2312 }
2313
2314 /* Telnet protocol requests from the front end */
2315 void
2316 TelnetRequest (unsigned char ddww, unsigned char option)
2317 {
2318     unsigned char msg[3];
2319     int outCount, outError;
2320
2321     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2322
2323     if (appData.debugMode) {
2324         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2325         switch (ddww) {
2326           case TN_DO:
2327             ddwwStr = "DO";
2328             break;
2329           case TN_DONT:
2330             ddwwStr = "DONT";
2331             break;
2332           case TN_WILL:
2333             ddwwStr = "WILL";
2334             break;
2335           case TN_WONT:
2336             ddwwStr = "WONT";
2337             break;
2338           default:
2339             ddwwStr = buf1;
2340             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2341             break;
2342         }
2343         switch (option) {
2344           case TN_ECHO:
2345             optionStr = "ECHO";
2346             break;
2347           default:
2348             optionStr = buf2;
2349             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2350             break;
2351         }
2352         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2353     }
2354     msg[0] = TN_IAC;
2355     msg[1] = ddww;
2356     msg[2] = option;
2357     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2358     if (outCount < 3) {
2359         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2360     }
2361 }
2362
2363 void
2364 DoEcho ()
2365 {
2366     if (!appData.icsActive) return;
2367     TelnetRequest(TN_DO, TN_ECHO);
2368 }
2369
2370 void
2371 DontEcho ()
2372 {
2373     if (!appData.icsActive) return;
2374     TelnetRequest(TN_DONT, TN_ECHO);
2375 }
2376
2377 void
2378 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2379 {
2380     /* put the holdings sent to us by the server on the board holdings area */
2381     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2382     char p;
2383     ChessSquare piece;
2384
2385     if(gameInfo.holdingsWidth < 2)  return;
2386     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2387         return; // prevent overwriting by pre-board holdings
2388
2389     if( (int)lowestPiece >= BlackPawn ) {
2390         holdingsColumn = 0;
2391         countsColumn = 1;
2392         holdingsStartRow = BOARD_HEIGHT-1;
2393         direction = -1;
2394     } else {
2395         holdingsColumn = BOARD_WIDTH-1;
2396         countsColumn = BOARD_WIDTH-2;
2397         holdingsStartRow = 0;
2398         direction = 1;
2399     }
2400
2401     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2402         board[i][holdingsColumn] = EmptySquare;
2403         board[i][countsColumn]   = (ChessSquare) 0;
2404     }
2405     while( (p=*holdings++) != NULLCHAR ) {
2406         piece = CharToPiece( ToUpper(p) );
2407         if(piece == EmptySquare) continue;
2408         /*j = (int) piece - (int) WhitePawn;*/
2409         j = PieceToNumber(piece);
2410         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2411         if(j < 0) continue;               /* should not happen */
2412         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2413         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2414         board[holdingsStartRow+j*direction][countsColumn]++;
2415     }
2416 }
2417
2418
2419 void
2420 VariantSwitch (Board board, VariantClass newVariant)
2421 {
2422    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2423    static Board oldBoard;
2424
2425    startedFromPositionFile = FALSE;
2426    if(gameInfo.variant == newVariant) return;
2427
2428    /* [HGM] This routine is called each time an assignment is made to
2429     * gameInfo.variant during a game, to make sure the board sizes
2430     * are set to match the new variant. If that means adding or deleting
2431     * holdings, we shift the playing board accordingly
2432     * This kludge is needed because in ICS observe mode, we get boards
2433     * of an ongoing game without knowing the variant, and learn about the
2434     * latter only later. This can be because of the move list we requested,
2435     * in which case the game history is refilled from the beginning anyway,
2436     * but also when receiving holdings of a crazyhouse game. In the latter
2437     * case we want to add those holdings to the already received position.
2438     */
2439
2440
2441    if (appData.debugMode) {
2442      fprintf(debugFP, "Switch board from %s to %s\n",
2443              VariantName(gameInfo.variant), VariantName(newVariant));
2444      setbuf(debugFP, NULL);
2445    }
2446    shuffleOpenings = 0;       /* [HGM] shuffle */
2447    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2448    switch(newVariant)
2449      {
2450      case VariantShogi:
2451        newWidth = 9;  newHeight = 9;
2452        gameInfo.holdingsSize = 7;
2453      case VariantBughouse:
2454      case VariantCrazyhouse:
2455        newHoldingsWidth = 2; break;
2456      case VariantGreat:
2457        newWidth = 10;
2458      case VariantSuper:
2459        newHoldingsWidth = 2;
2460        gameInfo.holdingsSize = 8;
2461        break;
2462      case VariantGothic:
2463      case VariantCapablanca:
2464      case VariantCapaRandom:
2465        newWidth = 10;
2466      default:
2467        newHoldingsWidth = gameInfo.holdingsSize = 0;
2468      };
2469
2470    if(newWidth  != gameInfo.boardWidth  ||
2471       newHeight != gameInfo.boardHeight ||
2472       newHoldingsWidth != gameInfo.holdingsWidth ) {
2473
2474      /* shift position to new playing area, if needed */
2475      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2476        for(i=0; i<BOARD_HEIGHT; i++)
2477          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2478            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2479              board[i][j];
2480        for(i=0; i<newHeight; i++) {
2481          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2482          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2483        }
2484      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2485        for(i=0; i<BOARD_HEIGHT; i++)
2486          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2487            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2488              board[i][j];
2489      }
2490      board[HOLDINGS_SET] = 0;
2491      gameInfo.boardWidth  = newWidth;
2492      gameInfo.boardHeight = newHeight;
2493      gameInfo.holdingsWidth = newHoldingsWidth;
2494      gameInfo.variant = newVariant;
2495      InitDrawingSizes(-2, 0);
2496    } else gameInfo.variant = newVariant;
2497    CopyBoard(oldBoard, board);   // remember correctly formatted board
2498      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2499    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2500 }
2501
2502 static int loggedOn = FALSE;
2503
2504 /*-- Game start info cache: --*/
2505 int gs_gamenum;
2506 char gs_kind[MSG_SIZ];
2507 static char player1Name[128] = "";
2508 static char player2Name[128] = "";
2509 static char cont_seq[] = "\n\\   ";
2510 static int player1Rating = -1;
2511 static int player2Rating = -1;
2512 /*----------------------------*/
2513
2514 ColorClass curColor = ColorNormal;
2515 int suppressKibitz = 0;
2516
2517 // [HGM] seekgraph
2518 Boolean soughtPending = FALSE;
2519 Boolean seekGraphUp;
2520 #define MAX_SEEK_ADS 200
2521 #define SQUARE 0x80
2522 char *seekAdList[MAX_SEEK_ADS];
2523 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2524 float tcList[MAX_SEEK_ADS];
2525 char colorList[MAX_SEEK_ADS];
2526 int nrOfSeekAds = 0;
2527 int minRating = 1010, maxRating = 2800;
2528 int hMargin = 10, vMargin = 20, h, w;
2529 extern int squareSize, lineGap;
2530
2531 void
2532 PlotSeekAd (int i)
2533 {
2534         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2535         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2536         if(r < minRating+100 && r >=0 ) r = minRating+100;
2537         if(r > maxRating) r = maxRating;
2538         if(tc < 1.f) tc = 1.f;
2539         if(tc > 95.f) tc = 95.f;
2540         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2541         y = ((double)r - minRating)/(maxRating - minRating)
2542             * (h-vMargin-squareSize/8-1) + vMargin;
2543         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2544         if(strstr(seekAdList[i], " u ")) color = 1;
2545         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2546            !strstr(seekAdList[i], "bullet") &&
2547            !strstr(seekAdList[i], "blitz") &&
2548            !strstr(seekAdList[i], "standard") ) color = 2;
2549         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2550         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2551 }
2552
2553 void
2554 PlotSingleSeekAd (int i)
2555 {
2556         PlotSeekAd(i);
2557 }
2558
2559 void
2560 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2561 {
2562         char buf[MSG_SIZ], *ext = "";
2563         VariantClass v = StringToVariant(type);
2564         if(strstr(type, "wild")) {
2565             ext = type + 4; // append wild number
2566             if(v == VariantFischeRandom) type = "chess960"; else
2567             if(v == VariantLoadable) type = "setup"; else
2568             type = VariantName(v);
2569         }
2570         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2571         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2572             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2573             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2574             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2575             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2576             seekNrList[nrOfSeekAds] = nr;
2577             zList[nrOfSeekAds] = 0;
2578             seekAdList[nrOfSeekAds++] = StrSave(buf);
2579             if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2580         }
2581 }
2582
2583 void
2584 EraseSeekDot (int i)
2585 {
2586     int x = xList[i], y = yList[i], d=squareSize/4, k;
2587     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2588     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2589     // now replot every dot that overlapped
2590     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2591         int xx = xList[k], yy = yList[k];
2592         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2593             DrawSeekDot(xx, yy, colorList[k]);
2594     }
2595 }
2596
2597 void
2598 RemoveSeekAd (int nr)
2599 {
2600         int i;
2601         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2602             EraseSeekDot(i);
2603             if(seekAdList[i]) free(seekAdList[i]);
2604             seekAdList[i] = seekAdList[--nrOfSeekAds];
2605             seekNrList[i] = seekNrList[nrOfSeekAds];
2606             ratingList[i] = ratingList[nrOfSeekAds];
2607             colorList[i]  = colorList[nrOfSeekAds];
2608             tcList[i] = tcList[nrOfSeekAds];
2609             xList[i]  = xList[nrOfSeekAds];
2610             yList[i]  = yList[nrOfSeekAds];
2611             zList[i]  = zList[nrOfSeekAds];
2612             seekAdList[nrOfSeekAds] = NULL;
2613             break;
2614         }
2615 }
2616
2617 Boolean
2618 MatchSoughtLine (char *line)
2619 {
2620     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2621     int nr, base, inc, u=0; char dummy;
2622
2623     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2624        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2625        (u=1) &&
2626        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2627         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2628         // match: compact and save the line
2629         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2630         return TRUE;
2631     }
2632     return FALSE;
2633 }
2634
2635 int
2636 DrawSeekGraph ()
2637 {
2638     int i;
2639     if(!seekGraphUp) return FALSE;
2640     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2641     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2642
2643     DrawSeekBackground(0, 0, w, h);
2644     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2645     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2646     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2647         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2648         yy = h-1-yy;
2649         DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2650         if(i%500 == 0) {
2651             char buf[MSG_SIZ];
2652             snprintf(buf, MSG_SIZ, "%d", i);
2653             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2654         }
2655     }
2656     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2657     for(i=1; i<100; i+=(i<10?1:5)) {
2658         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2659         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2660         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2661             char buf[MSG_SIZ];
2662             snprintf(buf, MSG_SIZ, "%d", i);
2663             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2664         }
2665     }
2666     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2667     return TRUE;
2668 }
2669
2670 int
2671 SeekGraphClick (ClickType click, int x, int y, int moving)
2672 {
2673     static int lastDown = 0, displayed = 0, lastSecond;
2674     if(y < 0) return FALSE;
2675     if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2676         (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2677         if(!seekGraphUp) return FALSE;
2678         seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2679         DrawPosition(TRUE, NULL);
2680         return TRUE;
2681     }
2682     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2683         if(click == Release || moving) return FALSE;
2684         nrOfSeekAds = 0;
2685         soughtPending = TRUE;
2686         SendToICS(ics_prefix);
2687         SendToICS("sought\n"); // should this be "sought all"?
2688     } else { // issue challenge based on clicked ad
2689         int dist = 10000; int i, closest = 0, second = 0;
2690         for(i=0; i<nrOfSeekAds; i++) {
2691             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2692             if(d < dist) { dist = d; closest = i; }
2693             second += (d - zList[i] < 120); // count in-range ads
2694             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2695         }
2696         if(dist < 120) {
2697             char buf[MSG_SIZ];
2698             second = (second > 1);
2699             if(displayed != closest || second != lastSecond) {
2700                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2701                 lastSecond = second; displayed = closest;
2702             }
2703             if(click == Press) {
2704                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2705                 lastDown = closest;
2706                 return TRUE;
2707             } // on press 'hit', only show info
2708             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2709             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2710             SendToICS(ics_prefix);
2711             SendToICS(buf);
2712             return TRUE; // let incoming board of started game pop down the graph
2713         } else if(click == Release) { // release 'miss' is ignored
2714             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2715             if(moving == 2) { // right up-click
2716                 nrOfSeekAds = 0; // refresh graph
2717                 soughtPending = TRUE;
2718                 SendToICS(ics_prefix);
2719                 SendToICS("sought\n"); // should this be "sought all"?
2720             }
2721             return TRUE;
2722         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2723         // press miss or release hit 'pop down' seek graph
2724         seekGraphUp = FALSE;
2725         DrawPosition(TRUE, NULL);
2726     }
2727     return TRUE;
2728 }
2729
2730 void
2731 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2732 {
2733 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2734 #define STARTED_NONE 0
2735 #define STARTED_MOVES 1
2736 #define STARTED_BOARD 2
2737 #define STARTED_OBSERVE 3
2738 #define STARTED_HOLDINGS 4
2739 #define STARTED_CHATTER 5
2740 #define STARTED_COMMENT 6
2741 #define STARTED_MOVES_NOHIDE 7
2742
2743     static int started = STARTED_NONE;
2744     static char parse[20000];
2745     static int parse_pos = 0;
2746     static char buf[BUF_SIZE + 1];
2747     static int firstTime = TRUE, intfSet = FALSE;
2748     static ColorClass prevColor = ColorNormal;
2749     static int savingComment = FALSE;
2750     static int cmatch = 0; // continuation sequence match
2751     char *bp;
2752     char str[MSG_SIZ];
2753     int i, oldi;
2754     int buf_len;
2755     int next_out;
2756     int tkind;
2757     int backup;    /* [DM] For zippy color lines */
2758     char *p;
2759     char talker[MSG_SIZ]; // [HGM] chat
2760     int channel;
2761
2762     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2763
2764     if (appData.debugMode) {
2765       if (!error) {
2766         fprintf(debugFP, "<ICS: ");
2767         show_bytes(debugFP, data, count);
2768         fprintf(debugFP, "\n");
2769       }
2770     }
2771
2772     if (appData.debugMode) { int f = forwardMostMove;
2773         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2774                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2775                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2776     }
2777     if (count > 0) {
2778         /* If last read ended with a partial line that we couldn't parse,
2779            prepend it to the new read and try again. */
2780         if (leftover_len > 0) {
2781             for (i=0; i<leftover_len; i++)
2782               buf[i] = buf[leftover_start + i];
2783         }
2784
2785     /* copy new characters into the buffer */
2786     bp = buf + leftover_len;
2787     buf_len=leftover_len;
2788     for (i=0; i<count; i++)
2789     {
2790         // ignore these
2791         if (data[i] == '\r')
2792             continue;
2793
2794         // join lines split by ICS?
2795         if (!appData.noJoin)
2796         {
2797             /*
2798                 Joining just consists of finding matches against the
2799                 continuation sequence, and discarding that sequence
2800                 if found instead of copying it.  So, until a match
2801                 fails, there's nothing to do since it might be the
2802                 complete sequence, and thus, something we don't want
2803                 copied.
2804             */
2805             if (data[i] == cont_seq[cmatch])
2806             {
2807                 cmatch++;
2808                 if (cmatch == strlen(cont_seq))
2809                 {
2810                     cmatch = 0; // complete match.  just reset the counter
2811
2812                     /*
2813                         it's possible for the ICS to not include the space
2814                         at the end of the last word, making our [correct]
2815                         join operation fuse two separate words.  the server
2816                         does this when the space occurs at the width setting.
2817                     */
2818                     if (!buf_len || buf[buf_len-1] != ' ')
2819                     {
2820                         *bp++ = ' ';
2821                         buf_len++;
2822                     }
2823                 }
2824                 continue;
2825             }
2826             else if (cmatch)
2827             {
2828                 /*
2829                     match failed, so we have to copy what matched before
2830                     falling through and copying this character.  In reality,
2831                     this will only ever be just the newline character, but
2832                     it doesn't hurt to be precise.
2833                 */
2834                 strncpy(bp, cont_seq, cmatch);
2835                 bp += cmatch;
2836                 buf_len += cmatch;
2837                 cmatch = 0;
2838             }
2839         }
2840
2841         // copy this char
2842         *bp++ = data[i];
2843         buf_len++;
2844     }
2845
2846         buf[buf_len] = NULLCHAR;
2847 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2848         next_out = 0;
2849         leftover_start = 0;
2850
2851         i = 0;
2852         while (i < buf_len) {
2853             /* Deal with part of the TELNET option negotiation
2854                protocol.  We refuse to do anything beyond the
2855                defaults, except that we allow the WILL ECHO option,
2856                which ICS uses to turn off password echoing when we are
2857                directly connected to it.  We reject this option
2858                if localLineEditing mode is on (always on in xboard)
2859                and we are talking to port 23, which might be a real
2860                telnet server that will try to keep WILL ECHO on permanently.
2861              */
2862             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2863                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2864                 unsigned char option;
2865                 oldi = i;
2866                 switch ((unsigned char) buf[++i]) {
2867                   case TN_WILL:
2868                     if (appData.debugMode)
2869                       fprintf(debugFP, "\n<WILL ");
2870                     switch (option = (unsigned char) buf[++i]) {
2871                       case TN_ECHO:
2872                         if (appData.debugMode)
2873                           fprintf(debugFP, "ECHO ");
2874                         /* Reply only if this is a change, according
2875                            to the protocol rules. */
2876                         if (remoteEchoOption) break;
2877                         if (appData.localLineEditing &&
2878                             atoi(appData.icsPort) == TN_PORT) {
2879                             TelnetRequest(TN_DONT, TN_ECHO);
2880                         } else {
2881                             EchoOff();
2882                             TelnetRequest(TN_DO, TN_ECHO);
2883                             remoteEchoOption = TRUE;
2884                         }
2885                         break;
2886                       default:
2887                         if (appData.debugMode)
2888                           fprintf(debugFP, "%d ", option);
2889                         /* Whatever this is, we don't want it. */
2890                         TelnetRequest(TN_DONT, option);
2891                         break;
2892                     }
2893                     break;
2894                   case TN_WONT:
2895                     if (appData.debugMode)
2896                       fprintf(debugFP, "\n<WONT ");
2897                     switch (option = (unsigned char) buf[++i]) {
2898                       case TN_ECHO:
2899                         if (appData.debugMode)
2900                           fprintf(debugFP, "ECHO ");
2901                         /* Reply only if this is a change, according
2902                            to the protocol rules. */
2903                         if (!remoteEchoOption) break;
2904                         EchoOn();
2905                         TelnetRequest(TN_DONT, TN_ECHO);
2906                         remoteEchoOption = FALSE;
2907                         break;
2908                       default:
2909                         if (appData.debugMode)
2910                           fprintf(debugFP, "%d ", (unsigned char) option);
2911                         /* Whatever this is, it must already be turned
2912                            off, because we never agree to turn on
2913                            anything non-default, so according to the
2914                            protocol rules, we don't reply. */
2915                         break;
2916                     }
2917                     break;
2918                   case TN_DO:
2919                     if (appData.debugMode)
2920                       fprintf(debugFP, "\n<DO ");
2921                     switch (option = (unsigned char) buf[++i]) {
2922                       default:
2923                         /* Whatever this is, we refuse to do it. */
2924                         if (appData.debugMode)
2925                           fprintf(debugFP, "%d ", option);
2926                         TelnetRequest(TN_WONT, option);
2927                         break;
2928                     }
2929                     break;
2930                   case TN_DONT:
2931                     if (appData.debugMode)
2932                       fprintf(debugFP, "\n<DONT ");
2933                     switch (option = (unsigned char) buf[++i]) {
2934                       default:
2935                         if (appData.debugMode)
2936                           fprintf(debugFP, "%d ", option);
2937                         /* Whatever this is, we are already not doing
2938                            it, because we never agree to do anything
2939                            non-default, so according to the protocol
2940                            rules, we don't reply. */
2941                         break;
2942                     }
2943                     break;
2944                   case TN_IAC:
2945                     if (appData.debugMode)
2946                       fprintf(debugFP, "\n<IAC ");
2947                     /* Doubled IAC; pass it through */
2948                     i--;
2949                     break;
2950                   default:
2951                     if (appData.debugMode)
2952                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2953                     /* Drop all other telnet commands on the floor */
2954                     break;
2955                 }
2956                 if (oldi > next_out)
2957                   SendToPlayer(&buf[next_out], oldi - next_out);
2958                 if (++i > next_out)
2959                   next_out = i;
2960                 continue;
2961             }
2962
2963             /* OK, this at least will *usually* work */
2964             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2965                 loggedOn = TRUE;
2966             }
2967
2968             if (loggedOn && !intfSet) {
2969                 if (ics_type == ICS_ICC) {
2970                   snprintf(str, MSG_SIZ,
2971                           "/set-quietly interface %s\n/set-quietly style 12\n",
2972                           programVersion);
2973                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2974                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2975                 } else if (ics_type == ICS_CHESSNET) {
2976                   snprintf(str, MSG_SIZ, "/style 12\n");
2977                 } else {
2978                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2979                   strcat(str, programVersion);
2980                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2981                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2982                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2983 #ifdef WIN32
2984                   strcat(str, "$iset nohighlight 1\n");
2985 #endif
2986                   strcat(str, "$iset lock 1\n$style 12\n");
2987                 }
2988                 SendToICS(str);
2989                 NotifyFrontendLogin();
2990                 intfSet = TRUE;
2991             }
2992
2993             if (started == STARTED_COMMENT) {
2994                 /* Accumulate characters in comment */
2995                 parse[parse_pos++] = buf[i];
2996                 if (buf[i] == '\n') {
2997                     parse[parse_pos] = NULLCHAR;
2998                     if(chattingPartner>=0) {
2999                         char mess[MSG_SIZ];
3000                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
3001                         OutputChatMessage(chattingPartner, mess);
3002                         chattingPartner = -1;
3003                         next_out = i+1; // [HGM] suppress printing in ICS window
3004                     } else
3005                     if(!suppressKibitz) // [HGM] kibitz
3006                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
3007                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
3008                         int nrDigit = 0, nrAlph = 0, j;
3009                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
3010                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
3011                         parse[parse_pos] = NULLCHAR;
3012                         // try to be smart: if it does not look like search info, it should go to
3013                         // ICS interaction window after all, not to engine-output window.
3014                         for(j=0; j<parse_pos; j++) { // count letters and digits
3015                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
3016                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
3017                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
3018                         }
3019                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
3020                             int depth=0; float score;
3021                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
3022                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
3023                                 pvInfoList[forwardMostMove-1].depth = depth;
3024                                 pvInfoList[forwardMostMove-1].score = 100*score;
3025                             }
3026                             OutputKibitz(suppressKibitz, parse);
3027                         } else {
3028                             char tmp[MSG_SIZ];
3029                             if(gameMode == IcsObserving) // restore original ICS messages
3030                               /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3031                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3032                             else
3033                             /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3034                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3035                             SendToPlayer(tmp, strlen(tmp));
3036                         }
3037                         next_out = i+1; // [HGM] suppress printing in ICS window
3038                     }
3039                     started = STARTED_NONE;
3040                 } else {
3041                     /* Don't match patterns against characters in comment */
3042                     i++;
3043                     continue;
3044                 }
3045             }
3046             if (started == STARTED_CHATTER) {
3047                 if (buf[i] != '\n') {
3048                     /* Don't match patterns against characters in chatter */
3049                     i++;
3050                     continue;
3051                 }
3052                 started = STARTED_NONE;
3053                 if(suppressKibitz) next_out = i+1;
3054             }
3055
3056             /* Kludge to deal with rcmd protocol */
3057             if (firstTime && looking_at(buf, &i, "\001*")) {
3058                 DisplayFatalError(&buf[1], 0, 1);
3059                 continue;
3060             } else {
3061                 firstTime = FALSE;
3062             }
3063
3064             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3065                 ics_type = ICS_ICC;
3066                 ics_prefix = "/";
3067                 if (appData.debugMode)
3068                   fprintf(debugFP, "ics_type %d\n", ics_type);
3069                 continue;
3070             }
3071             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3072                 ics_type = ICS_FICS;
3073                 ics_prefix = "$";
3074                 if (appData.debugMode)
3075                   fprintf(debugFP, "ics_type %d\n", ics_type);
3076                 continue;
3077             }
3078             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3079                 ics_type = ICS_CHESSNET;
3080                 ics_prefix = "/";
3081                 if (appData.debugMode)
3082                   fprintf(debugFP, "ics_type %d\n", ics_type);
3083                 continue;
3084             }
3085
3086             if (!loggedOn &&
3087                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3088                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3089                  looking_at(buf, &i, "will be \"*\""))) {
3090               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3091               continue;
3092             }
3093
3094             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3095               char buf[MSG_SIZ];
3096               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3097               DisplayIcsInteractionTitle(buf);
3098               have_set_title = TRUE;
3099             }
3100
3101             /* skip finger notes */
3102             if (started == STARTED_NONE &&
3103                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3104                  (buf[i] == '1' && buf[i+1] == '0')) &&
3105                 buf[i+2] == ':' && buf[i+3] == ' ') {
3106               started = STARTED_CHATTER;
3107               i += 3;
3108               continue;
3109             }
3110
3111             oldi = i;
3112             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3113             if(appData.seekGraph) {
3114                 if(soughtPending && MatchSoughtLine(buf+i)) {
3115                     i = strstr(buf+i, "rated") - buf;
3116                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3117                     next_out = leftover_start = i;
3118                     started = STARTED_CHATTER;
3119                     suppressKibitz = TRUE;
3120                     continue;
3121                 }
3122                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3123                         && looking_at(buf, &i, "* ads displayed")) {
3124                     soughtPending = FALSE;
3125                     seekGraphUp = TRUE;
3126                     DrawSeekGraph();
3127                     continue;
3128                 }
3129                 if(appData.autoRefresh) {
3130                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3131                         int s = (ics_type == ICS_ICC); // ICC format differs
3132                         if(seekGraphUp)
3133                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3134                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3135                         looking_at(buf, &i, "*% "); // eat prompt
3136                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3137                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3138                         next_out = i; // suppress
3139                         continue;
3140                     }
3141                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3142                         char *p = star_match[0];
3143                         while(*p) {
3144                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3145                             while(*p && *p++ != ' '); // next
3146                         }
3147                         looking_at(buf, &i, "*% "); // eat prompt
3148                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3149                         next_out = i;
3150                         continue;
3151                     }
3152                 }
3153             }
3154
3155             /* skip formula vars */
3156             if (started == STARTED_NONE &&
3157                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3158               started = STARTED_CHATTER;
3159               i += 3;
3160               continue;
3161             }
3162
3163             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3164             if (appData.autoKibitz && started == STARTED_NONE &&
3165                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3166                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3167                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3168                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3169                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3170                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3171                         suppressKibitz = TRUE;
3172                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3173                         next_out = i;
3174                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3175                                 && (gameMode == IcsPlayingWhite)) ||
3176                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3177                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3178                             started = STARTED_CHATTER; // own kibitz we simply discard
3179                         else {
3180                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3181                             parse_pos = 0; parse[0] = NULLCHAR;
3182                             savingComment = TRUE;
3183                             suppressKibitz = gameMode != IcsObserving ? 2 :
3184                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3185                         }
3186                         continue;
3187                 } else
3188                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3189                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3190                          && atoi(star_match[0])) {
3191                     // suppress the acknowledgements of our own autoKibitz
3192                     char *p;
3193                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3194                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3195                     SendToPlayer(star_match[0], strlen(star_match[0]));
3196                     if(looking_at(buf, &i, "*% ")) // eat prompt
3197                         suppressKibitz = FALSE;
3198                     next_out = i;
3199                     continue;
3200                 }
3201             } // [HGM] kibitz: end of patch
3202
3203             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3204
3205             // [HGM] chat: intercept tells by users for which we have an open chat window
3206             channel = -1;
3207             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3208                                            looking_at(buf, &i, "* whispers:") ||
3209                                            looking_at(buf, &i, "* kibitzes:") ||
3210                                            looking_at(buf, &i, "* shouts:") ||
3211                                            looking_at(buf, &i, "* c-shouts:") ||
3212                                            looking_at(buf, &i, "--> * ") ||
3213                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3214                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3215                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3216                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3217                 int p;
3218                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3219                 chattingPartner = -1;
3220
3221                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3222                 for(p=0; p<MAX_CHAT; p++) {
3223                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3224                     talker[0] = '['; strcat(talker, "] ");
3225                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3226                     chattingPartner = p; break;
3227                     }
3228                 } else
3229                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3230                 for(p=0; p<MAX_CHAT; p++) {
3231                     if(!strcmp("kibitzes", chatPartner[p])) {
3232                         talker[0] = '['; strcat(talker, "] ");
3233                         chattingPartner = p; break;
3234                     }
3235                 } else
3236                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3237                 for(p=0; p<MAX_CHAT; p++) {
3238                     if(!strcmp("whispers", chatPartner[p])) {
3239                         talker[0] = '['; strcat(talker, "] ");
3240                         chattingPartner = p; break;
3241                     }
3242                 } else
3243                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3244                   if(buf[i-8] == '-' && buf[i-3] == 't')
3245                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3246                     if(!strcmp("c-shouts", chatPartner[p])) {
3247                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3248                         chattingPartner = p; break;
3249                     }
3250                   }
3251                   if(chattingPartner < 0)
3252                   for(p=0; p<MAX_CHAT; p++) {
3253                     if(!strcmp("shouts", chatPartner[p])) {
3254                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3255                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3256                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3257                         chattingPartner = p; break;
3258                     }
3259                   }
3260                 }
3261                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3262                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3263                     talker[0] = 0; Colorize(ColorTell, FALSE);
3264                     chattingPartner = p; break;
3265                 }
3266                 if(chattingPartner<0) i = oldi; else {
3267                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3268                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3269                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3270                     started = STARTED_COMMENT;
3271                     parse_pos = 0; parse[0] = NULLCHAR;
3272                     savingComment = 3 + chattingPartner; // counts as TRUE
3273                     suppressKibitz = TRUE;
3274                     continue;
3275                 }
3276             } // [HGM] chat: end of patch
3277
3278           backup = i;
3279             if (appData.zippyTalk || appData.zippyPlay) {
3280                 /* [DM] Backup address for color zippy lines */
3281 #if ZIPPY
3282                if (loggedOn == TRUE)
3283                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3284                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3285 #endif
3286             } // [DM] 'else { ' deleted
3287                 if (
3288                     /* Regular tells and says */
3289                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3290                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3291                     looking_at(buf, &i, "* says: ") ||
3292                     /* Don't color "message" or "messages" output */
3293                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3294                     looking_at(buf, &i, "*. * at *:*: ") ||
3295                     looking_at(buf, &i, "--* (*:*): ") ||
3296                     /* Message notifications (same color as tells) */
3297                     looking_at(buf, &i, "* has left a message ") ||
3298                     looking_at(buf, &i, "* just sent you a message:\n") ||
3299                     /* Whispers and kibitzes */
3300                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3301                     looking_at(buf, &i, "* kibitzes: ") ||
3302                     /* Channel tells */
3303                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3304
3305                   if (tkind == 1 && strchr(star_match[0], ':')) {
3306                       /* Avoid "tells you:" spoofs in channels */
3307                      tkind = 3;
3308                   }
3309                   if (star_match[0][0] == NULLCHAR ||
3310                       strchr(star_match[0], ' ') ||
3311                       (tkind == 3 && strchr(star_match[1], ' '))) {
3312                     /* Reject bogus matches */
3313                     i = oldi;
3314                   } else {
3315                     if (appData.colorize) {
3316                       if (oldi > next_out) {
3317                         SendToPlayer(&buf[next_out], oldi - next_out);
3318                         next_out = oldi;
3319                       }
3320                       switch (tkind) {
3321                       case 1:
3322                         Colorize(ColorTell, FALSE);
3323                         curColor = ColorTell;
3324                         break;
3325                       case 2:
3326                         Colorize(ColorKibitz, FALSE);
3327                         curColor = ColorKibitz;
3328                         break;
3329                       case 3:
3330                         p = strrchr(star_match[1], '(');
3331                         if (p == NULL) {
3332                           p = star_match[1];
3333                         } else {
3334                           p++;
3335                         }
3336                         if (atoi(p) == 1) {
3337                           Colorize(ColorChannel1, FALSE);
3338                           curColor = ColorChannel1;
3339                         } else {
3340                           Colorize(ColorChannel, FALSE);
3341                           curColor = ColorChannel;
3342                         }
3343                         break;
3344                       case 5:
3345                         curColor = ColorNormal;
3346                         break;
3347                       }
3348                     }
3349                     if (started == STARTED_NONE && appData.autoComment &&
3350                         (gameMode == IcsObserving ||
3351                          gameMode == IcsPlayingWhite ||
3352                          gameMode == IcsPlayingBlack)) {
3353                       parse_pos = i - oldi;
3354                       memcpy(parse, &buf[oldi], parse_pos);
3355                       parse[parse_pos] = NULLCHAR;
3356                       started = STARTED_COMMENT;
3357                       savingComment = TRUE;
3358                     } else {
3359                       started = STARTED_CHATTER;
3360                       savingComment = FALSE;
3361                     }
3362                     loggedOn = TRUE;
3363                     continue;
3364                   }
3365                 }
3366
3367                 if (looking_at(buf, &i, "* s-shouts: ") ||
3368                     looking_at(buf, &i, "* c-shouts: ")) {
3369                     if (appData.colorize) {
3370                         if (oldi > next_out) {
3371                             SendToPlayer(&buf[next_out], oldi - next_out);
3372                             next_out = oldi;
3373                         }
3374                         Colorize(ColorSShout, FALSE);
3375                         curColor = ColorSShout;
3376                     }
3377                     loggedOn = TRUE;
3378                     started = STARTED_CHATTER;
3379                     continue;
3380                 }
3381
3382                 if (looking_at(buf, &i, "--->")) {
3383                     loggedOn = TRUE;
3384                     continue;
3385                 }
3386
3387                 if (looking_at(buf, &i, "* shouts: ") ||
3388                     looking_at(buf, &i, "--> ")) {
3389                     if (appData.colorize) {
3390                         if (oldi > next_out) {
3391                             SendToPlayer(&buf[next_out], oldi - next_out);
3392                             next_out = oldi;
3393                         }
3394                         Colorize(ColorShout, FALSE);
3395                         curColor = ColorShout;
3396                     }
3397                     loggedOn = TRUE;
3398                     started = STARTED_CHATTER;
3399                     continue;
3400                 }
3401
3402                 if (looking_at( buf, &i, "Challenge:")) {
3403                     if (appData.colorize) {
3404                         if (oldi > next_out) {
3405                             SendToPlayer(&buf[next_out], oldi - next_out);
3406                             next_out = oldi;
3407                         }
3408                         Colorize(ColorChallenge, FALSE);
3409                         curColor = ColorChallenge;
3410                     }
3411                     loggedOn = TRUE;
3412                     continue;
3413                 }
3414
3415                 if (looking_at(buf, &i, "* offers you") ||
3416                     looking_at(buf, &i, "* offers to be") ||
3417                     looking_at(buf, &i, "* would like to") ||
3418                     looking_at(buf, &i, "* requests to") ||
3419                     looking_at(buf, &i, "Your opponent offers") ||
3420                     looking_at(buf, &i, "Your opponent requests")) {
3421
3422                     if (appData.colorize) {
3423                         if (oldi > next_out) {
3424                             SendToPlayer(&buf[next_out], oldi - next_out);
3425                             next_out = oldi;
3426                         }
3427                         Colorize(ColorRequest, FALSE);
3428                         curColor = ColorRequest;
3429                     }
3430                     continue;
3431                 }
3432
3433                 if (looking_at(buf, &i, "* (*) seeking")) {
3434                     if (appData.colorize) {
3435                         if (oldi > next_out) {
3436                             SendToPlayer(&buf[next_out], oldi - next_out);
3437                             next_out = oldi;
3438                         }
3439                         Colorize(ColorSeek, FALSE);
3440                         curColor = ColorSeek;
3441                     }
3442                     continue;
3443             }
3444
3445           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3446
3447             if (looking_at(buf, &i, "\\   ")) {
3448                 if (prevColor != ColorNormal) {
3449                     if (oldi > next_out) {
3450                         SendToPlayer(&buf[next_out], oldi - next_out);
3451                         next_out = oldi;
3452                     }
3453                     Colorize(prevColor, TRUE);
3454                     curColor = prevColor;
3455                 }
3456                 if (savingComment) {
3457                     parse_pos = i - oldi;
3458                     memcpy(parse, &buf[oldi], parse_pos);
3459                     parse[parse_pos] = NULLCHAR;
3460                     started = STARTED_COMMENT;
3461                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3462                         chattingPartner = savingComment - 3; // kludge to remember the box
3463                 } else {
3464                     started = STARTED_CHATTER;
3465                 }
3466                 continue;
3467             }
3468
3469             if (looking_at(buf, &i, "Black Strength :") ||
3470                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3471                 looking_at(buf, &i, "<10>") ||
3472                 looking_at(buf, &i, "#@#")) {
3473                 /* Wrong board style */
3474                 loggedOn = TRUE;
3475                 SendToICS(ics_prefix);
3476                 SendToICS("set style 12\n");
3477                 SendToICS(ics_prefix);
3478                 SendToICS("refresh\n");
3479                 continue;
3480             }
3481
3482             if (looking_at(buf, &i, "login:")) {
3483               if (!have_sent_ICS_logon) {
3484                 if(ICSInitScript())
3485                   have_sent_ICS_logon = 1;
3486                 else // no init script was found
3487                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3488               } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3489                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3490               }
3491                 continue;
3492             }
3493
3494             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3495                 (looking_at(buf, &i, "\n<12> ") ||
3496                  looking_at(buf, &i, "<12> "))) {
3497                 loggedOn = TRUE;
3498                 if (oldi > next_out) {
3499                     SendToPlayer(&buf[next_out], oldi - next_out);
3500                 }
3501                 next_out = i;
3502                 started = STARTED_BOARD;
3503                 parse_pos = 0;
3504                 continue;
3505             }
3506
3507             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3508                 looking_at(buf, &i, "<b1> ")) {
3509                 if (oldi > next_out) {
3510                     SendToPlayer(&buf[next_out], oldi - next_out);
3511                 }
3512                 next_out = i;
3513                 started = STARTED_HOLDINGS;
3514                 parse_pos = 0;
3515                 continue;
3516             }
3517
3518             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3519                 loggedOn = TRUE;
3520                 /* Header for a move list -- first line */
3521
3522                 switch (ics_getting_history) {
3523                   case H_FALSE:
3524                     switch (gameMode) {
3525                       case IcsIdle:
3526                       case BeginningOfGame:
3527                         /* User typed "moves" or "oldmoves" while we
3528                            were idle.  Pretend we asked for these
3529                            moves and soak them up so user can step
3530                            through them and/or save them.
3531                            */
3532                         Reset(FALSE, TRUE);
3533                         gameMode = IcsObserving;
3534                         ModeHighlight();
3535                         ics_gamenum = -1;
3536                         ics_getting_history = H_GOT_UNREQ_HEADER;
3537                         break;
3538                       case EditGame: /*?*/
3539                       case EditPosition: /*?*/
3540                         /* Should above feature work in these modes too? */
3541                         /* For now it doesn't */
3542                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3543                         break;
3544                       default:
3545                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3546                         break;
3547                     }
3548                     break;
3549                   case H_REQUESTED:
3550                     /* Is this the right one? */
3551                     if (gameInfo.white && gameInfo.black &&
3552                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3553                         strcmp(gameInfo.black, star_match[2]) == 0) {
3554                         /* All is well */
3555                         ics_getting_history = H_GOT_REQ_HEADER;
3556                     }
3557                     break;
3558                   case H_GOT_REQ_HEADER:
3559                   case H_GOT_UNREQ_HEADER:
3560                   case H_GOT_UNWANTED_HEADER:
3561                   case H_GETTING_MOVES:
3562                     /* Should not happen */
3563                     DisplayError(_("Error gathering move list: two headers"), 0);
3564                     ics_getting_history = H_FALSE;
3565                     break;
3566                 }
3567
3568                 /* Save player ratings into gameInfo if needed */
3569                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3570                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3571                     (gameInfo.whiteRating == -1 ||
3572                      gameInfo.blackRating == -1)) {
3573
3574                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3575                     gameInfo.blackRating = string_to_rating(star_match[3]);
3576                     if (appData.debugMode)
3577                       fprintf(debugFP, "Ratings from header: W %d, B %d\n",
3578                               gameInfo.whiteRating, gameInfo.blackRating);
3579                 }
3580                 continue;
3581             }
3582
3583             if (looking_at(buf, &i,
3584               "* * match, initial time: * minute*, increment: * second")) {
3585                 /* Header for a move list -- second line */
3586                 /* Initial board will follow if this is a wild game */
3587                 if (gameInfo.event != NULL) free(gameInfo.event);
3588                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3589                 gameInfo.event = StrSave(str);
3590                 /* [HGM] we switched variant. Translate boards if needed. */
3591                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3592                 continue;
3593             }
3594
3595             if (looking_at(buf, &i, "Move  ")) {
3596                 /* Beginning of a move list */
3597                 switch (ics_getting_history) {
3598                   case H_FALSE:
3599                     /* Normally should not happen */
3600                     /* Maybe user hit reset while we were parsing */
3601                     break;
3602                   case H_REQUESTED:
3603                     /* Happens if we are ignoring a move list that is not
3604                      * the one we just requested.  Common if the user
3605                      * tries to observe two games without turning off
3606                      * getMoveList */
3607                     break;
3608                   case H_GETTING_MOVES:
3609                     /* Should not happen */
3610                     DisplayError(_("Error gathering move list: nested"), 0);
3611                     ics_getting_history = H_FALSE;
3612                     break;
3613                   case H_GOT_REQ_HEADER:
3614                     ics_getting_history = H_GETTING_MOVES;
3615                     started = STARTED_MOVES;
3616                     parse_pos = 0;
3617                     if (oldi > next_out) {
3618                         SendToPlayer(&buf[next_out], oldi - next_out);
3619                     }
3620                     break;
3621                   case H_GOT_UNREQ_HEADER:
3622                     ics_getting_history = H_GETTING_MOVES;
3623                     started = STARTED_MOVES_NOHIDE;
3624                     parse_pos = 0;
3625                     break;
3626                   case H_GOT_UNWANTED_HEADER:
3627                     ics_getting_history = H_FALSE;
3628                     break;
3629                 }
3630                 continue;
3631             }
3632
3633             if (looking_at(buf, &i, "% ") ||
3634                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3635                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3636                 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3637                     soughtPending = FALSE;
3638                     seekGraphUp = TRUE;
3639                     DrawSeekGraph();
3640                 }
3641                 if(suppressKibitz) next_out = i;
3642                 savingComment = FALSE;
3643                 suppressKibitz = 0;
3644                 switch (started) {
3645                   case STARTED_MOVES:
3646                   case STARTED_MOVES_NOHIDE:
3647                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3648                     parse[parse_pos + i - oldi] = NULLCHAR;
3649                     ParseGameHistory(parse);
3650 #if ZIPPY
3651                     if (appData.zippyPlay && first.initDone) {
3652                         FeedMovesToProgram(&first, forwardMostMove);
3653                         if (gameMode == IcsPlayingWhite) {
3654                             if (WhiteOnMove(forwardMostMove)) {
3655                                 if (first.sendTime) {
3656                                   if (first.useColors) {
3657                                     SendToProgram("black\n", &first);
3658                                   }
3659                                   SendTimeRemaining(&first, TRUE);
3660                                 }
3661                                 if (first.useColors) {
3662                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3663                                 }
3664                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3665                                 first.maybeThinking = TRUE;
3666                             } else {
3667                                 if (first.usePlayother) {
3668                                   if (first.sendTime) {
3669                                     SendTimeRemaining(&first, TRUE);
3670                                   }
3671                                   SendToProgram("playother\n", &first);
3672                                   firstMove = FALSE;
3673                                 } else {
3674                                   firstMove = TRUE;
3675                                 }
3676                             }
3677                         } else if (gameMode == IcsPlayingBlack) {
3678                             if (!WhiteOnMove(forwardMostMove)) {
3679                                 if (first.sendTime) {
3680                                   if (first.useColors) {
3681                                     SendToProgram("white\n", &first);
3682                                   }
3683                                   SendTimeRemaining(&first, FALSE);
3684                                 }
3685                                 if (first.useColors) {
3686                                   SendToProgram("black\n", &first);
3687                                 }
3688                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3689                                 first.maybeThinking = TRUE;
3690                             } else {
3691                                 if (first.usePlayother) {
3692                                   if (first.sendTime) {
3693                                     SendTimeRemaining(&first, FALSE);
3694                                   }
3695                                   SendToProgram("playother\n", &first);
3696                                   firstMove = FALSE;
3697                                 } else {
3698                                   firstMove = TRUE;
3699                                 }
3700                             }
3701                         }
3702                     }
3703 #endif
3704                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3705                         /* Moves came from oldmoves or moves command
3706                            while we weren't doing anything else.
3707                            */
3708                         currentMove = forwardMostMove;
3709                         ClearHighlights();/*!!could figure this out*/
3710                         flipView = appData.flipView;
3711                         DrawPosition(TRUE, boards[currentMove]);
3712                         DisplayBothClocks();
3713                         snprintf(str, MSG_SIZ, "%s %s %s",
3714                                 gameInfo.white, _("vs."),  gameInfo.black);
3715                         DisplayTitle(str);
3716                         gameMode = IcsIdle;
3717                     } else {
3718                         /* Moves were history of an active game */
3719                         if (gameInfo.resultDetails != NULL) {
3720                             free(gameInfo.resultDetails);
3721                             gameInfo.resultDetails = NULL;
3722                         }
3723                     }
3724                     HistorySet(parseList, backwardMostMove,
3725                                forwardMostMove, currentMove-1);
3726                     DisplayMove(currentMove - 1);
3727                     if (started == STARTED_MOVES) next_out = i;
3728                     started = STARTED_NONE;
3729                     ics_getting_history = H_FALSE;
3730                     break;
3731
3732                   case STARTED_OBSERVE:
3733                     started = STARTED_NONE;
3734                     SendToICS(ics_prefix);
3735                     SendToICS("refresh\n");
3736                     break;
3737
3738                   default:
3739                     break;
3740                 }
3741                 if(bookHit) { // [HGM] book: simulate book reply
3742                     static char bookMove[MSG_SIZ]; // a bit generous?
3743
3744                     programStats.nodes = programStats.depth = programStats.time =
3745                     programStats.score = programStats.got_only_move = 0;
3746                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3747
3748                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3749                     strcat(bookMove, bookHit);
3750                     HandleMachineMove(bookMove, &first);
3751                 }
3752                 continue;
3753             }
3754
3755             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3756                  started == STARTED_HOLDINGS ||
3757                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3758                 /* Accumulate characters in move list or board */
3759                 parse[parse_pos++] = buf[i];
3760             }
3761
3762             /* Start of game messages.  Mostly we detect start of game
3763                when the first board image arrives.  On some versions
3764                of the ICS, though, we need to do a "refresh" after starting
3765                to observe in order to get the current board right away. */
3766             if (looking_at(buf, &i, "Adding game * to observation list")) {
3767                 started = STARTED_OBSERVE;
3768                 continue;
3769             }
3770
3771             /* Handle auto-observe */
3772             if (appData.autoObserve &&
3773                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3774                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3775                 char *player;
3776                 /* Choose the player that was highlighted, if any. */
3777                 if (star_match[0][0] == '\033' ||
3778                     star_match[1][0] != '\033') {
3779                     player = star_match[0];
3780                 } else {
3781                     player = star_match[2];
3782                 }
3783                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3784                         ics_prefix, StripHighlightAndTitle(player));
3785                 SendToICS(str);
3786
3787                 /* Save ratings from notify string */
3788                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3789                 player1Rating = string_to_rating(star_match[1]);
3790                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3791                 player2Rating = string_to_rating(star_match[3]);
3792
3793                 if (appData.debugMode)
3794                   fprintf(debugFP,
3795                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3796                           player1Name, player1Rating,
3797                           player2Name, player2Rating);
3798
3799                 continue;
3800             }
3801
3802             /* Deal with automatic examine mode after a game,
3803                and with IcsObserving -> IcsExamining transition */
3804             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3805                 looking_at(buf, &i, "has made you an examiner of game *")) {
3806
3807                 int gamenum = atoi(star_match[0]);
3808                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3809                     gamenum == ics_gamenum) {
3810                     /* We were already playing or observing this game;
3811                        no need to refetch history */
3812                     gameMode = IcsExamining;
3813                     if (pausing) {
3814                         pauseExamForwardMostMove = forwardMostMove;
3815                     } else if (currentMove < forwardMostMove) {
3816                         ForwardInner(forwardMostMove);
3817                     }
3818                 } else {
3819                     /* I don't think this case really can happen */
3820                     SendToICS(ics_prefix);
3821                     SendToICS("refresh\n");
3822                 }
3823                 continue;
3824             }
3825
3826             /* Error messages */
3827 //          if (ics_user_moved) {
3828             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3829                 if (looking_at(buf, &i, "Illegal move") ||
3830                     looking_at(buf, &i, "Not a legal move") ||
3831                     looking_at(buf, &i, "Your king is in check") ||
3832                     looking_at(buf, &i, "It isn't your turn") ||
3833                     looking_at(buf, &i, "It is not your move")) {
3834                     /* Illegal move */
3835                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3836                         currentMove = forwardMostMove-1;
3837                         DisplayMove(currentMove - 1); /* before DMError */
3838                         DrawPosition(FALSE, boards[currentMove]);
3839                         SwitchClocks(forwardMostMove-1); // [HGM] race
3840                         DisplayBothClocks();
3841                     }
3842                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3843                     ics_user_moved = 0;
3844                     continue;
3845                 }
3846             }
3847
3848             if (looking_at(buf, &i, "still have time") ||
3849                 looking_at(buf, &i, "not out of time") ||
3850                 looking_at(buf, &i, "either player is out of time") ||
3851                 looking_at(buf, &i, "has timeseal; checking")) {
3852                 /* We must have called his flag a little too soon */
3853                 whiteFlag = blackFlag = FALSE;
3854                 continue;
3855             }
3856
3857             if (looking_at(buf, &i, "added * seconds to") ||
3858                 looking_at(buf, &i, "seconds were added to")) {
3859                 /* Update the clocks */
3860                 SendToICS(ics_prefix);
3861                 SendToICS("refresh\n");
3862                 continue;
3863             }
3864
3865             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3866                 ics_clock_paused = TRUE;
3867                 StopClocks();
3868                 continue;
3869             }
3870
3871             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3872                 ics_clock_paused = FALSE;
3873                 StartClocks();
3874                 continue;
3875             }
3876
3877             /* Grab player ratings from the Creating: message.
3878                Note we have to check for the special case when
3879                the ICS inserts things like [white] or [black]. */
3880             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3881                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3882                 /* star_matches:
3883                    0    player 1 name (not necessarily white)
3884                    1    player 1 rating
3885                    2    empty, white, or black (IGNORED)
3886                    3    player 2 name (not necessarily black)
3887                    4    player 2 rating
3888
3889                    The names/ratings are sorted out when the game
3890                    actually starts (below).
3891                 */
3892                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3893                 player1Rating = string_to_rating(star_match[1]);
3894                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3895                 player2Rating = string_to_rating(star_match[4]);
3896
3897                 if (appData.debugMode)
3898                   fprintf(debugFP,
3899                           "Ratings from 'Creating:' %s %d, %s %d\n",
3900                           player1Name, player1Rating,
3901                           player2Name, player2Rating);
3902
3903                 continue;
3904             }
3905
3906             /* Improved generic start/end-of-game messages */
3907             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3908                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3909                 /* If tkind == 0: */
3910                 /* star_match[0] is the game number */
3911                 /*           [1] is the white player's name */
3912                 /*           [2] is the black player's name */
3913                 /* For end-of-game: */
3914                 /*           [3] is the reason for the game end */
3915                 /*           [4] is a PGN end game-token, preceded by " " */
3916                 /* For start-of-game: */
3917                 /*           [3] begins with "Creating" or "Continuing" */
3918                 /*           [4] is " *" or empty (don't care). */
3919                 int gamenum = atoi(star_match[0]);
3920                 char *whitename, *blackname, *why, *endtoken;
3921                 ChessMove endtype = EndOfFile;
3922
3923                 if (tkind == 0) {
3924                   whitename = star_match[1];
3925                   blackname = star_match[2];
3926                   why = star_match[3];
3927                   endtoken = star_match[4];
3928                 } else {
3929                   whitename = star_match[1];
3930                   blackname = star_match[3];
3931                   why = star_match[5];
3932                   endtoken = star_match[6];
3933                 }
3934
3935                 /* Game start messages */
3936                 if (strncmp(why, "Creating ", 9) == 0 ||
3937                     strncmp(why, "Continuing ", 11) == 0) {
3938                     gs_gamenum = gamenum;
3939                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3940                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
3941                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3942 #if ZIPPY
3943                     if (appData.zippyPlay) {
3944                         ZippyGameStart(whitename, blackname);
3945                     }
3946 #endif /*ZIPPY*/
3947                     partnerBoardValid = FALSE; // [HGM] bughouse
3948                     continue;
3949                 }
3950
3951                 /* Game end messages */
3952                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3953                     ics_gamenum != gamenum) {
3954                     continue;
3955                 }
3956                 while (endtoken[0] == ' ') endtoken++;
3957                 switch (endtoken[0]) {
3958                   case '*':
3959                   default:
3960                     endtype = GameUnfinished;
3961                     break;
3962                   case '0':
3963                     endtype = BlackWins;
3964                     break;
3965                   case '1':
3966                     if (endtoken[1] == '/')
3967                       endtype = GameIsDrawn;
3968                     else
3969                       endtype = WhiteWins;
3970                     break;
3971                 }
3972                 GameEnds(endtype, why, GE_ICS);
3973 #if ZIPPY
3974                 if (appData.zippyPlay && first.initDone) {
3975                     ZippyGameEnd(endtype, why);
3976                     if (first.pr == NoProc) {
3977                       /* Start the next process early so that we'll
3978                          be ready for the next challenge */
3979                       StartChessProgram(&first);
3980                     }
3981                     /* Send "new" early, in case this command takes
3982                        a long time to finish, so that we'll be ready
3983                        for the next challenge. */
3984                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3985                     Reset(TRUE, TRUE);
3986                 }
3987 #endif /*ZIPPY*/
3988                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3989                 continue;
3990             }
3991
3992             if (looking_at(buf, &i, "Removing game * from observation") ||
3993                 looking_at(buf, &i, "no longer observing game *") ||
3994                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3995                 if (gameMode == IcsObserving &&
3996                     atoi(star_match[0]) == ics_gamenum)
3997                   {
3998                       /* icsEngineAnalyze */
3999                       if (appData.icsEngineAnalyze) {
4000                             ExitAnalyzeMode();
4001                             ModeHighlight();
4002                       }
4003                       StopClocks();
4004                       gameMode = IcsIdle;
4005                       ics_gamenum = -1;
4006                       ics_user_moved = FALSE;
4007                   }
4008                 continue;
4009             }
4010
4011             if (looking_at(buf, &i, "no longer examining game *")) {
4012                 if (gameMode == IcsExamining &&
4013                     atoi(star_match[0]) == ics_gamenum)
4014                   {
4015                       gameMode = IcsIdle;
4016                       ics_gamenum = -1;
4017                       ics_user_moved = FALSE;
4018                   }
4019                 continue;
4020             }
4021
4022             /* Advance leftover_start past any newlines we find,
4023                so only partial lines can get reparsed */
4024             if (looking_at(buf, &i, "\n")) {
4025                 prevColor = curColor;
4026                 if (curColor != ColorNormal) {
4027                     if (oldi > next_out) {
4028                         SendToPlayer(&buf[next_out], oldi - next_out);
4029                         next_out = oldi;
4030                     }
4031                     Colorize(ColorNormal, FALSE);
4032                     curColor = ColorNormal;
4033                 }
4034                 if (started == STARTED_BOARD) {
4035                     started = STARTED_NONE;
4036                     parse[parse_pos] = NULLCHAR;
4037                     ParseBoard12(parse);
4038                     ics_user_moved = 0;
4039
4040                     /* Send premove here */
4041                     if (appData.premove) {
4042                       char str[MSG_SIZ];
4043                       if (currentMove == 0 &&
4044                           gameMode == IcsPlayingWhite &&
4045                           appData.premoveWhite) {
4046                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
4047                         if (appData.debugMode)
4048                           fprintf(debugFP, "Sending premove:\n");
4049                         SendToICS(str);
4050                       } else if (currentMove == 1 &&
4051                                  gameMode == IcsPlayingBlack &&
4052                                  appData.premoveBlack) {
4053                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
4054                         if (appData.debugMode)
4055                           fprintf(debugFP, "Sending premove:\n");
4056                         SendToICS(str);
4057                       } else if (gotPremove) {
4058                         gotPremove = 0;
4059                         ClearPremoveHighlights();
4060                         if (appData.debugMode)
4061                           fprintf(debugFP, "Sending premove:\n");
4062                           UserMoveEvent(premoveFromX, premoveFromY,
4063                                         premoveToX, premoveToY,
4064                                         premovePromoChar);
4065                       }
4066                     }
4067
4068                     /* Usually suppress following prompt */
4069                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4070                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4071                         if (looking_at(buf, &i, "*% ")) {
4072                             savingComment = FALSE;
4073                             suppressKibitz = 0;
4074                         }
4075                     }
4076                     next_out = i;
4077                 } else if (started == STARTED_HOLDINGS) {
4078                     int gamenum;
4079                     char new_piece[MSG_SIZ];
4080                     started = STARTED_NONE;
4081                     parse[parse_pos] = NULLCHAR;
4082                     if (appData.debugMode)
4083                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4084                                                         parse, currentMove);
4085                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4086                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4087                         if (gameInfo.variant == VariantNormal) {
4088                           /* [HGM] We seem to switch variant during a game!
4089                            * Presumably no holdings were displayed, so we have
4090                            * to move the position two files to the right to
4091                            * create room for them!
4092                            */
4093                           VariantClass newVariant;
4094                           switch(gameInfo.boardWidth) { // base guess on board width
4095                                 case 9:  newVariant = VariantShogi; break;
4096                                 case 10: newVariant = VariantGreat; break;
4097                                 default: newVariant = VariantCrazyhouse; break;
4098                           }
4099                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4100                           /* Get a move list just to see the header, which
4101                              will tell us whether this is really bug or zh */
4102                           if (ics_getting_history == H_FALSE) {
4103                             ics_getting_history = H_REQUESTED;
4104                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4105                             SendToICS(str);
4106                           }
4107                         }
4108                         new_piece[0] = NULLCHAR;
4109                         sscanf(parse, "game %d white [%s black [%s <- %s",
4110                                &gamenum, white_holding, black_holding,
4111                                new_piece);
4112                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4113                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4114                         /* [HGM] copy holdings to board holdings area */
4115                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4116                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4117                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4118 #if ZIPPY
4119                         if (appData.zippyPlay && first.initDone) {
4120                             ZippyHoldings(white_holding, black_holding,
4121                                           new_piece);
4122                         }
4123 #endif /*ZIPPY*/
4124                         if (tinyLayout || smallLayout) {
4125                             char wh[16], bh[16];
4126                             PackHolding(wh, white_holding);
4127                             PackHolding(bh, black_holding);
4128                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4129                                     gameInfo.white, gameInfo.black);
4130                         } else {
4131                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4132                                     gameInfo.white, white_holding, _("vs."),
4133                                     gameInfo.black, black_holding);
4134                         }
4135                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4136                         DrawPosition(FALSE, boards[currentMove]);
4137                         DisplayTitle(str);
4138                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4139                         sscanf(parse, "game %d white [%s black [%s <- %s",
4140                                &gamenum, white_holding, black_holding,
4141                                new_piece);
4142                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4143                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4144                         /* [HGM] copy holdings to partner-board holdings area */
4145                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4146                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4147                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4148                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4149                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4150                       }
4151                     }
4152                     /* Suppress following prompt */
4153                     if (looking_at(buf, &i, "*% ")) {
4154                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4155                         savingComment = FALSE;
4156                         suppressKibitz = 0;
4157                     }
4158                     next_out = i;
4159                 }
4160                 continue;
4161             }
4162
4163             i++;                /* skip unparsed character and loop back */
4164         }
4165
4166         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4167 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4168 //          SendToPlayer(&buf[next_out], i - next_out);
4169             started != STARTED_HOLDINGS && leftover_start > next_out) {
4170             SendToPlayer(&buf[next_out], leftover_start - next_out);
4171             next_out = i;
4172         }
4173
4174         leftover_len = buf_len - leftover_start;
4175         /* if buffer ends with something we couldn't parse,
4176            reparse it after appending the next read */
4177
4178     } else if (count == 0) {
4179         RemoveInputSource(isr);
4180         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4181     } else {
4182         DisplayFatalError(_("Error reading from ICS"), error, 1);
4183     }
4184 }
4185
4186
4187 /* Board style 12 looks like this:
4188
4189    <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
4190
4191  * The "<12> " is stripped before it gets to this routine.  The two
4192  * trailing 0's (flip state and clock ticking) are later addition, and
4193  * some chess servers may not have them, or may have only the first.
4194  * Additional trailing fields may be added in the future.
4195  */
4196
4197 #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"
4198
4199 #define RELATION_OBSERVING_PLAYED    0
4200 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4201 #define RELATION_PLAYING_MYMOVE      1
4202 #define RELATION_PLAYING_NOTMYMOVE  -1
4203 #define RELATION_EXAMINING           2
4204 #define RELATION_ISOLATED_BOARD     -3
4205 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4206
4207 void
4208 ParseBoard12 (char *string)
4209 {
4210 #if ZIPPY
4211     int i, takeback;
4212     char *bookHit = NULL; // [HGM] book
4213 #endif
4214     GameMode newGameMode;
4215     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
4216     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
4217     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4218     char to_play, board_chars[200];
4219     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4220     char black[32], white[32];
4221     Board board;
4222     int prevMove = currentMove;
4223     int ticking = 2;
4224     ChessMove moveType;
4225     int fromX, fromY, toX, toY;
4226     char promoChar;
4227     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4228     Boolean weird = FALSE, reqFlag = FALSE;
4229
4230     fromX = fromY = toX = toY = -1;
4231
4232     newGame = FALSE;
4233
4234     if (appData.debugMode)
4235       fprintf(debugFP, "Parsing board: %s\n", string);
4236
4237     move_str[0] = NULLCHAR;
4238     elapsed_time[0] = NULLCHAR;
4239     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4240         int  i = 0, j;
4241         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4242             if(string[i] == ' ') { ranks++; files = 0; }
4243             else files++;
4244             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4245             i++;
4246         }
4247         for(j = 0; j <i; j++) board_chars[j] = string[j];
4248         board_chars[i] = '\0';
4249         string += i + 1;
4250     }
4251     n = sscanf(string, PATTERN, &to_play, &double_push,
4252                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4253                &gamenum, white, black, &relation, &basetime, &increment,
4254                &white_stren, &black_stren, &white_time, &black_time,
4255                &moveNum, str, elapsed_time, move_str, &ics_flip,
4256                &ticking);
4257
4258     if (n < 21) {
4259         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4260         DisplayError(str, 0);
4261         return;
4262     }
4263
4264     /* Convert the move number to internal form */
4265     moveNum = (moveNum - 1) * 2;
4266     if (to_play == 'B') moveNum++;
4267     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4268       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4269                         0, 1);
4270       return;
4271     }
4272
4273     switch (relation) {
4274       case RELATION_OBSERVING_PLAYED:
4275       case RELATION_OBSERVING_STATIC:
4276         if (gamenum == -1) {
4277             /* Old ICC buglet */
4278             relation = RELATION_OBSERVING_STATIC;
4279         }
4280         newGameMode = IcsObserving;
4281         break;
4282       case RELATION_PLAYING_MYMOVE:
4283       case RELATION_PLAYING_NOTMYMOVE:
4284         newGameMode =
4285           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4286             IcsPlayingWhite : IcsPlayingBlack;
4287         soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4288         break;
4289       case RELATION_EXAMINING:
4290         newGameMode = IcsExamining;
4291         break;
4292       case RELATION_ISOLATED_BOARD:
4293       default:
4294         /* Just display this board.  If user was doing something else,
4295            we will forget about it until the next board comes. */
4296         newGameMode = IcsIdle;
4297         break;
4298       case RELATION_STARTING_POSITION:
4299         newGameMode = gameMode;
4300         break;
4301     }
4302
4303     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4304         gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4305          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4306       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4307       int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4308       static int lastBgGame = -1;
4309       char *toSqr;
4310       for (k = 0; k < ranks; k++) {
4311         for (j = 0; j < files; j++)
4312           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4313         if(gameInfo.holdingsWidth > 1) {
4314              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4315              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4316         }
4317       }
4318       CopyBoard(partnerBoard, board);
4319       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4320         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4321         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4322       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4323       if(toSqr = strchr(str, '-')) {
4324         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4325         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4326       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4327       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4328       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4329       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4330       if(twoBoards) {
4331           DisplayWhiteClock(white_time*fac, to_play == 'W');
4332           DisplayBlackClock(black_time*fac, to_play != 'W');
4333           activePartner = to_play;
4334           if(gamenum != lastBgGame) {
4335               char buf[MSG_SIZ];
4336               snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4337               DisplayTitle(buf);
4338           }
4339           lastBgGame = gamenum;
4340           activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4341                       partnerUp = 0; flipView = !flipView; } // [HGM] dual
4342       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4343                  (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4344       if(!twoBoards) DisplayMessage(partnerStatus, "");
4345         partnerBoardValid = TRUE;
4346       return;
4347     }
4348
4349     if(appData.dualBoard && appData.bgObserve) {
4350         if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4351             SendToICS(ics_prefix), SendToICS("pobserve\n");
4352         else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4353             char buf[MSG_SIZ];
4354             snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4355             SendToICS(buf);
4356         }
4357     }
4358
4359     /* Modify behavior for initial board display on move listing
4360        of wild games.
4361        */
4362     switch (ics_getting_history) {
4363       case H_FALSE:
4364       case H_REQUESTED:
4365         break;
4366       case H_GOT_REQ_HEADER:
4367       case H_GOT_UNREQ_HEADER:
4368         /* This is the initial position of the current game */
4369         gamenum = ics_gamenum;
4370         moveNum = 0;            /* old ICS bug workaround */
4371         if (to_play == 'B') {
4372           startedFromSetupPosition = TRUE;
4373           blackPlaysFirst = TRUE;
4374           moveNum = 1;
4375           if (forwardMostMove == 0) forwardMostMove = 1;
4376           if (backwardMostMove == 0) backwardMostMove = 1;
4377           if (currentMove == 0) currentMove = 1;
4378         }
4379         newGameMode = gameMode;
4380         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4381         break;
4382       case H_GOT_UNWANTED_HEADER:
4383         /* This is an initial board that we don't want */
4384         return;
4385       case H_GETTING_MOVES:
4386         /* Should not happen */
4387         DisplayError(_("Error gathering move list: extra board"), 0);
4388         ics_getting_history = H_FALSE;
4389         return;
4390     }
4391
4392    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4393                                         move_str[1] == '@' && !gameInfo.holdingsWidth ||
4394                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4395      /* [HGM] We seem to have switched variant unexpectedly
4396       * Try to guess new variant from board size
4397       */
4398           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4399           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4400           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4401           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4402           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4403           if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4404           if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4405           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4406           /* Get a move list just to see the header, which
4407              will tell us whether this is really bug or zh */
4408           if (ics_getting_history == H_FALSE) {
4409             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4410             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4411             SendToICS(str);
4412           }
4413     }
4414
4415     /* Take action if this is the first board of a new game, or of a
4416        different game than is currently being displayed.  */
4417     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4418         relation == RELATION_ISOLATED_BOARD) {
4419
4420         /* Forget the old game and get the history (if any) of the new one */
4421         if (gameMode != BeginningOfGame) {
4422           Reset(TRUE, TRUE);
4423         }
4424         newGame = TRUE;
4425         if (appData.autoRaiseBoard) BoardToTop();
4426         prevMove = -3;
4427         if (gamenum == -1) {
4428             newGameMode = IcsIdle;
4429         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4430                    appData.getMoveList && !reqFlag) {
4431             /* Need to get game history */
4432             ics_getting_history = H_REQUESTED;
4433             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4434             SendToICS(str);
4435         }
4436
4437         /* Initially flip the board to have black on the bottom if playing
4438            black or if the ICS flip flag is set, but let the user change
4439            it with the Flip View button. */
4440         flipView = appData.autoFlipView ?
4441           (newGameMode == IcsPlayingBlack) || ics_flip :
4442           appData.flipView;
4443
4444         /* Done with values from previous mode; copy in new ones */
4445         gameMode = newGameMode;
4446         ModeHighlight();
4447         ics_gamenum = gamenum;
4448         if (gamenum == gs_gamenum) {
4449             int klen = strlen(gs_kind);
4450             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4451             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4452             gameInfo.event = StrSave(str);
4453         } else {
4454             gameInfo.event = StrSave("ICS game");
4455         }
4456         gameInfo.site = StrSave(appData.icsHost);
4457         gameInfo.date = PGNDate();
4458         gameInfo.round = StrSave("-");
4459         gameInfo.white = StrSave(white);
4460         gameInfo.black = StrSave(black);
4461         timeControl = basetime * 60 * 1000;
4462         timeControl_2 = 0;
4463         timeIncrement = increment * 1000;
4464         movesPerSession = 0;
4465         gameInfo.timeControl = TimeControlTagValue();
4466         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4467   if (appData.debugMode) {
4468     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4469     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4470     setbuf(debugFP, NULL);
4471   }
4472
4473         gameInfo.outOfBook = NULL;
4474
4475         /* Do we have the ratings? */
4476         if (strcmp(player1Name, white) == 0 &&
4477             strcmp(player2Name, black) == 0) {
4478             if (appData.debugMode)
4479               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4480                       player1Rating, player2Rating);
4481             gameInfo.whiteRating = player1Rating;
4482             gameInfo.blackRating = player2Rating;
4483         } else if (strcmp(player2Name, white) == 0 &&
4484                    strcmp(player1Name, black) == 0) {
4485             if (appData.debugMode)
4486               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4487                       player2Rating, player1Rating);
4488             gameInfo.whiteRating = player2Rating;
4489             gameInfo.blackRating = player1Rating;
4490         }
4491         player1Name[0] = player2Name[0] = NULLCHAR;
4492
4493         /* Silence shouts if requested */
4494         if (appData.quietPlay &&
4495             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4496             SendToICS(ics_prefix);
4497             SendToICS("set shout 0\n");
4498         }
4499     }
4500
4501     /* Deal with midgame name changes */
4502     if (!newGame) {
4503         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4504             if (gameInfo.white) free(gameInfo.white);
4505             gameInfo.white = StrSave(white);
4506         }
4507         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4508             if (gameInfo.black) free(gameInfo.black);
4509             gameInfo.black = StrSave(black);
4510         }
4511     }
4512
4513     /* Throw away game result if anything actually changes in examine mode */
4514     if (gameMode == IcsExamining && !newGame) {
4515         gameInfo.result = GameUnfinished;
4516         if (gameInfo.resultDetails != NULL) {
4517             free(gameInfo.resultDetails);
4518             gameInfo.resultDetails = NULL;
4519         }
4520     }
4521
4522     /* In pausing && IcsExamining mode, we ignore boards coming
4523        in if they are in a different variation than we are. */
4524     if (pauseExamInvalid) return;
4525     if (pausing && gameMode == IcsExamining) {
4526         if (moveNum <= pauseExamForwardMostMove) {
4527             pauseExamInvalid = TRUE;
4528             forwardMostMove = pauseExamForwardMostMove;
4529             return;
4530         }
4531     }
4532
4533   if (appData.debugMode) {
4534     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4535   }
4536     /* Parse the board */
4537     for (k = 0; k < ranks; k++) {
4538       for (j = 0; j < files; j++)
4539         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4540       if(gameInfo.holdingsWidth > 1) {
4541            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4542            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4543       }
4544     }
4545     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4546       board[5][BOARD_RGHT+1] = WhiteAngel;
4547       board[6][BOARD_RGHT+1] = WhiteMarshall;
4548       board[1][0] = BlackMarshall;
4549       board[2][0] = BlackAngel;
4550       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4551     }
4552     CopyBoard(boards[moveNum], board);
4553     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4554     if (moveNum == 0) {
4555         startedFromSetupPosition =
4556           !CompareBoards(board, initialPosition);
4557         if(startedFromSetupPosition)
4558             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4559     }
4560
4561     /* [HGM] Set castling rights. Take the outermost Rooks,
4562        to make it also work for FRC opening positions. Note that board12
4563        is really defective for later FRC positions, as it has no way to
4564        indicate which Rook can castle if they are on the same side of King.
4565        For the initial position we grant rights to the outermost Rooks,
4566        and remember thos rights, and we then copy them on positions
4567        later in an FRC game. This means WB might not recognize castlings with
4568        Rooks that have moved back to their original position as illegal,
4569        but in ICS mode that is not its job anyway.
4570     */
4571     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4572     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4573
4574         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4575             if(board[0][i] == WhiteRook) j = i;
4576         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4577         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4578             if(board[0][i] == WhiteRook) j = i;
4579         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4580         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4581             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4582         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4583         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4584             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4585         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4586
4587         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4588         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4589         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4590             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4591         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4592             if(board[BOARD_HEIGHT-1][k] == bKing)
4593                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4594         if(gameInfo.variant == VariantTwoKings) {
4595             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4596             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4597             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4598         }
4599     } else { int r;
4600         r = boards[moveNum][CASTLING][0] = initialRights[0];
4601         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4602         r = boards[moveNum][CASTLING][1] = initialRights[1];
4603         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4604         r = boards[moveNum][CASTLING][3] = initialRights[3];
4605         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4606         r = boards[moveNum][CASTLING][4] = initialRights[4];
4607         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4608         /* wildcastle kludge: always assume King has rights */
4609         r = boards[moveNum][CASTLING][2] = initialRights[2];
4610         r = boards[moveNum][CASTLING][5] = initialRights[5];
4611     }
4612     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4613     boards[moveNum][EP_STATUS] = EP_NONE;
4614     if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4615     if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4616     if(double_push !=  -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4617
4618
4619     if (ics_getting_history == H_GOT_REQ_HEADER ||
4620         ics_getting_history == H_GOT_UNREQ_HEADER) {
4621         /* This was an initial position from a move list, not
4622            the current position */
4623         return;
4624     }
4625
4626     /* Update currentMove and known move number limits */
4627     newMove = newGame || moveNum > forwardMostMove;
4628
4629     if (newGame) {
4630         forwardMostMove = backwardMostMove = currentMove = moveNum;
4631         if (gameMode == IcsExamining && moveNum == 0) {
4632           /* Workaround for ICS limitation: we are not told the wild
4633              type when starting to examine a game.  But if we ask for
4634              the move list, the move list header will tell us */
4635             ics_getting_history = H_REQUESTED;
4636             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4637             SendToICS(str);
4638         }
4639     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4640                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4641 #if ZIPPY
4642         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4643         /* [HGM] applied this also to an engine that is silently watching        */
4644         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4645             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4646             gameInfo.variant == currentlyInitializedVariant) {
4647           takeback = forwardMostMove - moveNum;
4648           for (i = 0; i < takeback; i++) {
4649             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4650             SendToProgram("undo\n", &first);
4651           }
4652         }
4653 #endif
4654
4655         forwardMostMove = moveNum;
4656         if (!pausing || currentMove > forwardMostMove)
4657           currentMove = forwardMostMove;
4658     } else {
4659         /* New part of history that is not contiguous with old part */
4660         if (pausing && gameMode == IcsExamining) {
4661             pauseExamInvalid = TRUE;
4662             forwardMostMove = pauseExamForwardMostMove;
4663             return;
4664         }
4665         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4666 #if ZIPPY
4667             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4668                 // [HGM] when we will receive the move list we now request, it will be
4669                 // fed to the engine from the first move on. So if the engine is not
4670                 // in the initial position now, bring it there.
4671                 InitChessProgram(&first, 0);
4672             }
4673 #endif
4674             ics_getting_history = H_REQUESTED;
4675             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4676             SendToICS(str);
4677         }
4678         forwardMostMove = backwardMostMove = currentMove = moveNum;
4679     }
4680
4681     /* Update the clocks */
4682     if (strchr(elapsed_time, '.')) {
4683       /* Time is in ms */
4684       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4685       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4686     } else {
4687       /* Time is in seconds */
4688       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4689       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4690     }
4691
4692
4693 #if ZIPPY
4694     if (appData.zippyPlay && newGame &&
4695         gameMode != IcsObserving && gameMode != IcsIdle &&
4696         gameMode != IcsExamining)
4697       ZippyFirstBoard(moveNum, basetime, increment);
4698 #endif
4699
4700     /* Put the move on the move list, first converting
4701        to canonical algebraic form. */
4702     if (moveNum > 0) {
4703   if (appData.debugMode) {
4704     int f = forwardMostMove;
4705     fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4706             boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4707             boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4708     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4709     fprintf(debugFP, "moveNum = %d\n", moveNum);
4710     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4711     setbuf(debugFP, NULL);
4712   }
4713         if (moveNum <= backwardMostMove) {
4714             /* We don't know what the board looked like before
4715                this move.  Punt. */
4716           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4717             strcat(parseList[moveNum - 1], " ");
4718             strcat(parseList[moveNum - 1], elapsed_time);
4719             moveList[moveNum - 1][0] = NULLCHAR;
4720         } else if (strcmp(move_str, "none") == 0) {
4721             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4722             /* Again, we don't know what the board looked like;
4723                this is really the start of the game. */
4724             parseList[moveNum - 1][0] = NULLCHAR;
4725             moveList[moveNum - 1][0] = NULLCHAR;
4726             backwardMostMove = moveNum;
4727             startedFromSetupPosition = TRUE;
4728             fromX = fromY = toX = toY = -1;
4729         } else {
4730           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4731           //                 So we parse the long-algebraic move string in stead of the SAN move
4732           int valid; char buf[MSG_SIZ], *prom;
4733
4734           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4735                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4736           // str looks something like "Q/a1-a2"; kill the slash
4737           if(str[1] == '/')
4738             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4739           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4740           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4741                 strcat(buf, prom); // long move lacks promo specification!
4742           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4743                 if(appData.debugMode)
4744                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4745                 safeStrCpy(move_str, buf, MSG_SIZ);
4746           }
4747           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4748                                 &fromX, &fromY, &toX, &toY, &promoChar)
4749                || ParseOneMove(buf, moveNum - 1, &moveType,
4750                                 &fromX, &fromY, &toX, &toY, &promoChar);
4751           // end of long SAN patch
4752           if (valid) {
4753             (void) CoordsToAlgebraic(boards[moveNum - 1],
4754                                      PosFlags(moveNum - 1),
4755                                      fromY, fromX, toY, toX, promoChar,
4756                                      parseList[moveNum-1]);
4757             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4758               case MT_NONE:
4759               case MT_STALEMATE:
4760               default:
4761                 break;
4762               case MT_CHECK:
4763                 if(gameInfo.variant != VariantShogi)
4764                     strcat(parseList[moveNum - 1], "+");
4765                 break;
4766               case MT_CHECKMATE:
4767               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4768                 strcat(parseList[moveNum - 1], "#");
4769                 break;
4770             }
4771             strcat(parseList[moveNum - 1], " ");
4772             strcat(parseList[moveNum - 1], elapsed_time);
4773             /* currentMoveString is set as a side-effect of ParseOneMove */
4774             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4775             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4776             strcat(moveList[moveNum - 1], "\n");
4777
4778             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4779                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4780               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4781                 ChessSquare old, new = boards[moveNum][k][j];
4782                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4783                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4784                   if(old == new) continue;
4785                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4786                   else if(new == WhiteWazir || new == BlackWazir) {
4787                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4788                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4789                       else boards[moveNum][k][j] = old; // preserve type of Gold
4790                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4791                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4792               }
4793           } else {
4794             /* Move from ICS was illegal!?  Punt. */
4795             if (appData.debugMode) {
4796               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4797               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4798             }
4799             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4800             strcat(parseList[moveNum - 1], " ");
4801             strcat(parseList[moveNum - 1], elapsed_time);
4802             moveList[moveNum - 1][0] = NULLCHAR;
4803             fromX = fromY = toX = toY = -1;
4804           }
4805         }
4806   if (appData.debugMode) {
4807     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4808     setbuf(debugFP, NULL);
4809   }
4810
4811 #if ZIPPY
4812         /* Send move to chess program (BEFORE animating it). */
4813         if (appData.zippyPlay && !newGame && newMove &&
4814            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4815
4816             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4817                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4818                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4819                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4820                             move_str);
4821                     DisplayError(str, 0);
4822                 } else {
4823                     if (first.sendTime) {
4824                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4825                     }
4826                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4827                     if (firstMove && !bookHit) {
4828                         firstMove = FALSE;
4829                         if (first.useColors) {
4830                           SendToProgram(gameMode == IcsPlayingWhite ?
4831                                         "white\ngo\n" :
4832                                         "black\ngo\n", &first);
4833                         } else {
4834                           SendToProgram("go\n", &first);
4835                         }
4836                         first.maybeThinking = TRUE;
4837                     }
4838                 }
4839             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4840               if (moveList[moveNum - 1][0] == NULLCHAR) {
4841                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4842                 DisplayError(str, 0);
4843               } else {
4844                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4845                 SendMoveToProgram(moveNum - 1, &first);
4846               }
4847             }
4848         }
4849 #endif
4850     }
4851
4852     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4853         /* If move comes from a remote source, animate it.  If it
4854            isn't remote, it will have already been animated. */
4855         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4856             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4857         }
4858         if (!pausing && appData.highlightLastMove) {
4859             SetHighlights(fromX, fromY, toX, toY);
4860         }
4861     }
4862
4863     /* Start the clocks */
4864     whiteFlag = blackFlag = FALSE;
4865     appData.clockMode = !(basetime == 0 && increment == 0);
4866     if (ticking == 0) {
4867       ics_clock_paused = TRUE;
4868       StopClocks();
4869     } else if (ticking == 1) {
4870       ics_clock_paused = FALSE;
4871     }
4872     if (gameMode == IcsIdle ||
4873         relation == RELATION_OBSERVING_STATIC ||
4874         relation == RELATION_EXAMINING ||
4875         ics_clock_paused)
4876       DisplayBothClocks();
4877     else
4878       StartClocks();
4879
4880     /* Display opponents and material strengths */
4881     if (gameInfo.variant != VariantBughouse &&
4882         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4883         if (tinyLayout || smallLayout) {
4884             if(gameInfo.variant == VariantNormal)
4885               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4886                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4887                     basetime, increment);
4888             else
4889               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4890                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4891                     basetime, increment, (int) gameInfo.variant);
4892         } else {
4893             if(gameInfo.variant == VariantNormal)
4894               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4895                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4896                     basetime, increment);
4897             else
4898               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
4899                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4900                     basetime, increment, VariantName(gameInfo.variant));
4901         }
4902         DisplayTitle(str);
4903   if (appData.debugMode) {
4904     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4905   }
4906     }
4907
4908
4909     /* Display the board */
4910     if (!pausing && !appData.noGUI) {
4911
4912       if (appData.premove)
4913           if (!gotPremove ||
4914              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4915              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4916               ClearPremoveHighlights();
4917
4918       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4919         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4920       DrawPosition(j, boards[currentMove]);
4921
4922       DisplayMove(moveNum - 1);
4923       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4924             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4925               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4926         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4927       }
4928     }
4929
4930     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4931 #if ZIPPY
4932     if(bookHit) { // [HGM] book: simulate book reply
4933         static char bookMove[MSG_SIZ]; // a bit generous?
4934
4935         programStats.nodes = programStats.depth = programStats.time =
4936         programStats.score = programStats.got_only_move = 0;
4937         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4938
4939         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4940         strcat(bookMove, bookHit);
4941         HandleMachineMove(bookMove, &first);
4942     }
4943 #endif
4944 }
4945
4946 void
4947 GetMoveListEvent ()
4948 {
4949     char buf[MSG_SIZ];
4950     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4951         ics_getting_history = H_REQUESTED;
4952         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4953         SendToICS(buf);
4954     }
4955 }
4956
4957 void
4958 SendToBoth (char *msg)
4959 {   // to make it easy to keep two engines in step in dual analysis
4960     SendToProgram(msg, &first);
4961     if(second.analyzing) SendToProgram(msg, &second);
4962 }
4963
4964 void
4965 AnalysisPeriodicEvent (int force)
4966 {
4967     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4968          && !force) || !appData.periodicUpdates)
4969       return;
4970
4971     /* Send . command to Crafty to collect stats */
4972     SendToBoth(".\n");
4973
4974     /* Don't send another until we get a response (this makes
4975        us stop sending to old Crafty's which don't understand
4976        the "." command (sending illegal cmds resets node count & time,
4977        which looks bad)) */
4978     programStats.ok_to_send = 0;
4979 }
4980
4981 void
4982 ics_update_width (int new_width)
4983 {
4984         ics_printf("set width %d\n", new_width);
4985 }
4986
4987 void
4988 SendMoveToProgram (int moveNum, ChessProgramState *cps)
4989 {
4990     char buf[MSG_SIZ];
4991
4992     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
4993         // null move in variant where engine does not understand it (for analysis purposes)
4994         SendBoard(cps, moveNum + 1); // send position after move in stead.
4995         return;
4996     }
4997     if (cps->useUsermove) {
4998       SendToProgram("usermove ", cps);
4999     }
5000     if (cps->useSAN) {
5001       char *space;
5002       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
5003         int len = space - parseList[moveNum];
5004         memcpy(buf, parseList[moveNum], len);
5005         buf[len++] = '\n';
5006         buf[len] = NULLCHAR;
5007       } else {
5008         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
5009       }
5010       SendToProgram(buf, cps);
5011     } else {
5012       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
5013         AlphaRank(moveList[moveNum], 4);
5014         SendToProgram(moveList[moveNum], cps);
5015         AlphaRank(moveList[moveNum], 4); // and back
5016       } else
5017       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
5018        * the engine. It would be nice to have a better way to identify castle
5019        * moves here. */
5020       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
5021                                                                          && cps->useOOCastle) {
5022         int fromX = moveList[moveNum][0] - AAA;
5023         int fromY = moveList[moveNum][1] - ONE;
5024         int toX = moveList[moveNum][2] - AAA;
5025         int toY = moveList[moveNum][3] - ONE;
5026         if((boards[moveNum][fromY][fromX] == WhiteKing
5027             && boards[moveNum][toY][toX] == WhiteRook)
5028            || (boards[moveNum][fromY][fromX] == BlackKing
5029                && boards[moveNum][toY][toX] == BlackRook)) {
5030           if(toX > fromX) SendToProgram("O-O\n", cps);
5031           else SendToProgram("O-O-O\n", cps);
5032         }
5033         else SendToProgram(moveList[moveNum], cps);
5034       } else
5035       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5036         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5037           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5038           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5039                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5040         } else
5041           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5042                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5043         SendToProgram(buf, cps);
5044       }
5045       else SendToProgram(moveList[moveNum], cps);
5046       /* End of additions by Tord */
5047     }
5048
5049     /* [HGM] setting up the opening has brought engine in force mode! */
5050     /*       Send 'go' if we are in a mode where machine should play. */
5051     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5052         (gameMode == TwoMachinesPlay   ||
5053 #if ZIPPY
5054          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
5055 #endif
5056          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5057         SendToProgram("go\n", cps);
5058   if (appData.debugMode) {
5059     fprintf(debugFP, "(extra)\n");
5060   }
5061     }
5062     setboardSpoiledMachineBlack = 0;
5063 }
5064
5065 void
5066 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5067 {
5068     char user_move[MSG_SIZ];
5069     char suffix[4];
5070
5071     if(gameInfo.variant == VariantSChess && promoChar) {
5072         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5073         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5074     } else suffix[0] = NULLCHAR;
5075
5076     switch (moveType) {
5077       default:
5078         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5079                 (int)moveType, fromX, fromY, toX, toY);
5080         DisplayError(user_move + strlen("say "), 0);
5081         break;
5082       case WhiteKingSideCastle:
5083       case BlackKingSideCastle:
5084       case WhiteQueenSideCastleWild:
5085       case BlackQueenSideCastleWild:
5086       /* PUSH Fabien */
5087       case WhiteHSideCastleFR:
5088       case BlackHSideCastleFR:
5089       /* POP Fabien */
5090         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5091         break;
5092       case WhiteQueenSideCastle:
5093       case BlackQueenSideCastle:
5094       case WhiteKingSideCastleWild:
5095       case BlackKingSideCastleWild:
5096       /* PUSH Fabien */
5097       case WhiteASideCastleFR:
5098       case BlackASideCastleFR:
5099       /* POP Fabien */
5100         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5101         break;
5102       case WhiteNonPromotion:
5103       case BlackNonPromotion:
5104         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5105         break;
5106       case WhitePromotion:
5107       case BlackPromotion:
5108         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
5109            gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
5110           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5111                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5112                 PieceToChar(WhiteFerz));
5113         else if(gameInfo.variant == VariantGreat)
5114           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5115                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5116                 PieceToChar(WhiteMan));
5117         else
5118           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5119                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5120                 promoChar);
5121         break;
5122       case WhiteDrop:
5123       case BlackDrop:
5124       drop:
5125         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5126                  ToUpper(PieceToChar((ChessSquare) fromX)),
5127                  AAA + toX, ONE + toY);
5128         break;
5129       case IllegalMove:  /* could be a variant we don't quite understand */
5130         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5131       case NormalMove:
5132       case WhiteCapturesEnPassant:
5133       case BlackCapturesEnPassant:
5134         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5135                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5136         break;
5137     }
5138     SendToICS(user_move);
5139     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5140         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5141 }
5142
5143 void
5144 UploadGameEvent ()
5145 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5146     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5147     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5148     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5149       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5150       return;
5151     }
5152     if(gameMode != IcsExamining) { // is this ever not the case?
5153         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5154
5155         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5156           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5157         } else { // on FICS we must first go to general examine mode
5158           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5159         }
5160         if(gameInfo.variant != VariantNormal) {
5161             // try figure out wild number, as xboard names are not always valid on ICS
5162             for(i=1; i<=36; i++) {
5163               snprintf(buf, MSG_SIZ, "wild/%d", i);
5164                 if(StringToVariant(buf) == gameInfo.variant) break;
5165             }
5166             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5167             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5168             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5169         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5170         SendToICS(ics_prefix);
5171         SendToICS(buf);
5172         if(startedFromSetupPosition || backwardMostMove != 0) {
5173           fen = PositionToFEN(backwardMostMove, NULL, 1);
5174           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5175             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5176             SendToICS(buf);
5177           } else { // FICS: everything has to set by separate bsetup commands
5178             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5179             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5180             SendToICS(buf);
5181             if(!WhiteOnMove(backwardMostMove)) {
5182                 SendToICS("bsetup tomove black\n");
5183             }
5184             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5185             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5186             SendToICS(buf);
5187             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5188             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5189             SendToICS(buf);
5190             i = boards[backwardMostMove][EP_STATUS];
5191             if(i >= 0) { // set e.p.
5192               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5193                 SendToICS(buf);
5194             }
5195             bsetup++;
5196           }
5197         }
5198       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5199             SendToICS("bsetup done\n"); // switch to normal examining.
5200     }
5201     for(i = backwardMostMove; i<last; i++) {
5202         char buf[20];
5203         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5204         if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5205             int len = strlen(moveList[i]);
5206             snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5207             if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5208         }
5209         SendToICS(buf);
5210     }
5211     SendToICS(ics_prefix);
5212     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5213 }
5214
5215 void
5216 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5217 {
5218     if (rf == DROP_RANK) {
5219       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5220       sprintf(move, "%c@%c%c\n",
5221                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5222     } else {
5223         if (promoChar == 'x' || promoChar == NULLCHAR) {
5224           sprintf(move, "%c%c%c%c\n",
5225                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5226         } else {
5227             sprintf(move, "%c%c%c%c%c\n",
5228                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5229         }
5230     }
5231 }
5232
5233 void
5234 ProcessICSInitScript (FILE *f)
5235 {
5236     char buf[MSG_SIZ];
5237
5238     while (fgets(buf, MSG_SIZ, f)) {
5239         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5240     }
5241
5242     fclose(f);
5243 }
5244
5245
5246 static int lastX, lastY, lastLeftX, lastLeftY, selectFlag, dragging;
5247 static ClickType lastClickType;
5248
5249 void
5250 Sweep (int step)
5251 {
5252     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5253     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5254     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5255     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5256     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5257     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5258     do {
5259         promoSweep -= step;
5260         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5261         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5262         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5263         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5264         if(!step) step = -1;
5265     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5266             appData.testLegality && (promoSweep == king ||
5267             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5268     if(toX >= 0) {
5269         int victim = boards[currentMove][toY][toX];
5270         boards[currentMove][toY][toX] = promoSweep;
5271         DrawPosition(FALSE, boards[currentMove]);
5272         boards[currentMove][toY][toX] = victim;
5273     } else
5274     ChangeDragPiece(promoSweep);
5275 }
5276
5277 int
5278 PromoScroll (int x, int y)
5279 {
5280   int step = 0;
5281
5282   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5283   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5284   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5285   if(!step) return FALSE;
5286   lastX = x; lastY = y;
5287   if((promoSweep < BlackPawn) == flipView) step = -step;
5288   if(step > 0) selectFlag = 1;
5289   if(!selectFlag) Sweep(step);
5290   return FALSE;
5291 }
5292
5293 void
5294 NextPiece (int step)
5295 {
5296     ChessSquare piece = boards[currentMove][toY][toX];
5297     do {
5298         pieceSweep -= step;
5299         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5300         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5301         if(!step) step = -1;
5302     } while(PieceToChar(pieceSweep) == '.');
5303     boards[currentMove][toY][toX] = pieceSweep;
5304     DrawPosition(FALSE, boards[currentMove]);
5305     boards[currentMove][toY][toX] = piece;
5306 }
5307 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5308 void
5309 AlphaRank (char *move, int n)
5310 {
5311 //    char *p = move, c; int x, y;
5312
5313     if (appData.debugMode) {
5314         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5315     }
5316
5317     if(move[1]=='*' &&
5318        move[2]>='0' && move[2]<='9' &&
5319        move[3]>='a' && move[3]<='x'    ) {
5320         move[1] = '@';
5321         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5322         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5323     } else
5324     if(move[0]>='0' && move[0]<='9' &&
5325        move[1]>='a' && move[1]<='x' &&
5326        move[2]>='0' && move[2]<='9' &&
5327        move[3]>='a' && move[3]<='x'    ) {
5328         /* input move, Shogi -> normal */
5329         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5330         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5331         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5332         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5333     } else
5334     if(move[1]=='@' &&
5335        move[3]>='0' && move[3]<='9' &&
5336        move[2]>='a' && move[2]<='x'    ) {
5337         move[1] = '*';
5338         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5339         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5340     } else
5341     if(
5342        move[0]>='a' && move[0]<='x' &&
5343        move[3]>='0' && move[3]<='9' &&
5344        move[2]>='a' && move[2]<='x'    ) {
5345          /* output move, normal -> Shogi */
5346         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5347         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5348         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5349         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5350         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5351     }
5352     if (appData.debugMode) {
5353         fprintf(debugFP, "   out = '%s'\n", move);
5354     }
5355 }
5356
5357 char yy_textstr[8000];
5358
5359 /* Parser for moves from gnuchess, ICS, or user typein box */
5360 Boolean
5361 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5362 {
5363     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5364
5365     switch (*moveType) {
5366       case WhitePromotion:
5367       case BlackPromotion:
5368       case WhiteNonPromotion:
5369       case BlackNonPromotion:
5370       case NormalMove:
5371       case WhiteCapturesEnPassant:
5372       case BlackCapturesEnPassant:
5373       case WhiteKingSideCastle:
5374       case WhiteQueenSideCastle:
5375       case BlackKingSideCastle:
5376       case BlackQueenSideCastle:
5377       case WhiteKingSideCastleWild:
5378       case WhiteQueenSideCastleWild:
5379       case BlackKingSideCastleWild:
5380       case BlackQueenSideCastleWild:
5381       /* Code added by Tord: */
5382       case WhiteHSideCastleFR:
5383       case WhiteASideCastleFR:
5384       case BlackHSideCastleFR:
5385       case BlackASideCastleFR:
5386       /* End of code added by Tord */
5387       case IllegalMove:         /* bug or odd chess variant */
5388         *fromX = currentMoveString[0] - AAA;
5389         *fromY = currentMoveString[1] - ONE;
5390         *toX = currentMoveString[2] - AAA;
5391         *toY = currentMoveString[3] - ONE;
5392         *promoChar = currentMoveString[4];
5393         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5394             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5395     if (appData.debugMode) {
5396         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5397     }
5398             *fromX = *fromY = *toX = *toY = 0;
5399             return FALSE;
5400         }
5401         if (appData.testLegality) {
5402           return (*moveType != IllegalMove);
5403         } else {
5404           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5405                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5406         }
5407
5408       case WhiteDrop:
5409       case BlackDrop:
5410         *fromX = *moveType == WhiteDrop ?
5411           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5412           (int) CharToPiece(ToLower(currentMoveString[0]));
5413         *fromY = DROP_RANK;
5414         *toX = currentMoveString[2] - AAA;
5415         *toY = currentMoveString[3] - ONE;
5416         *promoChar = NULLCHAR;
5417         return TRUE;
5418
5419       case AmbiguousMove:
5420       case ImpossibleMove:
5421       case EndOfFile:
5422       case ElapsedTime:
5423       case Comment:
5424       case PGNTag:
5425       case NAG:
5426       case WhiteWins:
5427       case BlackWins:
5428       case GameIsDrawn:
5429       default:
5430     if (appData.debugMode) {
5431         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5432     }
5433         /* bug? */
5434         *fromX = *fromY = *toX = *toY = 0;
5435         *promoChar = NULLCHAR;
5436         return FALSE;
5437     }
5438 }
5439
5440 Boolean pushed = FALSE;
5441 char *lastParseAttempt;
5442
5443 void
5444 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5445 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5446   int fromX, fromY, toX, toY; char promoChar;
5447   ChessMove moveType;
5448   Boolean valid;
5449   int nr = 0;
5450
5451   lastParseAttempt = pv; if(!*pv) return;    // turns out we crash when we parse an empty PV
5452   if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5453     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5454     pushed = TRUE;
5455   }
5456   endPV = forwardMostMove;
5457   do {
5458     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5459     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5460     lastParseAttempt = pv;
5461     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5462     if(!valid && nr == 0 &&
5463        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5464         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5465         // Hande case where played move is different from leading PV move
5466         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5467         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5468         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5469         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5470           endPV += 2; // if position different, keep this
5471           moveList[endPV-1][0] = fromX + AAA;
5472           moveList[endPV-1][1] = fromY + ONE;
5473           moveList[endPV-1][2] = toX + AAA;
5474           moveList[endPV-1][3] = toY + ONE;
5475           parseList[endPV-1][0] = NULLCHAR;
5476           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5477         }
5478       }
5479     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5480     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5481     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5482     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5483         valid++; // allow comments in PV
5484         continue;
5485     }
5486     nr++;
5487     if(endPV+1 > framePtr) break; // no space, truncate
5488     if(!valid) break;
5489     endPV++;
5490     CopyBoard(boards[endPV], boards[endPV-1]);
5491     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5492     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5493     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5494     CoordsToAlgebraic(boards[endPV - 1],
5495                              PosFlags(endPV - 1),
5496                              fromY, fromX, toY, toX, promoChar,
5497                              parseList[endPV - 1]);
5498   } while(valid);
5499   if(atEnd == 2) return; // used hidden, for PV conversion
5500   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5501   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5502   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5503                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5504   DrawPosition(TRUE, boards[currentMove]);
5505 }
5506
5507 int
5508 MultiPV (ChessProgramState *cps)
5509 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5510         int i;
5511         for(i=0; i<cps->nrOptions; i++)
5512             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5513                 return i;
5514         return -1;
5515 }
5516
5517 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
5518
5519 Boolean
5520 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5521 {
5522         int startPV, multi, lineStart, origIndex = index;
5523         char *p, buf2[MSG_SIZ];
5524         ChessProgramState *cps = (pane ? &second : &first);
5525
5526         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5527         lastX = x; lastY = y;
5528         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5529         lineStart = startPV = index;
5530         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5531         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5532         index = startPV;
5533         do{ while(buf[index] && buf[index] != '\n') index++;
5534         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5535         buf[index] = 0;
5536         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(cps)) >= 0) {
5537                 int n = cps->option[multi].value;
5538                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5539                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5540                 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5541                 cps->option[multi].value = n;
5542                 *start = *end = 0;
5543                 return FALSE;
5544         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5545                 ExcludeClick(origIndex - lineStart);
5546                 return FALSE;
5547         }
5548         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5549         *start = startPV; *end = index-1;
5550         extendGame = (gameMode == AnalyzeMode && appData.autoExtend);
5551         return TRUE;
5552 }
5553
5554 char *
5555 PvToSAN (char *pv)
5556 {
5557         static char buf[10*MSG_SIZ];
5558         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5559         *buf = NULLCHAR;
5560         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5561         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5562         for(i = forwardMostMove; i<endPV; i++){
5563             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5564             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5565             k += strlen(buf+k);
5566         }
5567         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5568         if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5569         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5570         endPV = savedEnd;
5571         return buf;
5572 }
5573
5574 Boolean
5575 LoadPV (int x, int y)
5576 { // called on right mouse click to load PV
5577   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5578   lastX = x; lastY = y;
5579   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5580   extendGame = FALSE;
5581   return TRUE;
5582 }
5583
5584 void
5585 UnLoadPV ()
5586 {
5587   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5588   if(endPV < 0) return;
5589   if(appData.autoCopyPV) CopyFENToClipboard();
5590   endPV = -1;
5591   if(extendGame && currentMove > forwardMostMove) {
5592         Boolean saveAnimate = appData.animate;
5593         if(pushed) {
5594             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5595                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5596             } else storedGames--; // abandon shelved tail of original game
5597         }
5598         pushed = FALSE;
5599         forwardMostMove = currentMove;
5600         currentMove = oldFMM;
5601         appData.animate = FALSE;
5602         ToNrEvent(forwardMostMove);
5603         appData.animate = saveAnimate;
5604   }
5605   currentMove = forwardMostMove;
5606   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5607   ClearPremoveHighlights();
5608   DrawPosition(TRUE, boards[currentMove]);
5609 }
5610
5611 void
5612 MovePV (int x, int y, int h)
5613 { // step through PV based on mouse coordinates (called on mouse move)
5614   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5615
5616   // we must somehow check if right button is still down (might be released off board!)
5617   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5618   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5619   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5620   if(!step) return;
5621   lastX = x; lastY = y;
5622
5623   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5624   if(endPV < 0) return;
5625   if(y < margin) step = 1; else
5626   if(y > h - margin) step = -1;
5627   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5628   currentMove += step;
5629   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5630   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5631                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5632   DrawPosition(FALSE, boards[currentMove]);
5633 }
5634
5635
5636 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5637 // All positions will have equal probability, but the current method will not provide a unique
5638 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5639 #define DARK 1
5640 #define LITE 2
5641 #define ANY 3
5642
5643 int squaresLeft[4];
5644 int piecesLeft[(int)BlackPawn];
5645 int seed, nrOfShuffles;
5646
5647 void
5648 GetPositionNumber ()
5649 {       // sets global variable seed
5650         int i;
5651
5652         seed = appData.defaultFrcPosition;
5653         if(seed < 0) { // randomize based on time for negative FRC position numbers
5654                 for(i=0; i<50; i++) seed += random();
5655                 seed = random() ^ random() >> 8 ^ random() << 8;
5656                 if(seed<0) seed = -seed;
5657         }
5658 }
5659
5660 int
5661 put (Board board, int pieceType, int rank, int n, int shade)
5662 // put the piece on the (n-1)-th empty squares of the given shade
5663 {
5664         int i;
5665
5666         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5667                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5668                         board[rank][i] = (ChessSquare) pieceType;
5669                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5670                         squaresLeft[ANY]--;
5671                         piecesLeft[pieceType]--;
5672                         return i;
5673                 }
5674         }
5675         return -1;
5676 }
5677
5678
5679 void
5680 AddOnePiece (Board board, int pieceType, int rank, int shade)
5681 // calculate where the next piece goes, (any empty square), and put it there
5682 {
5683         int i;
5684
5685         i = seed % squaresLeft[shade];
5686         nrOfShuffles *= squaresLeft[shade];
5687         seed /= squaresLeft[shade];
5688         put(board, pieceType, rank, i, shade);
5689 }
5690
5691 void
5692 AddTwoPieces (Board board, int pieceType, int rank)
5693 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5694 {
5695         int i, n=squaresLeft[ANY], j=n-1, k;
5696
5697         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5698         i = seed % k;  // pick one
5699         nrOfShuffles *= k;
5700         seed /= k;
5701         while(i >= j) i -= j--;
5702         j = n - 1 - j; i += j;
5703         put(board, pieceType, rank, j, ANY);
5704         put(board, pieceType, rank, i, ANY);
5705 }
5706
5707 void
5708 SetUpShuffle (Board board, int number)
5709 {
5710         int i, p, first=1;
5711
5712         GetPositionNumber(); nrOfShuffles = 1;
5713
5714         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5715         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5716         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5717
5718         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5719
5720         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5721             p = (int) board[0][i];
5722             if(p < (int) BlackPawn) piecesLeft[p] ++;
5723             board[0][i] = EmptySquare;
5724         }
5725
5726         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5727             // shuffles restricted to allow normal castling put KRR first
5728             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5729                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5730             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5731                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5732             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5733                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5734             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5735                 put(board, WhiteRook, 0, 0, ANY);
5736             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5737         }
5738
5739         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5740             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5741             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5742                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5743                 while(piecesLeft[p] >= 2) {
5744                     AddOnePiece(board, p, 0, LITE);
5745                     AddOnePiece(board, p, 0, DARK);
5746                 }
5747                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5748             }
5749
5750         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5751             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5752             // but we leave King and Rooks for last, to possibly obey FRC restriction
5753             if(p == (int)WhiteRook) continue;
5754             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5755             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5756         }
5757
5758         // now everything is placed, except perhaps King (Unicorn) and Rooks
5759
5760         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5761             // Last King gets castling rights
5762             while(piecesLeft[(int)WhiteUnicorn]) {
5763                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5764                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5765             }
5766
5767             while(piecesLeft[(int)WhiteKing]) {
5768                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5769                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5770             }
5771
5772
5773         } else {
5774             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5775             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5776         }
5777
5778         // Only Rooks can be left; simply place them all
5779         while(piecesLeft[(int)WhiteRook]) {
5780                 i = put(board, WhiteRook, 0, 0, ANY);
5781                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5782                         if(first) {
5783                                 first=0;
5784                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5785                         }
5786                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5787                 }
5788         }
5789         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5790             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5791         }
5792
5793         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5794 }
5795
5796 int
5797 SetCharTable (char *table, const char * map)
5798 /* [HGM] moved here from winboard.c because of its general usefulness */
5799 /*       Basically a safe strcpy that uses the last character as King */
5800 {
5801     int result = FALSE; int NrPieces;
5802
5803     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5804                     && NrPieces >= 12 && !(NrPieces&1)) {
5805         int i; /* [HGM] Accept even length from 12 to 34 */
5806
5807         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5808         for( i=0; i<NrPieces/2-1; i++ ) {
5809             table[i] = map[i];
5810             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5811         }
5812         table[(int) WhiteKing]  = map[NrPieces/2-1];
5813         table[(int) BlackKing]  = map[NrPieces-1];
5814
5815         result = TRUE;
5816     }
5817
5818     return result;
5819 }
5820
5821 void
5822 Prelude (Board board)
5823 {       // [HGM] superchess: random selection of exo-pieces
5824         int i, j, k; ChessSquare p;
5825         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5826
5827         GetPositionNumber(); // use FRC position number
5828
5829         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5830             SetCharTable(pieceToChar, appData.pieceToCharTable);
5831             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5832                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5833         }
5834
5835         j = seed%4;                 seed /= 4;
5836         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5837         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5838         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5839         j = seed%3 + (seed%3 >= j); seed /= 3;
5840         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5841         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5842         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5843         j = seed%3;                 seed /= 3;
5844         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5845         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5846         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5847         j = seed%2 + (seed%2 >= j); seed /= 2;
5848         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5849         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5850         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5851         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5852         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5853         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5854         put(board, exoPieces[0],    0, 0, ANY);
5855         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5856 }
5857
5858 void
5859 InitPosition (int redraw)
5860 {
5861     ChessSquare (* pieces)[BOARD_FILES];
5862     int i, j, pawnRow, overrule,
5863     oldx = gameInfo.boardWidth,
5864     oldy = gameInfo.boardHeight,
5865     oldh = gameInfo.holdingsWidth;
5866     static int oldv;
5867
5868     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5869
5870     /* [AS] Initialize pv info list [HGM] and game status */
5871     {
5872         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5873             pvInfoList[i].depth = 0;
5874             boards[i][EP_STATUS] = EP_NONE;
5875             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5876         }
5877
5878         initialRulePlies = 0; /* 50-move counter start */
5879
5880         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5881         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5882     }
5883
5884
5885     /* [HGM] logic here is completely changed. In stead of full positions */
5886     /* the initialized data only consist of the two backranks. The switch */
5887     /* selects which one we will use, which is than copied to the Board   */
5888     /* initialPosition, which for the rest is initialized by Pawns and    */
5889     /* empty squares. This initial position is then copied to boards[0],  */
5890     /* possibly after shuffling, so that it remains available.            */
5891
5892     gameInfo.holdingsWidth = 0; /* default board sizes */
5893     gameInfo.boardWidth    = 8;
5894     gameInfo.boardHeight   = 8;
5895     gameInfo.holdingsSize  = 0;
5896     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5897     for(i=0; i<BOARD_FILES-2; i++)
5898       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5899     initialPosition[EP_STATUS] = EP_NONE;
5900     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5901     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5902          SetCharTable(pieceNickName, appData.pieceNickNames);
5903     else SetCharTable(pieceNickName, "............");
5904     pieces = FIDEArray;
5905
5906     switch (gameInfo.variant) {
5907     case VariantFischeRandom:
5908       shuffleOpenings = TRUE;
5909     default:
5910       break;
5911     case VariantShatranj:
5912       pieces = ShatranjArray;
5913       nrCastlingRights = 0;
5914       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5915       break;
5916     case VariantMakruk:
5917       pieces = makrukArray;
5918       nrCastlingRights = 0;
5919       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5920       break;
5921     case VariantASEAN:
5922       pieces = aseanArray;
5923       nrCastlingRights = 0;
5924       SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk");
5925       break;
5926     case VariantTwoKings:
5927       pieces = twoKingsArray;
5928       break;
5929     case VariantGrand:
5930       pieces = GrandArray;
5931       nrCastlingRights = 0;
5932       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5933       gameInfo.boardWidth = 10;
5934       gameInfo.boardHeight = 10;
5935       gameInfo.holdingsSize = 7;
5936       break;
5937     case VariantCapaRandom:
5938       shuffleOpenings = TRUE;
5939     case VariantCapablanca:
5940       pieces = CapablancaArray;
5941       gameInfo.boardWidth = 10;
5942       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5943       break;
5944     case VariantGothic:
5945       pieces = GothicArray;
5946       gameInfo.boardWidth = 10;
5947       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5948       break;
5949     case VariantSChess:
5950       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5951       gameInfo.holdingsSize = 7;
5952       for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
5953       break;
5954     case VariantJanus:
5955       pieces = JanusArray;
5956       gameInfo.boardWidth = 10;
5957       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5958       nrCastlingRights = 6;
5959         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5960         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5961         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5962         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5963         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5964         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5965       break;
5966     case VariantFalcon:
5967       pieces = FalconArray;
5968       gameInfo.boardWidth = 10;
5969       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5970       break;
5971     case VariantXiangqi:
5972       pieces = XiangqiArray;
5973       gameInfo.boardWidth  = 9;
5974       gameInfo.boardHeight = 10;
5975       nrCastlingRights = 0;
5976       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5977       break;
5978     case VariantShogi:
5979       pieces = ShogiArray;
5980       gameInfo.boardWidth  = 9;
5981       gameInfo.boardHeight = 9;
5982       gameInfo.holdingsSize = 7;
5983       nrCastlingRights = 0;
5984       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5985       break;
5986     case VariantCourier:
5987       pieces = CourierArray;
5988       gameInfo.boardWidth  = 12;
5989       nrCastlingRights = 0;
5990       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5991       break;
5992     case VariantKnightmate:
5993       pieces = KnightmateArray;
5994       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5995       break;
5996     case VariantSpartan:
5997       pieces = SpartanArray;
5998       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5999       break;
6000     case VariantFairy:
6001       pieces = fairyArray;
6002       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
6003       break;
6004     case VariantGreat:
6005       pieces = GreatArray;
6006       gameInfo.boardWidth = 10;
6007       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
6008       gameInfo.holdingsSize = 8;
6009       break;
6010     case VariantSuper:
6011       pieces = FIDEArray;
6012       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
6013       gameInfo.holdingsSize = 8;
6014       startedFromSetupPosition = TRUE;
6015       break;
6016     case VariantCrazyhouse:
6017     case VariantBughouse:
6018       pieces = FIDEArray;
6019       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
6020       gameInfo.holdingsSize = 5;
6021       break;
6022     case VariantWildCastle:
6023       pieces = FIDEArray;
6024       /* !!?shuffle with kings guaranteed to be on d or e file */
6025       shuffleOpenings = 1;
6026       break;
6027     case VariantNoCastle:
6028       pieces = FIDEArray;
6029       nrCastlingRights = 0;
6030       /* !!?unconstrained back-rank shuffle */
6031       shuffleOpenings = 1;
6032       break;
6033     }
6034
6035     overrule = 0;
6036     if(appData.NrFiles >= 0) {
6037         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6038         gameInfo.boardWidth = appData.NrFiles;
6039     }
6040     if(appData.NrRanks >= 0) {
6041         gameInfo.boardHeight = appData.NrRanks;
6042     }
6043     if(appData.holdingsSize >= 0) {
6044         i = appData.holdingsSize;
6045         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6046         gameInfo.holdingsSize = i;
6047     }
6048     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6049     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6050         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6051
6052     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6053     if(pawnRow < 1) pawnRow = 1;
6054     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN || gameInfo.variant == VariantGrand) pawnRow = 2;
6055
6056     /* User pieceToChar list overrules defaults */
6057     if(appData.pieceToCharTable != NULL)
6058         SetCharTable(pieceToChar, appData.pieceToCharTable);
6059
6060     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6061
6062         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6063             s = (ChessSquare) 0; /* account holding counts in guard band */
6064         for( i=0; i<BOARD_HEIGHT; i++ )
6065             initialPosition[i][j] = s;
6066
6067         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6068         initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
6069         initialPosition[pawnRow][j] = WhitePawn;
6070         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6071         if(gameInfo.variant == VariantXiangqi) {
6072             if(j&1) {
6073                 initialPosition[pawnRow][j] =
6074                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6075                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6076                    initialPosition[2][j] = WhiteCannon;
6077                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6078                 }
6079             }
6080         }
6081         if(gameInfo.variant == VariantGrand) {
6082             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6083                initialPosition[0][j] = WhiteRook;
6084                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6085             }
6086         }
6087         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] =  pieces[1][j-gameInfo.holdingsWidth];
6088     }
6089     if( (gameInfo.variant == VariantShogi) && !overrule ) {
6090
6091             j=BOARD_LEFT+1;
6092             initialPosition[1][j] = WhiteBishop;
6093             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6094             j=BOARD_RGHT-2;
6095             initialPosition[1][j] = WhiteRook;
6096             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6097     }
6098
6099     if( nrCastlingRights == -1) {
6100         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6101         /*       This sets default castling rights from none to normal corners   */
6102         /* Variants with other castling rights must set them themselves above    */
6103         nrCastlingRights = 6;
6104
6105         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6106         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6107         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6108         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6109         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6110         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6111      }
6112
6113      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6114      if(gameInfo.variant == VariantGreat) { // promotion commoners
6115         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6116         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6117         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6118         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6119      }
6120      if( gameInfo.variant == VariantSChess ) {
6121       initialPosition[1][0] = BlackMarshall;
6122       initialPosition[2][0] = BlackAngel;
6123       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6124       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6125       initialPosition[1][1] = initialPosition[2][1] =
6126       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6127      }
6128   if (appData.debugMode) {
6129     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6130   }
6131     if(shuffleOpenings) {
6132         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6133         startedFromSetupPosition = TRUE;
6134     }
6135     if(startedFromPositionFile) {
6136       /* [HGM] loadPos: use PositionFile for every new game */
6137       CopyBoard(initialPosition, filePosition);
6138       for(i=0; i<nrCastlingRights; i++)
6139           initialRights[i] = filePosition[CASTLING][i];
6140       startedFromSetupPosition = TRUE;
6141     }
6142
6143     CopyBoard(boards[0], initialPosition);
6144
6145     if(oldx != gameInfo.boardWidth ||
6146        oldy != gameInfo.boardHeight ||
6147        oldv != gameInfo.variant ||
6148        oldh != gameInfo.holdingsWidth
6149                                          )
6150             InitDrawingSizes(-2 ,0);
6151
6152     oldv = gameInfo.variant;
6153     if (redraw)
6154       DrawPosition(TRUE, boards[currentMove]);
6155 }
6156
6157 void
6158 SendBoard (ChessProgramState *cps, int moveNum)
6159 {
6160     char message[MSG_SIZ];
6161
6162     if (cps->useSetboard) {
6163       char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6164       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6165       SendToProgram(message, cps);
6166       free(fen);
6167
6168     } else {
6169       ChessSquare *bp;
6170       int i, j, left=0, right=BOARD_WIDTH;
6171       /* Kludge to set black to move, avoiding the troublesome and now
6172        * deprecated "black" command.
6173        */
6174       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6175         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6176
6177       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6178
6179       SendToProgram("edit\n", cps);
6180       SendToProgram("#\n", cps);
6181       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6182         bp = &boards[moveNum][i][left];
6183         for (j = left; j < right; j++, bp++) {
6184           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6185           if ((int) *bp < (int) BlackPawn) {
6186             if(j == BOARD_RGHT+1)
6187                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6188             else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6189             if(message[0] == '+' || message[0] == '~') {
6190               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6191                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6192                         AAA + j, ONE + i);
6193             }
6194             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6195                 message[1] = BOARD_RGHT   - 1 - j + '1';
6196                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6197             }
6198             SendToProgram(message, cps);
6199           }
6200         }
6201       }
6202
6203       SendToProgram("c\n", cps);
6204       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6205         bp = &boards[moveNum][i][left];
6206         for (j = left; j < right; j++, bp++) {
6207           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6208           if (((int) *bp != (int) EmptySquare)
6209               && ((int) *bp >= (int) BlackPawn)) {
6210             if(j == BOARD_LEFT-2)
6211                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6212             else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6213                     AAA + j, ONE + i);
6214             if(message[0] == '+' || message[0] == '~') {
6215               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6216                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6217                         AAA + j, ONE + i);
6218             }
6219             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6220                 message[1] = BOARD_RGHT   - 1 - j + '1';
6221                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6222             }
6223             SendToProgram(message, cps);
6224           }
6225         }
6226       }
6227
6228       SendToProgram(".\n", cps);
6229     }
6230     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6231 }
6232
6233 char exclusionHeader[MSG_SIZ];
6234 int exCnt, excludePtr;
6235 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6236 static Exclusion excluTab[200];
6237 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6238
6239 static void
6240 WriteMap (int s)
6241 {
6242     int j;
6243     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6244     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6245 }
6246
6247 static void
6248 ClearMap ()
6249 {
6250     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6251     excludePtr = 24; exCnt = 0;
6252     WriteMap(0);
6253 }
6254
6255 static void
6256 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6257 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6258     char buf[2*MOVE_LEN], *p;
6259     Exclusion *e = excluTab;
6260     int i;
6261     for(i=0; i<exCnt; i++)
6262         if(e[i].ff == fromX && e[i].fr == fromY &&
6263            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6264     if(i == exCnt) { // was not in exclude list; add it
6265         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6266         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6267             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6268             return; // abort
6269         }
6270         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6271         excludePtr++; e[i].mark = excludePtr++;
6272         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6273         exCnt++;
6274     }
6275     exclusionHeader[e[i].mark] = state;
6276 }
6277
6278 static int
6279 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6280 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6281     char buf[MSG_SIZ];
6282     int j, k;
6283     ChessMove moveType;
6284     if((signed char)promoChar == -1) { // kludge to indicate best move
6285         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6286             return 1; // if unparsable, abort
6287     }
6288     // update exclusion map (resolving toggle by consulting existing state)
6289     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6290     j = k%8; k >>= 3;
6291     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6292     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6293          excludeMap[k] |=   1<<j;
6294     else excludeMap[k] &= ~(1<<j);
6295     // update header
6296     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6297     // inform engine
6298     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6299     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6300     SendToBoth(buf);
6301     return (state == '+');
6302 }
6303
6304 static void
6305 ExcludeClick (int index)
6306 {
6307     int i, j;
6308     Exclusion *e = excluTab;
6309     if(index < 25) { // none, best or tail clicked
6310         if(index < 13) { // none: include all
6311             WriteMap(0); // clear map
6312             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6313             SendToBoth("include all\n"); // and inform engine
6314         } else if(index > 18) { // tail
6315             if(exclusionHeader[19] == '-') { // tail was excluded
6316                 SendToBoth("include all\n");
6317                 WriteMap(0); // clear map completely
6318                 // now re-exclude selected moves
6319                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6320                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6321             } else { // tail was included or in mixed state
6322                 SendToBoth("exclude all\n");
6323                 WriteMap(0xFF); // fill map completely
6324                 // now re-include selected moves
6325                 j = 0; // count them
6326                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6327                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6328                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6329             }
6330         } else { // best
6331             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6332         }
6333     } else {
6334         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6335             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6336             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6337             break;
6338         }
6339     }
6340 }
6341
6342 ChessSquare
6343 DefaultPromoChoice (int white)
6344 {
6345     ChessSquare result;
6346     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6347        gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
6348         result = WhiteFerz; // no choice
6349     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6350         result= WhiteKing; // in Suicide Q is the last thing we want
6351     else if(gameInfo.variant == VariantSpartan)
6352         result = white ? WhiteQueen : WhiteAngel;
6353     else result = WhiteQueen;
6354     if(!white) result = WHITE_TO_BLACK result;
6355     return result;
6356 }
6357
6358 static int autoQueen; // [HGM] oneclick
6359
6360 int
6361 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6362 {
6363     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6364     /* [HGM] add Shogi promotions */
6365     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6366     ChessSquare piece;
6367     ChessMove moveType;
6368     Boolean premove;
6369
6370     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6371     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6372
6373     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6374       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6375         return FALSE;
6376
6377     piece = boards[currentMove][fromY][fromX];
6378     if(gameInfo.variant == VariantShogi) {
6379         promotionZoneSize = BOARD_HEIGHT/3;
6380         highestPromotingPiece = (int)WhiteFerz;
6381     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6382         promotionZoneSize = 3;
6383     }
6384
6385     // Treat Lance as Pawn when it is not representing Amazon
6386     if(gameInfo.variant != VariantSuper) {
6387         if(piece == WhiteLance) piece = WhitePawn; else
6388         if(piece == BlackLance) piece = BlackPawn;
6389     }
6390
6391     // next weed out all moves that do not touch the promotion zone at all
6392     if((int)piece >= BlackPawn) {
6393         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6394              return FALSE;
6395         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6396     } else {
6397         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6398            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6399     }
6400
6401     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6402
6403     // weed out mandatory Shogi promotions
6404     if(gameInfo.variant == VariantShogi) {
6405         if(piece >= BlackPawn) {
6406             if(toY == 0 && piece == BlackPawn ||
6407                toY == 0 && piece == BlackQueen ||
6408                toY <= 1 && piece == BlackKnight) {
6409                 *promoChoice = '+';
6410                 return FALSE;
6411             }
6412         } else {
6413             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6414                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6415                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6416                 *promoChoice = '+';
6417                 return FALSE;
6418             }
6419         }
6420     }
6421
6422     // weed out obviously illegal Pawn moves
6423     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6424         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6425         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6426         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6427         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6428         // note we are not allowed to test for valid (non-)capture, due to premove
6429     }
6430
6431     // we either have a choice what to promote to, or (in Shogi) whether to promote
6432     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6433        gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN) {
6434         *promoChoice = PieceToChar(BlackFerz);  // no choice
6435         return FALSE;
6436     }
6437     // no sense asking what we must promote to if it is going to explode...
6438     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6439         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6440         return FALSE;
6441     }
6442     // give caller the default choice even if we will not make it
6443     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6444     if(gameInfo.variant == VariantShogi) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6445     if(        sweepSelect && gameInfo.variant != VariantGreat
6446                            && gameInfo.variant != VariantGrand
6447                            && gameInfo.variant != VariantSuper) return FALSE;
6448     if(autoQueen) return FALSE; // predetermined
6449
6450     // suppress promotion popup on illegal moves that are not premoves
6451     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6452               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6453     if(appData.testLegality && !premove) {
6454         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6455                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6456         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6457             return FALSE;
6458     }
6459
6460     return TRUE;
6461 }
6462
6463 int
6464 InPalace (int row, int column)
6465 {   /* [HGM] for Xiangqi */
6466     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6467          column < (BOARD_WIDTH + 4)/2 &&
6468          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6469     return FALSE;
6470 }
6471
6472 int
6473 PieceForSquare (int x, int y)
6474 {
6475   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6476      return -1;
6477   else
6478      return boards[currentMove][y][x];
6479 }
6480
6481 int
6482 OKToStartUserMove (int x, int y)
6483 {
6484     ChessSquare from_piece;
6485     int white_piece;
6486
6487     if (matchMode) return FALSE;
6488     if (gameMode == EditPosition) return TRUE;
6489
6490     if (x >= 0 && y >= 0)
6491       from_piece = boards[currentMove][y][x];
6492     else
6493       from_piece = EmptySquare;
6494
6495     if (from_piece == EmptySquare) return FALSE;
6496
6497     white_piece = (int)from_piece >= (int)WhitePawn &&
6498       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6499
6500     switch (gameMode) {
6501       case AnalyzeFile:
6502       case TwoMachinesPlay:
6503       case EndOfGame:
6504         return FALSE;
6505
6506       case IcsObserving:
6507       case IcsIdle:
6508         return FALSE;
6509
6510       case MachinePlaysWhite:
6511       case IcsPlayingBlack:
6512         if (appData.zippyPlay) return FALSE;
6513         if (white_piece) {
6514             DisplayMoveError(_("You are playing Black"));
6515             return FALSE;
6516         }
6517         break;
6518
6519       case MachinePlaysBlack:
6520       case IcsPlayingWhite:
6521         if (appData.zippyPlay) return FALSE;
6522         if (!white_piece) {
6523             DisplayMoveError(_("You are playing White"));
6524             return FALSE;
6525         }
6526         break;
6527
6528       case PlayFromGameFile:
6529             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6530       case EditGame:
6531         if (!white_piece && WhiteOnMove(currentMove)) {
6532             DisplayMoveError(_("It is White's turn"));
6533             return FALSE;
6534         }
6535         if (white_piece && !WhiteOnMove(currentMove)) {
6536             DisplayMoveError(_("It is Black's turn"));
6537             return FALSE;
6538         }
6539         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6540             /* Editing correspondence game history */
6541             /* Could disallow this or prompt for confirmation */
6542             cmailOldMove = -1;
6543         }
6544         break;
6545
6546       case BeginningOfGame:
6547         if (appData.icsActive) return FALSE;
6548         if (!appData.noChessProgram) {
6549             if (!white_piece) {
6550                 DisplayMoveError(_("You are playing White"));
6551                 return FALSE;
6552             }
6553         }
6554         break;
6555
6556       case Training:
6557         if (!white_piece && WhiteOnMove(currentMove)) {
6558             DisplayMoveError(_("It is White's turn"));
6559             return FALSE;
6560         }
6561         if (white_piece && !WhiteOnMove(currentMove)) {
6562             DisplayMoveError(_("It is Black's turn"));
6563             return FALSE;
6564         }
6565         break;
6566
6567       default:
6568       case IcsExamining:
6569         break;
6570     }
6571     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6572         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6573         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6574         && gameMode != AnalyzeFile && gameMode != Training) {
6575         DisplayMoveError(_("Displayed position is not current"));
6576         return FALSE;
6577     }
6578     return TRUE;
6579 }
6580
6581 Boolean
6582 OnlyMove (int *x, int *y, Boolean captures)
6583 {
6584     DisambiguateClosure cl;
6585     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6586     switch(gameMode) {
6587       case MachinePlaysBlack:
6588       case IcsPlayingWhite:
6589       case BeginningOfGame:
6590         if(!WhiteOnMove(currentMove)) return FALSE;
6591         break;
6592       case MachinePlaysWhite:
6593       case IcsPlayingBlack:
6594         if(WhiteOnMove(currentMove)) return FALSE;
6595         break;
6596       case EditGame:
6597         break;
6598       default:
6599         return FALSE;
6600     }
6601     cl.pieceIn = EmptySquare;
6602     cl.rfIn = *y;
6603     cl.ffIn = *x;
6604     cl.rtIn = -1;
6605     cl.ftIn = -1;
6606     cl.promoCharIn = NULLCHAR;
6607     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6608     if( cl.kind == NormalMove ||
6609         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6610         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6611         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6612       fromX = cl.ff;
6613       fromY = cl.rf;
6614       *x = cl.ft;
6615       *y = cl.rt;
6616       return TRUE;
6617     }
6618     if(cl.kind != ImpossibleMove) return FALSE;
6619     cl.pieceIn = EmptySquare;
6620     cl.rfIn = -1;
6621     cl.ffIn = -1;
6622     cl.rtIn = *y;
6623     cl.ftIn = *x;
6624     cl.promoCharIn = NULLCHAR;
6625     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6626     if( cl.kind == NormalMove ||
6627         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6628         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6629         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6630       fromX = cl.ff;
6631       fromY = cl.rf;
6632       *x = cl.ft;
6633       *y = cl.rt;
6634       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6635       return TRUE;
6636     }
6637     return FALSE;
6638 }
6639
6640 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6641 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6642 int lastLoadGameUseList = FALSE;
6643 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6644 ChessMove lastLoadGameStart = EndOfFile;
6645 int doubleClick;
6646
6647 void
6648 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6649 {
6650     ChessMove moveType;
6651     ChessSquare pup;
6652     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6653
6654     /* Check if the user is playing in turn.  This is complicated because we
6655        let the user "pick up" a piece before it is his turn.  So the piece he
6656        tried to pick up may have been captured by the time he puts it down!
6657        Therefore we use the color the user is supposed to be playing in this
6658        test, not the color of the piece that is currently on the starting
6659        square---except in EditGame mode, where the user is playing both
6660        sides; fortunately there the capture race can't happen.  (It can
6661        now happen in IcsExamining mode, but that's just too bad.  The user
6662        will get a somewhat confusing message in that case.)
6663        */
6664
6665     switch (gameMode) {
6666       case AnalyzeFile:
6667       case TwoMachinesPlay:
6668       case EndOfGame:
6669       case IcsObserving:
6670       case IcsIdle:
6671         /* We switched into a game mode where moves are not accepted,
6672            perhaps while the mouse button was down. */
6673         return;
6674
6675       case MachinePlaysWhite:
6676         /* User is moving for Black */
6677         if (WhiteOnMove(currentMove)) {
6678             DisplayMoveError(_("It is White's turn"));
6679             return;
6680         }
6681         break;
6682
6683       case MachinePlaysBlack:
6684         /* User is moving for White */
6685         if (!WhiteOnMove(currentMove)) {
6686             DisplayMoveError(_("It is Black's turn"));
6687             return;
6688         }
6689         break;
6690
6691       case PlayFromGameFile:
6692             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6693       case EditGame:
6694       case IcsExamining:
6695       case BeginningOfGame:
6696       case AnalyzeMode:
6697       case Training:
6698         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6699         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6700             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6701             /* User is moving for Black */
6702             if (WhiteOnMove(currentMove)) {
6703                 DisplayMoveError(_("It is White's turn"));
6704                 return;
6705             }
6706         } else {
6707             /* User is moving for White */
6708             if (!WhiteOnMove(currentMove)) {
6709                 DisplayMoveError(_("It is Black's turn"));
6710                 return;
6711             }
6712         }
6713         break;
6714
6715       case IcsPlayingBlack:
6716         /* User is moving for Black */
6717         if (WhiteOnMove(currentMove)) {
6718             if (!appData.premove) {
6719                 DisplayMoveError(_("It is White's turn"));
6720             } else if (toX >= 0 && toY >= 0) {
6721                 premoveToX = toX;
6722                 premoveToY = toY;
6723                 premoveFromX = fromX;
6724                 premoveFromY = fromY;
6725                 premovePromoChar = promoChar;
6726                 gotPremove = 1;
6727                 if (appData.debugMode)
6728                     fprintf(debugFP, "Got premove: fromX %d,"
6729                             "fromY %d, toX %d, toY %d\n",
6730                             fromX, fromY, toX, toY);
6731             }
6732             return;
6733         }
6734         break;
6735
6736       case IcsPlayingWhite:
6737         /* User is moving for White */
6738         if (!WhiteOnMove(currentMove)) {
6739             if (!appData.premove) {
6740                 DisplayMoveError(_("It is Black's turn"));
6741             } else if (toX >= 0 && toY >= 0) {
6742                 premoveToX = toX;
6743                 premoveToY = toY;
6744                 premoveFromX = fromX;
6745                 premoveFromY = fromY;
6746                 premovePromoChar = promoChar;
6747                 gotPremove = 1;
6748                 if (appData.debugMode)
6749                     fprintf(debugFP, "Got premove: fromX %d,"
6750                             "fromY %d, toX %d, toY %d\n",
6751                             fromX, fromY, toX, toY);
6752             }
6753             return;
6754         }
6755         break;
6756
6757       default:
6758         break;
6759
6760       case EditPosition:
6761         /* EditPosition, empty square, or different color piece;
6762            click-click move is possible */
6763         if (toX == -2 || toY == -2) {
6764             boards[0][fromY][fromX] = EmptySquare;
6765             DrawPosition(FALSE, boards[currentMove]);
6766             return;
6767         } else if (toX >= 0 && toY >= 0) {
6768             boards[0][toY][toX] = boards[0][fromY][fromX];
6769             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6770                 if(boards[0][fromY][0] != EmptySquare) {
6771                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6772                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6773                 }
6774             } else
6775             if(fromX == BOARD_RGHT+1) {
6776                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6777                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6778                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6779                 }
6780             } else
6781             boards[0][fromY][fromX] = gatingPiece;
6782             DrawPosition(FALSE, boards[currentMove]);
6783             return;
6784         }
6785         return;
6786     }
6787
6788     if(toX < 0 || toY < 0) return;
6789     pup = boards[currentMove][toY][toX];
6790
6791     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6792     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6793          if( pup != EmptySquare ) return;
6794          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6795            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6796                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6797            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6798            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6799            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6800            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6801          fromY = DROP_RANK;
6802     }
6803
6804     /* [HGM] always test for legality, to get promotion info */
6805     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6806                                          fromY, fromX, toY, toX, promoChar);
6807
6808     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6809
6810     /* [HGM] but possibly ignore an IllegalMove result */
6811     if (appData.testLegality) {
6812         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6813             DisplayMoveError(_("Illegal move"));
6814             return;
6815         }
6816     }
6817
6818     if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
6819         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
6820              ClearPremoveHighlights(); // was included
6821         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
6822         return;
6823     }
6824
6825     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6826 }
6827
6828 /* Common tail of UserMoveEvent and DropMenuEvent */
6829 int
6830 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
6831 {
6832     char *bookHit = 0;
6833
6834     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6835         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6836         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6837         if(WhiteOnMove(currentMove)) {
6838             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6839         } else {
6840             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6841         }
6842     }
6843
6844     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6845        move type in caller when we know the move is a legal promotion */
6846     if(moveType == NormalMove && promoChar)
6847         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6848
6849     /* [HGM] <popupFix> The following if has been moved here from
6850        UserMoveEvent(). Because it seemed to belong here (why not allow
6851        piece drops in training games?), and because it can only be
6852        performed after it is known to what we promote. */
6853     if (gameMode == Training) {
6854       /* compare the move played on the board to the next move in the
6855        * game. If they match, display the move and the opponent's response.
6856        * If they don't match, display an error message.
6857        */
6858       int saveAnimate;
6859       Board testBoard;
6860       CopyBoard(testBoard, boards[currentMove]);
6861       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6862
6863       if (CompareBoards(testBoard, boards[currentMove+1])) {
6864         ForwardInner(currentMove+1);
6865
6866         /* Autoplay the opponent's response.
6867          * if appData.animate was TRUE when Training mode was entered,
6868          * the response will be animated.
6869          */
6870         saveAnimate = appData.animate;
6871         appData.animate = animateTraining;
6872         ForwardInner(currentMove+1);
6873         appData.animate = saveAnimate;
6874
6875         /* check for the end of the game */
6876         if (currentMove >= forwardMostMove) {
6877           gameMode = PlayFromGameFile;
6878           ModeHighlight();
6879           SetTrainingModeOff();
6880           DisplayInformation(_("End of game"));
6881         }
6882       } else {
6883         DisplayError(_("Incorrect move"), 0);
6884       }
6885       return 1;
6886     }
6887
6888   /* Ok, now we know that the move is good, so we can kill
6889      the previous line in Analysis Mode */
6890   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6891                                 && currentMove < forwardMostMove) {
6892     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6893     else forwardMostMove = currentMove;
6894   }
6895
6896   ClearMap();
6897
6898   /* If we need the chess program but it's dead, restart it */
6899   ResurrectChessProgram();
6900
6901   /* A user move restarts a paused game*/
6902   if (pausing)
6903     PauseEvent();
6904
6905   thinkOutput[0] = NULLCHAR;
6906
6907   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6908
6909   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6910     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6911     return 1;
6912   }
6913
6914   if (gameMode == BeginningOfGame) {
6915     if (appData.noChessProgram) {
6916       gameMode = EditGame;
6917       SetGameInfo();
6918     } else {
6919       char buf[MSG_SIZ];
6920       gameMode = MachinePlaysBlack;
6921       StartClocks();
6922       SetGameInfo();
6923       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
6924       DisplayTitle(buf);
6925       if (first.sendName) {
6926         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6927         SendToProgram(buf, &first);
6928       }
6929       StartClocks();
6930     }
6931     ModeHighlight();
6932   }
6933
6934   /* Relay move to ICS or chess engine */
6935   if (appData.icsActive) {
6936     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6937         gameMode == IcsExamining) {
6938       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6939         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6940         SendToICS("draw ");
6941         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6942       }
6943       // also send plain move, in case ICS does not understand atomic claims
6944       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6945       ics_user_moved = 1;
6946     }
6947   } else {
6948     if (first.sendTime && (gameMode == BeginningOfGame ||
6949                            gameMode == MachinePlaysWhite ||
6950                            gameMode == MachinePlaysBlack)) {
6951       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6952     }
6953     if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
6954          // [HGM] book: if program might be playing, let it use book
6955         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6956         first.maybeThinking = TRUE;
6957     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
6958         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
6959         SendBoard(&first, currentMove+1);
6960         if(second.analyzing) {
6961             if(!second.useSetboard) SendToProgram("undo\n", &second);
6962             SendBoard(&second, currentMove+1);
6963         }
6964     } else {
6965         SendMoveToProgram(forwardMostMove-1, &first);
6966         if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
6967     }
6968     if (currentMove == cmailOldMove + 1) {
6969       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6970     }
6971   }
6972
6973   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6974
6975   switch (gameMode) {
6976   case EditGame:
6977     if(appData.testLegality)
6978     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6979     case MT_NONE:
6980     case MT_CHECK:
6981       break;
6982     case MT_CHECKMATE:
6983     case MT_STAINMATE:
6984       if (WhiteOnMove(currentMove)) {
6985         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6986       } else {
6987         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6988       }
6989       break;
6990     case MT_STALEMATE:
6991       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6992       break;
6993     }
6994     break;
6995
6996   case MachinePlaysBlack:
6997   case MachinePlaysWhite:
6998     /* disable certain menu options while machine is thinking */
6999     SetMachineThinkingEnables();
7000     break;
7001
7002   default:
7003     break;
7004   }
7005
7006   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7007   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7008
7009   if(bookHit) { // [HGM] book: simulate book reply
7010         static char bookMove[MSG_SIZ]; // a bit generous?
7011
7012         programStats.nodes = programStats.depth = programStats.time =
7013         programStats.score = programStats.got_only_move = 0;
7014         sprintf(programStats.movelist, "%s (xbook)", bookHit);
7015
7016         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7017         strcat(bookMove, bookHit);
7018         HandleMachineMove(bookMove, &first);
7019   }
7020   return 1;
7021 }
7022
7023 void
7024 MarkByFEN(char *fen)
7025 {
7026         int r, f;
7027         if(!appData.markers || !appData.highlightDragging) return;
7028         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 0;
7029         r=BOARD_HEIGHT-1; f=BOARD_LEFT;
7030         while(*fen) {
7031             int s = 0;
7032             marker[r][f] = 0;
7033             if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7034             if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 1; else
7035             if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7036             if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7037             if(*fen == 'T') marker[r][f++] = 0; else
7038             if(*fen == 'Y') marker[r][f++] = 1; else
7039             if(*fen == 'G') marker[r][f++] = 3; else
7040             if(*fen == 'B') marker[r][f++] = 4; else
7041             if(*fen == 'C') marker[r][f++] = 5; else
7042             if(*fen == 'M') marker[r][f++] = 6; else
7043             if(*fen == 'W') marker[r][f++] = 7; else
7044             if(*fen == 'D') marker[r][f++] = 8; else
7045             if(*fen == 'R') marker[r][f++] = 2; else {
7046                 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7047               f += s; fen -= s>0;
7048             }
7049             while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7050             if(r < 0) break;
7051             fen++;
7052         }
7053         DrawPosition(TRUE, NULL);
7054 }
7055
7056 void
7057 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7058 {
7059     typedef char Markers[BOARD_RANKS][BOARD_FILES];
7060     Markers *m = (Markers *) closure;
7061     if(rf == fromY && ff == fromX)
7062         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7063                          || kind == WhiteCapturesEnPassant
7064                          || kind == BlackCapturesEnPassant);
7065     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
7066 }
7067
7068 void
7069 MarkTargetSquares (int clear)
7070 {
7071   int x, y, sum=0;
7072   if(clear) { // no reason to ever suppress clearing
7073     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = 0;
7074     if(!sum) return; // nothing was cleared,no redraw needed
7075   } else {
7076     int capt = 0;
7077     if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7078        !appData.testLegality || gameMode == EditPosition) return;
7079     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7080     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7081       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7082       if(capt)
7083       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7084     }
7085   }
7086   DrawPosition(FALSE, NULL);
7087 }
7088
7089 int
7090 Explode (Board board, int fromX, int fromY, int toX, int toY)
7091 {
7092     if(gameInfo.variant == VariantAtomic &&
7093        (board[toY][toX] != EmptySquare ||                     // capture?
7094         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
7095                          board[fromY][fromX] == BlackPawn   )
7096       )) {
7097         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7098         return TRUE;
7099     }
7100     return FALSE;
7101 }
7102
7103 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7104
7105 int
7106 CanPromote (ChessSquare piece, int y)
7107 {
7108         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7109         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7110         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
7111            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7112            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7113          gameInfo.variant == VariantMakruk   || gameInfo.variant == VariantASEAN) return FALSE;
7114         return (piece == BlackPawn && y == 1 ||
7115                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
7116                 piece == BlackLance && y == 1 ||
7117                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
7118 }
7119
7120 void
7121 HoverEvent (int hiX, int hiY, int x, int y)
7122 {
7123         static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7124         int r, f;
7125         if(!first.highlight) return;
7126         if(hiX == -1 && hiY == -1 && x == fromX && y == fromY) // record markings 
7127           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7128             baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7129         else if(hiX != x || hiY != y) {
7130           // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7131           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7132             marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7133           if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7134             char buf[MSG_SIZ];
7135             snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7136             SendToProgram(buf, &first);
7137           }
7138           SetHighlights(fromX, fromY, x, y);
7139         }
7140 }
7141
7142 void ReportClick(char *action, int x, int y)
7143 {
7144         char buf[MSG_SIZ]; // Inform engine of what user does
7145         int r, f;
7146         if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7147           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 1, marker[r][f] = 0;
7148         if(!first.highlight || gameMode == EditPosition) return;
7149         snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7150         SendToProgram(buf, &first);
7151 }
7152
7153 void
7154 LeftClick (ClickType clickType, int xPix, int yPix)
7155 {
7156     int x, y;
7157     Boolean saveAnimate;
7158     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7159     char promoChoice = NULLCHAR;
7160     ChessSquare piece;
7161     static TimeMark lastClickTime, prevClickTime;
7162
7163     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7164
7165     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7166
7167     if (clickType == Press) ErrorPopDown();
7168     lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7169
7170     x = EventToSquare(xPix, BOARD_WIDTH);
7171     y = EventToSquare(yPix, BOARD_HEIGHT);
7172     if (!flipView && y >= 0) {
7173         y = BOARD_HEIGHT - 1 - y;
7174     }
7175     if (flipView && x >= 0) {
7176         x = BOARD_WIDTH - 1 - x;
7177     }
7178
7179     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7180         defaultPromoChoice = promoSweep;
7181         promoSweep = EmptySquare;   // terminate sweep
7182         promoDefaultAltered = TRUE;
7183         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7184     }
7185
7186     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7187         if(clickType == Release) return; // ignore upclick of click-click destination
7188         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7189         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7190         if(gameInfo.holdingsWidth &&
7191                 (WhiteOnMove(currentMove)
7192                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7193                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7194             // click in right holdings, for determining promotion piece
7195             ChessSquare p = boards[currentMove][y][x];
7196             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7197             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7198             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7199                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7200                 fromX = fromY = -1;
7201                 return;
7202             }
7203         }
7204         DrawPosition(FALSE, boards[currentMove]);
7205         return;
7206     }
7207
7208     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7209     if(clickType == Press
7210             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7211               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7212               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7213         return;
7214
7215     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7216         // could be static click on premove from-square: abort premove
7217         gotPremove = 0;
7218         ClearPremoveHighlights();
7219     }
7220
7221     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7222         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7223
7224     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7225         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7226                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7227         defaultPromoChoice = DefaultPromoChoice(side);
7228     }
7229
7230     autoQueen = appData.alwaysPromoteToQueen;
7231
7232     if (fromX == -1) {
7233       int originalY = y;
7234       gatingPiece = EmptySquare;
7235       if (clickType != Press) {
7236         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7237             DragPieceEnd(xPix, yPix); dragging = 0;
7238             DrawPosition(FALSE, NULL);
7239         }
7240         return;
7241       }
7242       doubleClick = FALSE;
7243       if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7244         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7245       }
7246       fromX = x; fromY = y; toX = toY = -1;
7247       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7248          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7249          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7250             /* First square */
7251             if (OKToStartUserMove(fromX, fromY)) {
7252                 second = 0;
7253                 ReportClick("lift", x, y);
7254                 MarkTargetSquares(0);
7255                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7256                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7257                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7258                     promoSweep = defaultPromoChoice;
7259                     selectFlag = 0; lastX = xPix; lastY = yPix;
7260                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7261                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7262                 }
7263                 if (appData.highlightDragging) {
7264                     SetHighlights(fromX, fromY, -1, -1);
7265                 } else {
7266                     ClearHighlights();
7267                 }
7268             } else fromX = fromY = -1;
7269             return;
7270         }
7271     }
7272
7273     /* fromX != -1 */
7274     if (clickType == Press && gameMode != EditPosition) {
7275         ChessSquare fromP;
7276         ChessSquare toP;
7277         int frc;
7278
7279         // ignore off-board to clicks
7280         if(y < 0 || x < 0) return;
7281
7282         /* Check if clicking again on the same color piece */
7283         fromP = boards[currentMove][fromY][fromX];
7284         toP = boards[currentMove][y][x];
7285         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
7286         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
7287              WhitePawn <= toP && toP <= WhiteKing &&
7288              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7289              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7290             (BlackPawn <= fromP && fromP <= BlackKing &&
7291              BlackPawn <= toP && toP <= BlackKing &&
7292              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7293              !(fromP == BlackKing && toP == BlackRook && frc))) {
7294             /* Clicked again on same color piece -- changed his mind */
7295             second = (x == fromX && y == fromY);
7296             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7297                 second = FALSE; // first double-click rather than scond click
7298                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7299             }
7300             promoDefaultAltered = FALSE;
7301             MarkTargetSquares(1);
7302            if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7303             if (appData.highlightDragging) {
7304                 SetHighlights(x, y, -1, -1);
7305             } else {
7306                 ClearHighlights();
7307             }
7308             if (OKToStartUserMove(x, y)) {
7309                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7310                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7311                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7312                  gatingPiece = boards[currentMove][fromY][fromX];
7313                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7314                 fromX = x;
7315                 fromY = y; dragging = 1;
7316                 ReportClick("lift", x, y);
7317                 MarkTargetSquares(0);
7318                 DragPieceBegin(xPix, yPix, FALSE);
7319                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7320                     promoSweep = defaultPromoChoice;
7321                     selectFlag = 0; lastX = xPix; lastY = yPix;
7322                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7323                 }
7324             }
7325            }
7326            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7327            second = FALSE;
7328         }
7329         // ignore clicks on holdings
7330         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7331     }
7332
7333     if (clickType == Release && x == fromX && y == fromY) {
7334         DragPieceEnd(xPix, yPix); dragging = 0;
7335         if(clearFlag) {
7336             // a deferred attempt to click-click move an empty square on top of a piece
7337             boards[currentMove][y][x] = EmptySquare;
7338             ClearHighlights();
7339             DrawPosition(FALSE, boards[currentMove]);
7340             fromX = fromY = -1; clearFlag = 0;
7341             return;
7342         }
7343         if (appData.animateDragging) {
7344             /* Undo animation damage if any */
7345             DrawPosition(FALSE, NULL);
7346         }
7347         if (second || sweepSelecting) {
7348             /* Second up/down in same square; just abort move */
7349             if(sweepSelecting) DrawPosition(FALSE, boards[currentMove]);
7350             second = sweepSelecting = 0;
7351             fromX = fromY = -1;
7352             gatingPiece = EmptySquare;
7353             MarkTargetSquares(1);
7354             ClearHighlights();
7355             gotPremove = 0;
7356             ClearPremoveHighlights();
7357         } else {
7358             /* First upclick in same square; start click-click mode */
7359             SetHighlights(x, y, -1, -1);
7360         }
7361         return;
7362     }
7363
7364     clearFlag = 0;
7365
7366     if(gameMode != EditPosition && !appData.testLegality && !legal[y][x]) {
7367         if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7368         DisplayMessage(_("only marked squares are legal"),"");
7369         DrawPosition(TRUE, NULL);
7370         return; // ignore to-click
7371     }
7372
7373     /* we now have a different from- and (possibly off-board) to-square */
7374     /* Completed move */
7375     if(!sweepSelecting) {
7376         toX = x;
7377         toY = y;
7378     } else sweepSelecting = 0; // this must be the up-click corresponding to the down-click that started the sweep
7379
7380     saveAnimate = appData.animate;
7381     if (clickType == Press) {
7382         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7383             // must be Edit Position mode with empty-square selected
7384             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7385             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7386             return;
7387         }
7388         if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7389           if(appData.sweepSelect) {
7390             ChessSquare piece = boards[currentMove][fromY][fromX];
7391             promoSweep = defaultPromoChoice;
7392             if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
7393             selectFlag = 0; lastX = xPix; lastY = yPix;
7394             Sweep(0); // Pawn that is going to promote: preview promotion piece
7395             sweepSelecting = 1;
7396             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7397             MarkTargetSquares(1);
7398           }
7399           return; // promo popup appears on up-click
7400         }
7401         /* Finish clickclick move */
7402         if (appData.animate || appData.highlightLastMove) {
7403             SetHighlights(fromX, fromY, toX, toY);
7404         } else {
7405             ClearHighlights();
7406         }
7407     } else {
7408 #if 0
7409 // [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
7410         /* Finish drag move */
7411         if (appData.highlightLastMove) {
7412             SetHighlights(fromX, fromY, toX, toY);
7413         } else {
7414             ClearHighlights();
7415         }
7416 #endif
7417         DragPieceEnd(xPix, yPix); dragging = 0;
7418         /* Don't animate move and drag both */
7419         appData.animate = FALSE;
7420     }
7421
7422     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7423     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7424         ChessSquare piece = boards[currentMove][fromY][fromX];
7425         if(gameMode == EditPosition && piece != EmptySquare &&
7426            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7427             int n;
7428
7429             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7430                 n = PieceToNumber(piece - (int)BlackPawn);
7431                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7432                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7433                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7434             } else
7435             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7436                 n = PieceToNumber(piece);
7437                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7438                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7439                 boards[currentMove][n][BOARD_WIDTH-2]++;
7440             }
7441             boards[currentMove][fromY][fromX] = EmptySquare;
7442         }
7443         ClearHighlights();
7444         fromX = fromY = -1;
7445         MarkTargetSquares(1);
7446         DrawPosition(TRUE, boards[currentMove]);
7447         return;
7448     }
7449
7450     // off-board moves should not be highlighted
7451     if(x < 0 || y < 0) ClearHighlights();
7452     else ReportClick("put", x, y);
7453
7454     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7455
7456     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7457         SetHighlights(fromX, fromY, toX, toY);
7458         MarkTargetSquares(1);
7459         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7460             // [HGM] super: promotion to captured piece selected from holdings
7461             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7462             promotionChoice = TRUE;
7463             // kludge follows to temporarily execute move on display, without promoting yet
7464             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7465             boards[currentMove][toY][toX] = p;
7466             DrawPosition(FALSE, boards[currentMove]);
7467             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7468             boards[currentMove][toY][toX] = q;
7469             DisplayMessage("Click in holdings to choose piece", "");
7470             return;
7471         }
7472         PromotionPopUp();
7473     } else {
7474         int oldMove = currentMove;
7475         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7476         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7477         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7478         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7479            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7480             DrawPosition(TRUE, boards[currentMove]);
7481         MarkTargetSquares(1);
7482         fromX = fromY = -1;
7483     }
7484     appData.animate = saveAnimate;
7485     if (appData.animate || appData.animateDragging) {
7486         /* Undo animation damage if needed */
7487         DrawPosition(FALSE, NULL);
7488     }
7489 }
7490
7491 int
7492 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7493 {   // front-end-free part taken out of PieceMenuPopup
7494     int whichMenu; int xSqr, ySqr;
7495
7496     if(seekGraphUp) { // [HGM] seekgraph
7497         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7498         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7499         return -2;
7500     }
7501
7502     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7503          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7504         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7505         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7506         if(action == Press)   {
7507             originalFlip = flipView;
7508             flipView = !flipView; // temporarily flip board to see game from partners perspective
7509             DrawPosition(TRUE, partnerBoard);
7510             DisplayMessage(partnerStatus, "");
7511             partnerUp = TRUE;
7512         } else if(action == Release) {
7513             flipView = originalFlip;
7514             DrawPosition(TRUE, boards[currentMove]);
7515             partnerUp = FALSE;
7516         }
7517         return -2;
7518     }
7519
7520     xSqr = EventToSquare(x, BOARD_WIDTH);
7521     ySqr = EventToSquare(y, BOARD_HEIGHT);
7522     if (action == Release) {
7523         if(pieceSweep != EmptySquare) {
7524             EditPositionMenuEvent(pieceSweep, toX, toY);
7525             pieceSweep = EmptySquare;
7526         } else UnLoadPV(); // [HGM] pv
7527     }
7528     if (action != Press) return -2; // return code to be ignored
7529     switch (gameMode) {
7530       case IcsExamining:
7531         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7532       case EditPosition:
7533         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7534         if (xSqr < 0 || ySqr < 0) return -1;
7535         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7536         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7537         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7538         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7539         NextPiece(0);
7540         return 2; // grab
7541       case IcsObserving:
7542         if(!appData.icsEngineAnalyze) return -1;
7543       case IcsPlayingWhite:
7544       case IcsPlayingBlack:
7545         if(!appData.zippyPlay) goto noZip;
7546       case AnalyzeMode:
7547       case AnalyzeFile:
7548       case MachinePlaysWhite:
7549       case MachinePlaysBlack:
7550       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7551         if (!appData.dropMenu) {
7552           LoadPV(x, y);
7553           return 2; // flag front-end to grab mouse events
7554         }
7555         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7556            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7557       case EditGame:
7558       noZip:
7559         if (xSqr < 0 || ySqr < 0) return -1;
7560         if (!appData.dropMenu || appData.testLegality &&
7561             gameInfo.variant != VariantBughouse &&
7562             gameInfo.variant != VariantCrazyhouse) return -1;
7563         whichMenu = 1; // drop menu
7564         break;
7565       default:
7566         return -1;
7567     }
7568
7569     if (((*fromX = xSqr) < 0) ||
7570         ((*fromY = ySqr) < 0)) {
7571         *fromX = *fromY = -1;
7572         return -1;
7573     }
7574     if (flipView)
7575       *fromX = BOARD_WIDTH - 1 - *fromX;
7576     else
7577       *fromY = BOARD_HEIGHT - 1 - *fromY;
7578
7579     return whichMenu;
7580 }
7581
7582 void
7583 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7584 {
7585 //    char * hint = lastHint;
7586     FrontEndProgramStats stats;
7587
7588     stats.which = cps == &first ? 0 : 1;
7589     stats.depth = cpstats->depth;
7590     stats.nodes = cpstats->nodes;
7591     stats.score = cpstats->score;
7592     stats.time = cpstats->time;
7593     stats.pv = cpstats->movelist;
7594     stats.hint = lastHint;
7595     stats.an_move_index = 0;
7596     stats.an_move_count = 0;
7597
7598     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7599         stats.hint = cpstats->move_name;
7600         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7601         stats.an_move_count = cpstats->nr_moves;
7602     }
7603
7604     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
7605
7606     SetProgramStats( &stats );
7607 }
7608
7609 void
7610 ClearEngineOutputPane (int which)
7611 {
7612     static FrontEndProgramStats dummyStats;
7613     dummyStats.which = which;
7614     dummyStats.pv = "#";
7615     SetProgramStats( &dummyStats );
7616 }
7617
7618 #define MAXPLAYERS 500
7619
7620 char *
7621 TourneyStandings (int display)
7622 {
7623     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7624     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7625     char result, *p, *names[MAXPLAYERS];
7626
7627     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7628         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7629     names[0] = p = strdup(appData.participants);
7630     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7631
7632     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7633
7634     while(result = appData.results[nr]) {
7635         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7636         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7637         wScore = bScore = 0;
7638         switch(result) {
7639           case '+': wScore = 2; break;
7640           case '-': bScore = 2; break;
7641           case '=': wScore = bScore = 1; break;
7642           case ' ':
7643           case '*': return strdup("busy"); // tourney not finished
7644         }
7645         score[w] += wScore;
7646         score[b] += bScore;
7647         games[w]++;
7648         games[b]++;
7649         nr++;
7650     }
7651     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7652     for(w=0; w<nPlayers; w++) {
7653         bScore = -1;
7654         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7655         ranking[w] = b; points[w] = bScore; score[b] = -2;
7656     }
7657     p = malloc(nPlayers*34+1);
7658     for(w=0; w<nPlayers && w<display; w++)
7659         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7660     free(names[0]);
7661     return p;
7662 }
7663
7664 void
7665 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7666 {       // count all piece types
7667         int p, f, r;
7668         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7669         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7670         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7671                 p = board[r][f];
7672                 pCnt[p]++;
7673                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7674                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7675                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7676                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7677                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7678                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7679         }
7680 }
7681
7682 int
7683 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7684 {
7685         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7686         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7687
7688         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7689         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7690         if(myPawns == 2 && nMine == 3) // KPP
7691             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7692         if(myPawns == 1 && nMine == 2) // KP
7693             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7694         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7695             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7696         if(myPawns) return FALSE;
7697         if(pCnt[WhiteRook+side])
7698             return pCnt[BlackRook-side] ||
7699                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7700                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7701                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7702         if(pCnt[WhiteCannon+side]) {
7703             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7704             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7705         }
7706         if(pCnt[WhiteKnight+side])
7707             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7708         return FALSE;
7709 }
7710
7711 int
7712 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7713 {
7714         VariantClass v = gameInfo.variant;
7715
7716         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7717         if(v == VariantShatranj) return TRUE; // always winnable through baring
7718         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7719         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7720
7721         if(v == VariantXiangqi) {
7722                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7723
7724                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7725                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7726                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7727                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7728                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7729                 if(stale) // we have at least one last-rank P plus perhaps C
7730                     return majors // KPKX
7731                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7732                 else // KCA*E*
7733                     return pCnt[WhiteFerz+side] // KCAK
7734                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7735                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7736                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7737
7738         } else if(v == VariantKnightmate) {
7739                 if(nMine == 1) return FALSE;
7740                 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
7741         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7742                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7743
7744                 if(nMine == 1) return FALSE; // bare King
7745                 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
7746                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7747                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7748                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7749                 if(pCnt[WhiteKnight+side])
7750                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7751                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7752                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7753                 if(nBishops)
7754                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7755                 if(pCnt[WhiteAlfil+side])
7756                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7757                 if(pCnt[WhiteWazir+side])
7758                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7759         }
7760
7761         return TRUE;
7762 }
7763
7764 int
7765 CompareWithRights (Board b1, Board b2)
7766 {
7767     int rights = 0;
7768     if(!CompareBoards(b1, b2)) return FALSE;
7769     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7770     /* compare castling rights */
7771     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7772            rights++; /* King lost rights, while rook still had them */
7773     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7774         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7775            rights++; /* but at least one rook lost them */
7776     }
7777     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7778            rights++;
7779     if( b1[CASTLING][5] != NoRights ) {
7780         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7781            rights++;
7782     }
7783     return rights == 0;
7784 }
7785
7786 int
7787 Adjudicate (ChessProgramState *cps)
7788 {       // [HGM] some adjudications useful with buggy engines
7789         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7790         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7791         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7792         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7793         int k, drop, count = 0; static int bare = 1;
7794         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7795         Boolean canAdjudicate = !appData.icsActive;
7796
7797         // most tests only when we understand the game, i.e. legality-checking on
7798             if( appData.testLegality )
7799             {   /* [HGM] Some more adjudications for obstinate engines */
7800                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7801                 static int moveCount = 6;
7802                 ChessMove result;
7803                 char *reason = NULL;
7804
7805                 /* Count what is on board. */
7806                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7807
7808                 /* Some material-based adjudications that have to be made before stalemate test */
7809                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7810                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7811                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7812                      if(canAdjudicate && appData.checkMates) {
7813                          if(engineOpponent)
7814                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7815                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7816                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7817                          return 1;
7818                      }
7819                 }
7820
7821                 /* Bare King in Shatranj (loses) or Losers (wins) */
7822                 if( nrW == 1 || nrB == 1) {
7823                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7824                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7825                      if(canAdjudicate && appData.checkMates) {
7826                          if(engineOpponent)
7827                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7828                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7829                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7830                          return 1;
7831                      }
7832                   } else
7833                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7834                   {    /* bare King */
7835                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7836                         if(canAdjudicate && appData.checkMates) {
7837                             /* but only adjudicate if adjudication enabled */
7838                             if(engineOpponent)
7839                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7840                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7841                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7842                             return 1;
7843                         }
7844                   }
7845                 } else bare = 1;
7846
7847
7848             // don't wait for engine to announce game end if we can judge ourselves
7849             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7850               case MT_CHECK:
7851                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7852                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7853                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7854                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7855                             checkCnt++;
7856                         if(checkCnt >= 2) {
7857                             reason = "Xboard adjudication: 3rd check";
7858                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7859                             break;
7860                         }
7861                     }
7862                 }
7863               case MT_NONE:
7864               default:
7865                 break;
7866               case MT_STALEMATE:
7867               case MT_STAINMATE:
7868                 reason = "Xboard adjudication: Stalemate";
7869                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7870                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7871                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7872                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7873                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7874                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7875                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7876                                                                         EP_CHECKMATE : EP_WINS);
7877                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
7878                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7879                 }
7880                 break;
7881               case MT_CHECKMATE:
7882                 reason = "Xboard adjudication: Checkmate";
7883                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7884                 if(gameInfo.variant == VariantShogi) {
7885                     if(forwardMostMove > backwardMostMove
7886                        && moveList[forwardMostMove-1][1] == '@'
7887                        && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
7888                         reason = "XBoard adjudication: pawn-drop mate";
7889                         boards[forwardMostMove][EP_STATUS] = EP_WINS;
7890                     }
7891                 }
7892                 break;
7893             }
7894
7895                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7896                     case EP_STALEMATE:
7897                         result = GameIsDrawn; break;
7898                     case EP_CHECKMATE:
7899                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7900                     case EP_WINS:
7901                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7902                     default:
7903                         result = EndOfFile;
7904                 }
7905                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7906                     if(engineOpponent)
7907                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7908                     GameEnds( result, reason, GE_XBOARD );
7909                     return 1;
7910                 }
7911
7912                 /* Next absolutely insufficient mating material. */
7913                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7914                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7915                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7916
7917                      /* always flag draws, for judging claims */
7918                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7919
7920                      if(canAdjudicate && appData.materialDraws) {
7921                          /* but only adjudicate them if adjudication enabled */
7922                          if(engineOpponent) {
7923                            SendToProgram("force\n", engineOpponent); // suppress reply
7924                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7925                          }
7926                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7927                          return 1;
7928                      }
7929                 }
7930
7931                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7932                 if(gameInfo.variant == VariantXiangqi ?
7933                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7934                  : nrW + nrB == 4 &&
7935                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7936                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7937                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7938                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7939                    ) ) {
7940                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7941                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7942                           if(engineOpponent) {
7943                             SendToProgram("force\n", engineOpponent); // suppress reply
7944                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7945                           }
7946                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7947                           return 1;
7948                      }
7949                 } else moveCount = 6;
7950             }
7951
7952         // Repetition draws and 50-move rule can be applied independently of legality testing
7953
7954                 /* Check for rep-draws */
7955                 count = 0;
7956                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
7957                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
7958                 for(k = forwardMostMove-2;
7959                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
7960                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7961                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
7962                     k-=2)
7963                 {   int rights=0;
7964                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7965                         /* compare castling rights */
7966                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7967                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7968                                 rights++; /* King lost rights, while rook still had them */
7969                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7970                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7971                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7972                                    rights++; /* but at least one rook lost them */
7973                         }
7974                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7975                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7976                                 rights++;
7977                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7978                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7979                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7980                                    rights++;
7981                         }
7982                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7983                             && appData.drawRepeats > 1) {
7984                              /* adjudicate after user-specified nr of repeats */
7985                              int result = GameIsDrawn;
7986                              char *details = "XBoard adjudication: repetition draw";
7987                              if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
7988                                 // [HGM] xiangqi: check for forbidden perpetuals
7989                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7990                                 for(m=forwardMostMove; m>k; m-=2) {
7991                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7992                                         ourPerpetual = 0; // the current mover did not always check
7993                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7994                                         hisPerpetual = 0; // the opponent did not always check
7995                                 }
7996                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7997                                                                         ourPerpetual, hisPerpetual);
7998                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7999                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8000                                     details = "Xboard adjudication: perpetual checking";
8001                                 } else
8002                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8003                                     break; // (or we would have caught him before). Abort repetition-checking loop.
8004                                 } else
8005                                 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8006                                     if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8007                                         result = BlackWins;
8008                                         details = "Xboard adjudication: repetition";
8009                                     }
8010                                 } else // it must be XQ
8011                                 // Now check for perpetual chases
8012                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8013                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
8014                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8015                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8016                                         static char resdet[MSG_SIZ];
8017                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8018                                         details = resdet;
8019                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8020                                     } else
8021                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
8022                                         break; // Abort repetition-checking loop.
8023                                 }
8024                                 // if neither of us is checking or chasing all the time, or both are, it is draw
8025                              }
8026                              if(engineOpponent) {
8027                                SendToProgram("force\n", engineOpponent); // suppress reply
8028                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8029                              }
8030                              GameEnds( result, details, GE_XBOARD );
8031                              return 1;
8032                         }
8033                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8034                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8035                     }
8036                 }
8037
8038                 /* Now we test for 50-move draws. Determine ply count */
8039                 count = forwardMostMove;
8040                 /* look for last irreversble move */
8041                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8042                     count--;
8043                 /* if we hit starting position, add initial plies */
8044                 if( count == backwardMostMove )
8045                     count -= initialRulePlies;
8046                 count = forwardMostMove - count;
8047                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8048                         // adjust reversible move counter for checks in Xiangqi
8049                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
8050                         if(i < backwardMostMove) i = backwardMostMove;
8051                         while(i <= forwardMostMove) {
8052                                 lastCheck = inCheck; // check evasion does not count
8053                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8054                                 if(inCheck || lastCheck) count--; // check does not count
8055                                 i++;
8056                         }
8057                 }
8058                 if( count >= 100)
8059                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8060                          /* this is used to judge if draw claims are legal */
8061                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8062                          if(engineOpponent) {
8063                            SendToProgram("force\n", engineOpponent); // suppress reply
8064                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8065                          }
8066                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8067                          return 1;
8068                 }
8069
8070                 /* if draw offer is pending, treat it as a draw claim
8071                  * when draw condition present, to allow engines a way to
8072                  * claim draws before making their move to avoid a race
8073                  * condition occurring after their move
8074                  */
8075                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8076                          char *p = NULL;
8077                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8078                              p = "Draw claim: 50-move rule";
8079                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8080                              p = "Draw claim: 3-fold repetition";
8081                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8082                              p = "Draw claim: insufficient mating material";
8083                          if( p != NULL && canAdjudicate) {
8084                              if(engineOpponent) {
8085                                SendToProgram("force\n", engineOpponent); // suppress reply
8086                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8087                              }
8088                              GameEnds( GameIsDrawn, p, GE_XBOARD );
8089                              return 1;
8090                          }
8091                 }
8092
8093                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8094                     if(engineOpponent) {
8095                       SendToProgram("force\n", engineOpponent); // suppress reply
8096                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8097                     }
8098                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8099                     return 1;
8100                 }
8101         return 0;
8102 }
8103
8104 char *
8105 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8106 {   // [HGM] book: this routine intercepts moves to simulate book replies
8107     char *bookHit = NULL;
8108
8109     //first determine if the incoming move brings opponent into his book
8110     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8111         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8112     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8113     if(bookHit != NULL && !cps->bookSuspend) {
8114         // make sure opponent is not going to reply after receiving move to book position
8115         SendToProgram("force\n", cps);
8116         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8117     }
8118     if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8119     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8120     // now arrange restart after book miss
8121     if(bookHit) {
8122         // after a book hit we never send 'go', and the code after the call to this routine
8123         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8124         char buf[MSG_SIZ], *move = bookHit;
8125         if(cps->useSAN) {
8126             int fromX, fromY, toX, toY;
8127             char promoChar;
8128             ChessMove moveType;
8129             move = buf + 30;
8130             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8131                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8132                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8133                                     PosFlags(forwardMostMove),
8134                                     fromY, fromX, toY, toX, promoChar, move);
8135             } else {
8136                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8137                 bookHit = NULL;
8138             }
8139         }
8140         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8141         SendToProgram(buf, cps);
8142         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8143     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8144         SendToProgram("go\n", cps);
8145         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8146     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8147         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8148             SendToProgram("go\n", cps);
8149         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8150     }
8151     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8152 }
8153
8154 int
8155 LoadError (char *errmess, ChessProgramState *cps)
8156 {   // unloads engine and switches back to -ncp mode if it was first
8157     if(cps->initDone) return FALSE;
8158     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8159     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8160     cps->pr = NoProc;
8161     if(cps == &first) {
8162         appData.noChessProgram = TRUE;
8163         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8164         gameMode = BeginningOfGame; ModeHighlight();
8165         SetNCPMode();
8166     }
8167     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8168     DisplayMessage("", ""); // erase waiting message
8169     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8170     return TRUE;
8171 }
8172
8173 char *savedMessage;
8174 ChessProgramState *savedState;
8175 void
8176 DeferredBookMove (void)
8177 {
8178         if(savedState->lastPing != savedState->lastPong)
8179                     ScheduleDelayedEvent(DeferredBookMove, 10);
8180         else
8181         HandleMachineMove(savedMessage, savedState);
8182 }
8183
8184 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8185 static ChessProgramState *stalledEngine;
8186 static char stashedInputMove[MSG_SIZ];
8187
8188 void
8189 HandleMachineMove (char *message, ChessProgramState *cps)
8190 {
8191     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8192     char realname[MSG_SIZ];
8193     int fromX, fromY, toX, toY;
8194     ChessMove moveType;
8195     char promoChar;
8196     char *p, *pv=buf1;
8197     int machineWhite, oldError;
8198     char *bookHit;
8199
8200     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8201         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8202         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8203             DisplayError(_("Invalid pairing from pairing engine"), 0);
8204             return;
8205         }
8206         pairingReceived = 1;
8207         NextMatchGame();
8208         return; // Skim the pairing messages here.
8209     }
8210
8211     oldError = cps->userError; cps->userError = 0;
8212
8213 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8214     /*
8215      * Kludge to ignore BEL characters
8216      */
8217     while (*message == '\007') message++;
8218
8219     /*
8220      * [HGM] engine debug message: ignore lines starting with '#' character
8221      */
8222     if(cps->debug && *message == '#') return;
8223
8224     /*
8225      * Look for book output
8226      */
8227     if (cps == &first && bookRequested) {
8228         if (message[0] == '\t' || message[0] == ' ') {
8229             /* Part of the book output is here; append it */
8230             strcat(bookOutput, message);
8231             strcat(bookOutput, "  \n");
8232             return;
8233         } else if (bookOutput[0] != NULLCHAR) {
8234             /* All of book output has arrived; display it */
8235             char *p = bookOutput;
8236             while (*p != NULLCHAR) {
8237                 if (*p == '\t') *p = ' ';
8238                 p++;
8239             }
8240             DisplayInformation(bookOutput);
8241             bookRequested = FALSE;
8242             /* Fall through to parse the current output */
8243         }
8244     }
8245
8246     /*
8247      * Look for machine move.
8248      */
8249     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8250         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8251     {
8252         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8253             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8254             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8255             stalledEngine = cps;
8256             if(appData.ponderNextMove) { // bring opponent out of ponder
8257                 if(gameMode == TwoMachinesPlay) {
8258                     if(cps->other->pause)
8259                         PauseEngine(cps->other);
8260                     else
8261                         SendToProgram("easy\n", cps->other);
8262                 }
8263             }
8264             StopClocks();
8265             return;
8266         }
8267
8268         /* This method is only useful on engines that support ping */
8269         if (cps->lastPing != cps->lastPong) {
8270           if (gameMode == BeginningOfGame) {
8271             /* Extra move from before last new; ignore */
8272             if (appData.debugMode) {
8273                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8274             }
8275           } else {
8276             if (appData.debugMode) {
8277                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8278                         cps->which, gameMode);
8279             }
8280
8281             SendToProgram("undo\n", cps);
8282           }
8283           return;
8284         }
8285
8286         switch (gameMode) {
8287           case BeginningOfGame:
8288             /* Extra move from before last reset; ignore */
8289             if (appData.debugMode) {
8290                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8291             }
8292             return;
8293
8294           case EndOfGame:
8295           case IcsIdle:
8296           default:
8297             /* Extra move after we tried to stop.  The mode test is
8298                not a reliable way of detecting this problem, but it's
8299                the best we can do on engines that don't support ping.
8300             */
8301             if (appData.debugMode) {
8302                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8303                         cps->which, gameMode);
8304             }
8305             SendToProgram("undo\n", cps);
8306             return;
8307
8308           case MachinePlaysWhite:
8309           case IcsPlayingWhite:
8310             machineWhite = TRUE;
8311             break;
8312
8313           case MachinePlaysBlack:
8314           case IcsPlayingBlack:
8315             machineWhite = FALSE;
8316             break;
8317
8318           case TwoMachinesPlay:
8319             machineWhite = (cps->twoMachinesColor[0] == 'w');
8320             break;
8321         }
8322         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8323             if (appData.debugMode) {
8324                 fprintf(debugFP,
8325                         "Ignoring move out of turn by %s, gameMode %d"
8326                         ", forwardMost %d\n",
8327                         cps->which, gameMode, forwardMostMove);
8328             }
8329             return;
8330         }
8331
8332         if(cps->alphaRank) AlphaRank(machineMove, 4);
8333         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8334                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8335             /* Machine move could not be parsed; ignore it. */
8336           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8337                     machineMove, _(cps->which));
8338             DisplayMoveError(buf1);
8339             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
8340                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
8341             if (gameMode == TwoMachinesPlay) {
8342               GameEnds(machineWhite ? BlackWins : WhiteWins,
8343                        buf1, GE_XBOARD);
8344             }
8345             return;
8346         }
8347
8348         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8349         /* So we have to redo legality test with true e.p. status here,  */
8350         /* to make sure an illegal e.p. capture does not slip through,   */
8351         /* to cause a forfeit on a justified illegal-move complaint      */
8352         /* of the opponent.                                              */
8353         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8354            ChessMove moveType;
8355            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8356                              fromY, fromX, toY, toX, promoChar);
8357             if(moveType == IllegalMove) {
8358               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8359                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8360                 GameEnds(machineWhite ? BlackWins : WhiteWins,
8361                            buf1, GE_XBOARD);
8362                 return;
8363            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
8364            /* [HGM] Kludge to handle engines that send FRC-style castling
8365               when they shouldn't (like TSCP-Gothic) */
8366            switch(moveType) {
8367              case WhiteASideCastleFR:
8368              case BlackASideCastleFR:
8369                toX+=2;
8370                currentMoveString[2]++;
8371                break;
8372              case WhiteHSideCastleFR:
8373              case BlackHSideCastleFR:
8374                toX--;
8375                currentMoveString[2]--;
8376                break;
8377              default: ; // nothing to do, but suppresses warning of pedantic compilers
8378            }
8379         }
8380         hintRequested = FALSE;
8381         lastHint[0] = NULLCHAR;
8382         bookRequested = FALSE;
8383         /* Program may be pondering now */
8384         cps->maybeThinking = TRUE;
8385         if (cps->sendTime == 2) cps->sendTime = 1;
8386         if (cps->offeredDraw) cps->offeredDraw--;
8387
8388         /* [AS] Save move info*/
8389         pvInfoList[ forwardMostMove ].score = programStats.score;
8390         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8391         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8392
8393         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8394
8395         /* Test suites abort the 'game' after one move */
8396         if(*appData.finger) {
8397            static FILE *f;
8398            char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8399            if(!f) f = fopen(appData.finger, "w");
8400            if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8401            else { DisplayFatalError("Bad output file", errno, 0); return; }
8402            free(fen);
8403            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8404         }
8405
8406         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8407         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8408             int count = 0;
8409
8410             while( count < adjudicateLossPlies ) {
8411                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8412
8413                 if( count & 1 ) {
8414                     score = -score; /* Flip score for winning side */
8415                 }
8416
8417                 if( score > adjudicateLossThreshold ) {
8418                     break;
8419                 }
8420
8421                 count++;
8422             }
8423
8424             if( count >= adjudicateLossPlies ) {
8425                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8426
8427                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8428                     "Xboard adjudication",
8429                     GE_XBOARD );
8430
8431                 return;
8432             }
8433         }
8434
8435         if(Adjudicate(cps)) {
8436             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8437             return; // [HGM] adjudicate: for all automatic game ends
8438         }
8439
8440 #if ZIPPY
8441         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8442             first.initDone) {
8443           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8444                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8445                 SendToICS("draw ");
8446                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8447           }
8448           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8449           ics_user_moved = 1;
8450           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8451                 char buf[3*MSG_SIZ];
8452
8453                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8454                         programStats.score / 100.,
8455                         programStats.depth,
8456                         programStats.time / 100.,
8457                         (unsigned int)programStats.nodes,
8458                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8459                         programStats.movelist);
8460                 SendToICS(buf);
8461 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8462           }
8463         }
8464 #endif
8465
8466         /* [AS] Clear stats for next move */
8467         ClearProgramStats();
8468         thinkOutput[0] = NULLCHAR;
8469         hiddenThinkOutputState = 0;
8470
8471         bookHit = NULL;
8472         if (gameMode == TwoMachinesPlay) {
8473             /* [HGM] relaying draw offers moved to after reception of move */
8474             /* and interpreting offer as claim if it brings draw condition */
8475             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8476                 SendToProgram("draw\n", cps->other);
8477             }
8478             if (cps->other->sendTime) {
8479                 SendTimeRemaining(cps->other,
8480                                   cps->other->twoMachinesColor[0] == 'w');
8481             }
8482             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8483             if (firstMove && !bookHit) {
8484                 firstMove = FALSE;
8485                 if (cps->other->useColors) {
8486                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8487                 }
8488                 SendToProgram("go\n", cps->other);
8489             }
8490             cps->other->maybeThinking = TRUE;
8491         }
8492
8493         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8494
8495         if (!pausing && appData.ringBellAfterMoves) {
8496             RingBell();
8497         }
8498
8499         /*
8500          * Reenable menu items that were disabled while
8501          * machine was thinking
8502          */
8503         if (gameMode != TwoMachinesPlay)
8504             SetUserThinkingEnables();
8505
8506         // [HGM] book: after book hit opponent has received move and is now in force mode
8507         // force the book reply into it, and then fake that it outputted this move by jumping
8508         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8509         if(bookHit) {
8510                 static char bookMove[MSG_SIZ]; // a bit generous?
8511
8512                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8513                 strcat(bookMove, bookHit);
8514                 message = bookMove;
8515                 cps = cps->other;
8516                 programStats.nodes = programStats.depth = programStats.time =
8517                 programStats.score = programStats.got_only_move = 0;
8518                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8519
8520                 if(cps->lastPing != cps->lastPong) {
8521                     savedMessage = message; // args for deferred call
8522                     savedState = cps;
8523                     ScheduleDelayedEvent(DeferredBookMove, 10);
8524                     return;
8525                 }
8526                 goto FakeBookMove;
8527         }
8528
8529         return;
8530     }
8531
8532     /* Set special modes for chess engines.  Later something general
8533      *  could be added here; for now there is just one kludge feature,
8534      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8535      *  when "xboard" is given as an interactive command.
8536      */
8537     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8538         cps->useSigint = FALSE;
8539         cps->useSigterm = FALSE;
8540     }
8541     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8542       ParseFeatures(message+8, cps);
8543       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8544     }
8545
8546     if (!strncmp(message, "setup ", 6) && 
8547         (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown || NonStandardBoardSize())
8548                                         ) { // [HGM] allow first engine to define opening position
8549       int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
8550       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8551       *buf = NULLCHAR;
8552       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8553       if(startedFromSetupPosition) return;
8554       dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
8555       if(dummy >= 3) {
8556         while(message[s] && message[s++] != ' ');
8557         if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
8558            dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
8559             appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
8560             if(dummy == 4) gameInfo.variant = StringToVariant(varName);     // parent variant
8561           InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
8562           if(*buf) SetCharTable(pieceToChar, buf); // do again, for it was spoiled by InitPosition
8563         }
8564       }
8565       ParseFEN(boards[0], &dummy, message+s);
8566       DrawPosition(TRUE, boards[0]);
8567       startedFromSetupPosition = TRUE;
8568       return;
8569     }
8570     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8571      * want this, I was asked to put it in, and obliged.
8572      */
8573     if (!strncmp(message, "setboard ", 9)) {
8574         Board initial_position;
8575
8576         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8577
8578         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8579             DisplayError(_("Bad FEN received from engine"), 0);
8580             return ;
8581         } else {
8582            Reset(TRUE, FALSE);
8583            CopyBoard(boards[0], initial_position);
8584            initialRulePlies = FENrulePlies;
8585            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8586            else gameMode = MachinePlaysBlack;
8587            DrawPosition(FALSE, boards[currentMove]);
8588         }
8589         return;
8590     }
8591
8592     /*
8593      * Look for communication commands
8594      */
8595     if (!strncmp(message, "telluser ", 9)) {
8596         if(message[9] == '\\' && message[10] == '\\')
8597             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8598         PlayTellSound();
8599         DisplayNote(message + 9);
8600         return;
8601     }
8602     if (!strncmp(message, "tellusererror ", 14)) {
8603         cps->userError = 1;
8604         if(message[14] == '\\' && message[15] == '\\')
8605             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8606         PlayTellSound();
8607         DisplayError(message + 14, 0);
8608         return;
8609     }
8610     if (!strncmp(message, "tellopponent ", 13)) {
8611       if (appData.icsActive) {
8612         if (loggedOn) {
8613           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8614           SendToICS(buf1);
8615         }
8616       } else {
8617         DisplayNote(message + 13);
8618       }
8619       return;
8620     }
8621     if (!strncmp(message, "tellothers ", 11)) {
8622       if (appData.icsActive) {
8623         if (loggedOn) {
8624           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8625           SendToICS(buf1);
8626         }
8627       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
8628       return;
8629     }
8630     if (!strncmp(message, "tellall ", 8)) {
8631       if (appData.icsActive) {
8632         if (loggedOn) {
8633           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8634           SendToICS(buf1);
8635         }
8636       } else {
8637         DisplayNote(message + 8);
8638       }
8639       return;
8640     }
8641     if (strncmp(message, "warning", 7) == 0) {
8642         /* Undocumented feature, use tellusererror in new code */
8643         DisplayError(message, 0);
8644         return;
8645     }
8646     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8647         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8648         strcat(realname, " query");
8649         AskQuestion(realname, buf2, buf1, cps->pr);
8650         return;
8651     }
8652     /* Commands from the engine directly to ICS.  We don't allow these to be
8653      *  sent until we are logged on. Crafty kibitzes have been known to
8654      *  interfere with the login process.
8655      */
8656     if (loggedOn) {
8657         if (!strncmp(message, "tellics ", 8)) {
8658             SendToICS(message + 8);
8659             SendToICS("\n");
8660             return;
8661         }
8662         if (!strncmp(message, "tellicsnoalias ", 15)) {
8663             SendToICS(ics_prefix);
8664             SendToICS(message + 15);
8665             SendToICS("\n");
8666             return;
8667         }
8668         /* The following are for backward compatibility only */
8669         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8670             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8671             SendToICS(ics_prefix);
8672             SendToICS(message);
8673             SendToICS("\n");
8674             return;
8675         }
8676     }
8677     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8678         return;
8679     }
8680     if(!strncmp(message, "highlight ", 10)) {
8681         if(appData.testLegality && appData.markers) return;
8682         MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
8683         return;
8684     }
8685     if(!strncmp(message, "click ", 6)) {
8686         char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
8687         if(appData.testLegality || !appData.oneClick) return;
8688         sscanf(message+6, "%c%d%c", &f, &y, &c);
8689         x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
8690         if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
8691         x = x*squareSize + (x+1)*lineGap + squareSize/2;
8692         y = y*squareSize + (y+1)*lineGap + squareSize/2;
8693         f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
8694         if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
8695             LeftClick(Release, lastLeftX, lastLeftY);
8696         controlKey  = (c == ',');
8697         LeftClick(Press, x, y);
8698         LeftClick(Release, x, y);
8699         first.highlight = f;
8700         return;
8701     }
8702     /*
8703      * If the move is illegal, cancel it and redraw the board.
8704      * Also deal with other error cases.  Matching is rather loose
8705      * here to accommodate engines written before the spec.
8706      */
8707     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8708         strncmp(message, "Error", 5) == 0) {
8709         if (StrStr(message, "name") ||
8710             StrStr(message, "rating") || StrStr(message, "?") ||
8711             StrStr(message, "result") || StrStr(message, "board") ||
8712             StrStr(message, "bk") || StrStr(message, "computer") ||
8713             StrStr(message, "variant") || StrStr(message, "hint") ||
8714             StrStr(message, "random") || StrStr(message, "depth") ||
8715             StrStr(message, "accepted")) {
8716             return;
8717         }
8718         if (StrStr(message, "protover")) {
8719           /* Program is responding to input, so it's apparently done
8720              initializing, and this error message indicates it is
8721              protocol version 1.  So we don't need to wait any longer
8722              for it to initialize and send feature commands. */
8723           FeatureDone(cps, 1);
8724           cps->protocolVersion = 1;
8725           return;
8726         }
8727         cps->maybeThinking = FALSE;
8728
8729         if (StrStr(message, "draw")) {
8730             /* Program doesn't have "draw" command */
8731             cps->sendDrawOffers = 0;
8732             return;
8733         }
8734         if (cps->sendTime != 1 &&
8735             (StrStr(message, "time") || StrStr(message, "otim"))) {
8736           /* Program apparently doesn't have "time" or "otim" command */
8737           cps->sendTime = 0;
8738           return;
8739         }
8740         if (StrStr(message, "analyze")) {
8741             cps->analysisSupport = FALSE;
8742             cps->analyzing = FALSE;
8743 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8744             EditGameEvent(); // [HGM] try to preserve loaded game
8745             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8746             DisplayError(buf2, 0);
8747             return;
8748         }
8749         if (StrStr(message, "(no matching move)st")) {
8750           /* Special kludge for GNU Chess 4 only */
8751           cps->stKludge = TRUE;
8752           SendTimeControl(cps, movesPerSession, timeControl,
8753                           timeIncrement, appData.searchDepth,
8754                           searchTime);
8755           return;
8756         }
8757         if (StrStr(message, "(no matching move)sd")) {
8758           /* Special kludge for GNU Chess 4 only */
8759           cps->sdKludge = TRUE;
8760           SendTimeControl(cps, movesPerSession, timeControl,
8761                           timeIncrement, appData.searchDepth,
8762                           searchTime);
8763           return;
8764         }
8765         if (!StrStr(message, "llegal")) {
8766             return;
8767         }
8768         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8769             gameMode == IcsIdle) return;
8770         if (forwardMostMove <= backwardMostMove) return;
8771         if (pausing) PauseEvent();
8772       if(appData.forceIllegal) {
8773             // [HGM] illegal: machine refused move; force position after move into it
8774           SendToProgram("force\n", cps);
8775           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8776                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8777                 // when black is to move, while there might be nothing on a2 or black
8778                 // might already have the move. So send the board as if white has the move.
8779                 // But first we must change the stm of the engine, as it refused the last move
8780                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8781                 if(WhiteOnMove(forwardMostMove)) {
8782                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8783                     SendBoard(cps, forwardMostMove); // kludgeless board
8784                 } else {
8785                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8786                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8787                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8788                 }
8789           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8790             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8791                  gameMode == TwoMachinesPlay)
8792               SendToProgram("go\n", cps);
8793             return;
8794       } else
8795         if (gameMode == PlayFromGameFile) {
8796             /* Stop reading this game file */
8797             gameMode = EditGame;
8798             ModeHighlight();
8799         }
8800         /* [HGM] illegal-move claim should forfeit game when Xboard */
8801         /* only passes fully legal moves                            */
8802         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8803             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8804                                 "False illegal-move claim", GE_XBOARD );
8805             return; // do not take back move we tested as valid
8806         }
8807         currentMove = forwardMostMove-1;
8808         DisplayMove(currentMove-1); /* before DisplayMoveError */
8809         SwitchClocks(forwardMostMove-1); // [HGM] race
8810         DisplayBothClocks();
8811         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8812                 parseList[currentMove], _(cps->which));
8813         DisplayMoveError(buf1);
8814         DrawPosition(FALSE, boards[currentMove]);
8815
8816         SetUserThinkingEnables();
8817         return;
8818     }
8819     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8820         /* Program has a broken "time" command that
8821            outputs a string not ending in newline.
8822            Don't use it. */
8823         cps->sendTime = 0;
8824     }
8825
8826     /*
8827      * If chess program startup fails, exit with an error message.
8828      * Attempts to recover here are futile. [HGM] Well, we try anyway
8829      */
8830     if ((StrStr(message, "unknown host") != NULL)
8831         || (StrStr(message, "No remote directory") != NULL)
8832         || (StrStr(message, "not found") != NULL)
8833         || (StrStr(message, "No such file") != NULL)
8834         || (StrStr(message, "can't alloc") != NULL)
8835         || (StrStr(message, "Permission denied") != NULL)) {
8836
8837         cps->maybeThinking = FALSE;
8838         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8839                 _(cps->which), cps->program, cps->host, message);
8840         RemoveInputSource(cps->isr);
8841         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8842             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
8843             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
8844         }
8845         return;
8846     }
8847
8848     /*
8849      * Look for hint output
8850      */
8851     if (sscanf(message, "Hint: %s", buf1) == 1) {
8852         if (cps == &first && hintRequested) {
8853             hintRequested = FALSE;
8854             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8855                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8856                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8857                                     PosFlags(forwardMostMove),
8858                                     fromY, fromX, toY, toX, promoChar, buf1);
8859                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8860                 DisplayInformation(buf2);
8861             } else {
8862                 /* Hint move could not be parsed!? */
8863               snprintf(buf2, sizeof(buf2),
8864                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8865                         buf1, _(cps->which));
8866                 DisplayError(buf2, 0);
8867             }
8868         } else {
8869           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8870         }
8871         return;
8872     }
8873
8874     /*
8875      * Ignore other messages if game is not in progress
8876      */
8877     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8878         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8879
8880     /*
8881      * look for win, lose, draw, or draw offer
8882      */
8883     if (strncmp(message, "1-0", 3) == 0) {
8884         char *p, *q, *r = "";
8885         p = strchr(message, '{');
8886         if (p) {
8887             q = strchr(p, '}');
8888             if (q) {
8889                 *q = NULLCHAR;
8890                 r = p + 1;
8891             }
8892         }
8893         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8894         return;
8895     } else if (strncmp(message, "0-1", 3) == 0) {
8896         char *p, *q, *r = "";
8897         p = strchr(message, '{');
8898         if (p) {
8899             q = strchr(p, '}');
8900             if (q) {
8901                 *q = NULLCHAR;
8902                 r = p + 1;
8903             }
8904         }
8905         /* Kludge for Arasan 4.1 bug */
8906         if (strcmp(r, "Black resigns") == 0) {
8907             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8908             return;
8909         }
8910         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8911         return;
8912     } else if (strncmp(message, "1/2", 3) == 0) {
8913         char *p, *q, *r = "";
8914         p = strchr(message, '{');
8915         if (p) {
8916             q = strchr(p, '}');
8917             if (q) {
8918                 *q = NULLCHAR;
8919                 r = p + 1;
8920             }
8921         }
8922
8923         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8924         return;
8925
8926     } else if (strncmp(message, "White resign", 12) == 0) {
8927         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8928         return;
8929     } else if (strncmp(message, "Black resign", 12) == 0) {
8930         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8931         return;
8932     } else if (strncmp(message, "White matches", 13) == 0 ||
8933                strncmp(message, "Black matches", 13) == 0   ) {
8934         /* [HGM] ignore GNUShogi noises */
8935         return;
8936     } else if (strncmp(message, "White", 5) == 0 &&
8937                message[5] != '(' &&
8938                StrStr(message, "Black") == NULL) {
8939         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8940         return;
8941     } else if (strncmp(message, "Black", 5) == 0 &&
8942                message[5] != '(') {
8943         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8944         return;
8945     } else if (strcmp(message, "resign") == 0 ||
8946                strcmp(message, "computer resigns") == 0) {
8947         switch (gameMode) {
8948           case MachinePlaysBlack:
8949           case IcsPlayingBlack:
8950             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8951             break;
8952           case MachinePlaysWhite:
8953           case IcsPlayingWhite:
8954             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8955             break;
8956           case TwoMachinesPlay:
8957             if (cps->twoMachinesColor[0] == 'w')
8958               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8959             else
8960               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8961             break;
8962           default:
8963             /* can't happen */
8964             break;
8965         }
8966         return;
8967     } else if (strncmp(message, "opponent mates", 14) == 0) {
8968         switch (gameMode) {
8969           case MachinePlaysBlack:
8970           case IcsPlayingBlack:
8971             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8972             break;
8973           case MachinePlaysWhite:
8974           case IcsPlayingWhite:
8975             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8976             break;
8977           case TwoMachinesPlay:
8978             if (cps->twoMachinesColor[0] == 'w')
8979               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8980             else
8981               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8982             break;
8983           default:
8984             /* can't happen */
8985             break;
8986         }
8987         return;
8988     } else if (strncmp(message, "computer mates", 14) == 0) {
8989         switch (gameMode) {
8990           case MachinePlaysBlack:
8991           case IcsPlayingBlack:
8992             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8993             break;
8994           case MachinePlaysWhite:
8995           case IcsPlayingWhite:
8996             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8997             break;
8998           case TwoMachinesPlay:
8999             if (cps->twoMachinesColor[0] == 'w')
9000               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9001             else
9002               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9003             break;
9004           default:
9005             /* can't happen */
9006             break;
9007         }
9008         return;
9009     } else if (strncmp(message, "checkmate", 9) == 0) {
9010         if (WhiteOnMove(forwardMostMove)) {
9011             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9012         } else {
9013             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9014         }
9015         return;
9016     } else if (strstr(message, "Draw") != NULL ||
9017                strstr(message, "game is a draw") != NULL) {
9018         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9019         return;
9020     } else if (strstr(message, "offer") != NULL &&
9021                strstr(message, "draw") != NULL) {
9022 #if ZIPPY
9023         if (appData.zippyPlay && first.initDone) {
9024             /* Relay offer to ICS */
9025             SendToICS(ics_prefix);
9026             SendToICS("draw\n");
9027         }
9028 #endif
9029         cps->offeredDraw = 2; /* valid until this engine moves twice */
9030         if (gameMode == TwoMachinesPlay) {
9031             if (cps->other->offeredDraw) {
9032                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9033             /* [HGM] in two-machine mode we delay relaying draw offer      */
9034             /* until after we also have move, to see if it is really claim */
9035             }
9036         } else if (gameMode == MachinePlaysWhite ||
9037                    gameMode == MachinePlaysBlack) {
9038           if (userOfferedDraw) {
9039             DisplayInformation(_("Machine accepts your draw offer"));
9040             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9041           } else {
9042             DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9043           }
9044         }
9045     }
9046
9047
9048     /*
9049      * Look for thinking output
9050      */
9051     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9052           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9053                                 ) {
9054         int plylev, mvleft, mvtot, curscore, time;
9055         char mvname[MOVE_LEN];
9056         u64 nodes; // [DM]
9057         char plyext;
9058         int ignore = FALSE;
9059         int prefixHint = FALSE;
9060         mvname[0] = NULLCHAR;
9061
9062         switch (gameMode) {
9063           case MachinePlaysBlack:
9064           case IcsPlayingBlack:
9065             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9066             break;
9067           case MachinePlaysWhite:
9068           case IcsPlayingWhite:
9069             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9070             break;
9071           case AnalyzeMode:
9072           case AnalyzeFile:
9073             break;
9074           case IcsObserving: /* [DM] icsEngineAnalyze */
9075             if (!appData.icsEngineAnalyze) ignore = TRUE;
9076             break;
9077           case TwoMachinesPlay:
9078             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9079                 ignore = TRUE;
9080             }
9081             break;
9082           default:
9083             ignore = TRUE;
9084             break;
9085         }
9086
9087         if (!ignore) {
9088             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9089             buf1[0] = NULLCHAR;
9090             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9091                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9092
9093                 if (plyext != ' ' && plyext != '\t') {
9094                     time *= 100;
9095                 }
9096
9097                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9098                 if( cps->scoreIsAbsolute &&
9099                     ( gameMode == MachinePlaysBlack ||
9100                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9101                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
9102                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9103                      !WhiteOnMove(currentMove)
9104                     ) )
9105                 {
9106                     curscore = -curscore;
9107                 }
9108
9109                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9110
9111                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9112                         char buf[MSG_SIZ];
9113                         FILE *f;
9114                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9115                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9116                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9117                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9118                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
9119                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9120                                 fclose(f);
9121                         }
9122                         else
9123                           /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9124                           DisplayError(_("failed writing PV"), 0);
9125                 }
9126
9127                 tempStats.depth = plylev;
9128                 tempStats.nodes = nodes;
9129                 tempStats.time = time;
9130                 tempStats.score = curscore;
9131                 tempStats.got_only_move = 0;
9132
9133                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9134                         int ticklen;
9135
9136                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
9137                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9138                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9139                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9140                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9141                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9142                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9143                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9144                 }
9145
9146                 /* Buffer overflow protection */
9147                 if (pv[0] != NULLCHAR) {
9148                     if (strlen(pv) >= sizeof(tempStats.movelist)
9149                         && appData.debugMode) {
9150                         fprintf(debugFP,
9151                                 "PV is too long; using the first %u bytes.\n",
9152                                 (unsigned) sizeof(tempStats.movelist) - 1);
9153                     }
9154
9155                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9156                 } else {
9157                     sprintf(tempStats.movelist, " no PV\n");
9158                 }
9159
9160                 if (tempStats.seen_stat) {
9161                     tempStats.ok_to_send = 1;
9162                 }
9163
9164                 if (strchr(tempStats.movelist, '(') != NULL) {
9165                     tempStats.line_is_book = 1;
9166                     tempStats.nr_moves = 0;
9167                     tempStats.moves_left = 0;
9168                 } else {
9169                     tempStats.line_is_book = 0;
9170                 }
9171
9172                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9173                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9174
9175                 SendProgramStatsToFrontend( cps, &tempStats );
9176
9177                 /*
9178                     [AS] Protect the thinkOutput buffer from overflow... this
9179                     is only useful if buf1 hasn't overflowed first!
9180                 */
9181                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
9182                          plylev,
9183                          (gameMode == TwoMachinesPlay ?
9184                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9185                          ((double) curscore) / 100.0,
9186                          prefixHint ? lastHint : "",
9187                          prefixHint ? " " : "" );
9188
9189                 if( buf1[0] != NULLCHAR ) {
9190                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9191
9192                     if( strlen(pv) > max_len ) {
9193                         if( appData.debugMode) {
9194                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9195                         }
9196                         pv[max_len+1] = '\0';
9197                     }
9198
9199                     strcat( thinkOutput, pv);
9200                 }
9201
9202                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9203                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9204                     DisplayMove(currentMove - 1);
9205                 }
9206                 return;
9207
9208             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9209                 /* crafty (9.25+) says "(only move) <move>"
9210                  * if there is only 1 legal move
9211                  */
9212                 sscanf(p, "(only move) %s", buf1);
9213                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9214                 sprintf(programStats.movelist, "%s (only move)", buf1);
9215                 programStats.depth = 1;
9216                 programStats.nr_moves = 1;
9217                 programStats.moves_left = 1;
9218                 programStats.nodes = 1;
9219                 programStats.time = 1;
9220                 programStats.got_only_move = 1;
9221
9222                 /* Not really, but we also use this member to
9223                    mean "line isn't going to change" (Crafty
9224                    isn't searching, so stats won't change) */
9225                 programStats.line_is_book = 1;
9226
9227                 SendProgramStatsToFrontend( cps, &programStats );
9228
9229                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9230                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9231                     DisplayMove(currentMove - 1);
9232                 }
9233                 return;
9234             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9235                               &time, &nodes, &plylev, &mvleft,
9236                               &mvtot, mvname) >= 5) {
9237                 /* The stat01: line is from Crafty (9.29+) in response
9238                    to the "." command */
9239                 programStats.seen_stat = 1;
9240                 cps->maybeThinking = TRUE;
9241
9242                 if (programStats.got_only_move || !appData.periodicUpdates)
9243                   return;
9244
9245                 programStats.depth = plylev;
9246                 programStats.time = time;
9247                 programStats.nodes = nodes;
9248                 programStats.moves_left = mvleft;
9249                 programStats.nr_moves = mvtot;
9250                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9251                 programStats.ok_to_send = 1;
9252                 programStats.movelist[0] = '\0';
9253
9254                 SendProgramStatsToFrontend( cps, &programStats );
9255
9256                 return;
9257
9258             } else if (strncmp(message,"++",2) == 0) {
9259                 /* Crafty 9.29+ outputs this */
9260                 programStats.got_fail = 2;
9261                 return;
9262
9263             } else if (strncmp(message,"--",2) == 0) {
9264                 /* Crafty 9.29+ outputs this */
9265                 programStats.got_fail = 1;
9266                 return;
9267
9268             } else if (thinkOutput[0] != NULLCHAR &&
9269                        strncmp(message, "    ", 4) == 0) {
9270                 unsigned message_len;
9271
9272                 p = message;
9273                 while (*p && *p == ' ') p++;
9274
9275                 message_len = strlen( p );
9276
9277                 /* [AS] Avoid buffer overflow */
9278                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9279                     strcat(thinkOutput, " ");
9280                     strcat(thinkOutput, p);
9281                 }
9282
9283                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9284                     strcat(programStats.movelist, " ");
9285                     strcat(programStats.movelist, p);
9286                 }
9287
9288                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9289                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9290                     DisplayMove(currentMove - 1);
9291                 }
9292                 return;
9293             }
9294         }
9295         else {
9296             buf1[0] = NULLCHAR;
9297
9298             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9299                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9300             {
9301                 ChessProgramStats cpstats;
9302
9303                 if (plyext != ' ' && plyext != '\t') {
9304                     time *= 100;
9305                 }
9306
9307                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9308                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9309                     curscore = -curscore;
9310                 }
9311
9312                 cpstats.depth = plylev;
9313                 cpstats.nodes = nodes;
9314                 cpstats.time = time;
9315                 cpstats.score = curscore;
9316                 cpstats.got_only_move = 0;
9317                 cpstats.movelist[0] = '\0';
9318
9319                 if (buf1[0] != NULLCHAR) {
9320                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9321                 }
9322
9323                 cpstats.ok_to_send = 0;
9324                 cpstats.line_is_book = 0;
9325                 cpstats.nr_moves = 0;
9326                 cpstats.moves_left = 0;
9327
9328                 SendProgramStatsToFrontend( cps, &cpstats );
9329             }
9330         }
9331     }
9332 }
9333
9334
9335 /* Parse a game score from the character string "game", and
9336    record it as the history of the current game.  The game
9337    score is NOT assumed to start from the standard position.
9338    The display is not updated in any way.
9339    */
9340 void
9341 ParseGameHistory (char *game)
9342 {
9343     ChessMove moveType;
9344     int fromX, fromY, toX, toY, boardIndex;
9345     char promoChar;
9346     char *p, *q;
9347     char buf[MSG_SIZ];
9348
9349     if (appData.debugMode)
9350       fprintf(debugFP, "Parsing game history: %s\n", game);
9351
9352     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9353     gameInfo.site = StrSave(appData.icsHost);
9354     gameInfo.date = PGNDate();
9355     gameInfo.round = StrSave("-");
9356
9357     /* Parse out names of players */
9358     while (*game == ' ') game++;
9359     p = buf;
9360     while (*game != ' ') *p++ = *game++;
9361     *p = NULLCHAR;
9362     gameInfo.white = StrSave(buf);
9363     while (*game == ' ') game++;
9364     p = buf;
9365     while (*game != ' ' && *game != '\n') *p++ = *game++;
9366     *p = NULLCHAR;
9367     gameInfo.black = StrSave(buf);
9368
9369     /* Parse moves */
9370     boardIndex = blackPlaysFirst ? 1 : 0;
9371     yynewstr(game);
9372     for (;;) {
9373         yyboardindex = boardIndex;
9374         moveType = (ChessMove) Myylex();
9375         switch (moveType) {
9376           case IllegalMove:             /* maybe suicide chess, etc. */
9377   if (appData.debugMode) {
9378     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9379     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9380     setbuf(debugFP, NULL);
9381   }
9382           case WhitePromotion:
9383           case BlackPromotion:
9384           case WhiteNonPromotion:
9385           case BlackNonPromotion:
9386           case NormalMove:
9387           case WhiteCapturesEnPassant:
9388           case BlackCapturesEnPassant:
9389           case WhiteKingSideCastle:
9390           case WhiteQueenSideCastle:
9391           case BlackKingSideCastle:
9392           case BlackQueenSideCastle:
9393           case WhiteKingSideCastleWild:
9394           case WhiteQueenSideCastleWild:
9395           case BlackKingSideCastleWild:
9396           case BlackQueenSideCastleWild:
9397           /* PUSH Fabien */
9398           case WhiteHSideCastleFR:
9399           case WhiteASideCastleFR:
9400           case BlackHSideCastleFR:
9401           case BlackASideCastleFR:
9402           /* POP Fabien */
9403             fromX = currentMoveString[0] - AAA;
9404             fromY = currentMoveString[1] - ONE;
9405             toX = currentMoveString[2] - AAA;
9406             toY = currentMoveString[3] - ONE;
9407             promoChar = currentMoveString[4];
9408             break;
9409           case WhiteDrop:
9410           case BlackDrop:
9411             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9412             fromX = moveType == WhiteDrop ?
9413               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9414             (int) CharToPiece(ToLower(currentMoveString[0]));
9415             fromY = DROP_RANK;
9416             toX = currentMoveString[2] - AAA;
9417             toY = currentMoveString[3] - ONE;
9418             promoChar = NULLCHAR;
9419             break;
9420           case AmbiguousMove:
9421             /* bug? */
9422             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9423   if (appData.debugMode) {
9424     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9425     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9426     setbuf(debugFP, NULL);
9427   }
9428             DisplayError(buf, 0);
9429             return;
9430           case ImpossibleMove:
9431             /* bug? */
9432             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9433   if (appData.debugMode) {
9434     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9435     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9436     setbuf(debugFP, NULL);
9437   }
9438             DisplayError(buf, 0);
9439             return;
9440           case EndOfFile:
9441             if (boardIndex < backwardMostMove) {
9442                 /* Oops, gap.  How did that happen? */
9443                 DisplayError(_("Gap in move list"), 0);
9444                 return;
9445             }
9446             backwardMostMove =  blackPlaysFirst ? 1 : 0;
9447             if (boardIndex > forwardMostMove) {
9448                 forwardMostMove = boardIndex;
9449             }
9450             return;
9451           case ElapsedTime:
9452             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9453                 strcat(parseList[boardIndex-1], " ");
9454                 strcat(parseList[boardIndex-1], yy_text);
9455             }
9456             continue;
9457           case Comment:
9458           case PGNTag:
9459           case NAG:
9460           default:
9461             /* ignore */
9462             continue;
9463           case WhiteWins:
9464           case BlackWins:
9465           case GameIsDrawn:
9466           case GameUnfinished:
9467             if (gameMode == IcsExamining) {
9468                 if (boardIndex < backwardMostMove) {
9469                     /* Oops, gap.  How did that happen? */
9470                     return;
9471                 }
9472                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9473                 return;
9474             }
9475             gameInfo.result = moveType;
9476             p = strchr(yy_text, '{');
9477             if (p == NULL) p = strchr(yy_text, '(');
9478             if (p == NULL) {
9479                 p = yy_text;
9480                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9481             } else {
9482                 q = strchr(p, *p == '{' ? '}' : ')');
9483                 if (q != NULL) *q = NULLCHAR;
9484                 p++;
9485             }
9486             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9487             gameInfo.resultDetails = StrSave(p);
9488             continue;
9489         }
9490         if (boardIndex >= forwardMostMove &&
9491             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9492             backwardMostMove = blackPlaysFirst ? 1 : 0;
9493             return;
9494         }
9495         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9496                                  fromY, fromX, toY, toX, promoChar,
9497                                  parseList[boardIndex]);
9498         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9499         /* currentMoveString is set as a side-effect of yylex */
9500         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9501         strcat(moveList[boardIndex], "\n");
9502         boardIndex++;
9503         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9504         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9505           case MT_NONE:
9506           case MT_STALEMATE:
9507           default:
9508             break;
9509           case MT_CHECK:
9510             if(gameInfo.variant != VariantShogi)
9511                 strcat(parseList[boardIndex - 1], "+");
9512             break;
9513           case MT_CHECKMATE:
9514           case MT_STAINMATE:
9515             strcat(parseList[boardIndex - 1], "#");
9516             break;
9517         }
9518     }
9519 }
9520
9521
9522 /* Apply a move to the given board  */
9523 void
9524 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9525 {
9526   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9527   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9528
9529     /* [HGM] compute & store e.p. status and castling rights for new position */
9530     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9531
9532       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9533       oldEP = (signed char)board[EP_STATUS];
9534       board[EP_STATUS] = EP_NONE;
9535
9536   if (fromY == DROP_RANK) {
9537         /* must be first */
9538         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9539             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9540             return;
9541         }
9542         piece = board[toY][toX] = (ChessSquare) fromX;
9543   } else {
9544       int i;
9545
9546       if( board[toY][toX] != EmptySquare )
9547            board[EP_STATUS] = EP_CAPTURE;
9548
9549       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9550            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9551                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9552       } else
9553       if( board[fromY][fromX] == WhitePawn ) {
9554            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9555                board[EP_STATUS] = EP_PAWN_MOVE;
9556            if( toY-fromY==2) {
9557                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9558                         gameInfo.variant != VariantBerolina || toX < fromX)
9559                       board[EP_STATUS] = toX | berolina;
9560                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9561                         gameInfo.variant != VariantBerolina || toX > fromX)
9562                       board[EP_STATUS] = toX;
9563            }
9564       } else
9565       if( board[fromY][fromX] == BlackPawn ) {
9566            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9567                board[EP_STATUS] = EP_PAWN_MOVE;
9568            if( toY-fromY== -2) {
9569                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9570                         gameInfo.variant != VariantBerolina || toX < fromX)
9571                       board[EP_STATUS] = toX | berolina;
9572                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9573                         gameInfo.variant != VariantBerolina || toX > fromX)
9574                       board[EP_STATUS] = toX;
9575            }
9576        }
9577
9578        for(i=0; i<nrCastlingRights; i++) {
9579            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9580               board[CASTLING][i] == toX   && castlingRank[i] == toY
9581              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9582        }
9583
9584        if(gameInfo.variant == VariantSChess) { // update virginity
9585            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
9586            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
9587            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
9588            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
9589        }
9590
9591      if (fromX == toX && fromY == toY) return;
9592
9593      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9594      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9595      if(gameInfo.variant == VariantKnightmate)
9596          king += (int) WhiteUnicorn - (int) WhiteKing;
9597
9598     /* Code added by Tord: */
9599     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9600     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9601         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9602       board[fromY][fromX] = EmptySquare;
9603       board[toY][toX] = EmptySquare;
9604       if((toX > fromX) != (piece == WhiteRook)) {
9605         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9606       } else {
9607         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9608       }
9609     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9610                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9611       board[fromY][fromX] = EmptySquare;
9612       board[toY][toX] = EmptySquare;
9613       if((toX > fromX) != (piece == BlackRook)) {
9614         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9615       } else {
9616         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9617       }
9618     /* End of code added by Tord */
9619
9620     } else if (board[fromY][fromX] == king
9621         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9622         && toY == fromY && toX > fromX+1) {
9623         board[fromY][fromX] = EmptySquare;
9624         board[toY][toX] = king;
9625         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9626         board[fromY][BOARD_RGHT-1] = EmptySquare;
9627     } else if (board[fromY][fromX] == king
9628         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9629                && toY == fromY && toX < fromX-1) {
9630         board[fromY][fromX] = EmptySquare;
9631         board[toY][toX] = king;
9632         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9633         board[fromY][BOARD_LEFT] = EmptySquare;
9634     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9635                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9636                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9637                ) {
9638         /* white pawn promotion */
9639         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9640         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9641             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9642         board[fromY][fromX] = EmptySquare;
9643     } else if ((fromY >= BOARD_HEIGHT>>1)
9644                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9645                && (toX != fromX)
9646                && gameInfo.variant != VariantXiangqi
9647                && gameInfo.variant != VariantBerolina
9648                && (board[fromY][fromX] == WhitePawn)
9649                && (board[toY][toX] == EmptySquare)) {
9650         board[fromY][fromX] = EmptySquare;
9651         board[toY][toX] = WhitePawn;
9652         captured = board[toY - 1][toX];
9653         board[toY - 1][toX] = EmptySquare;
9654     } else if ((fromY == BOARD_HEIGHT-4)
9655                && (toX == fromX)
9656                && gameInfo.variant == VariantBerolina
9657                && (board[fromY][fromX] == WhitePawn)
9658                && (board[toY][toX] == EmptySquare)) {
9659         board[fromY][fromX] = EmptySquare;
9660         board[toY][toX] = WhitePawn;
9661         if(oldEP & EP_BEROLIN_A) {
9662                 captured = board[fromY][fromX-1];
9663                 board[fromY][fromX-1] = EmptySquare;
9664         }else{  captured = board[fromY][fromX+1];
9665                 board[fromY][fromX+1] = EmptySquare;
9666         }
9667     } else if (board[fromY][fromX] == king
9668         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9669                && toY == fromY && toX > fromX+1) {
9670         board[fromY][fromX] = EmptySquare;
9671         board[toY][toX] = king;
9672         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9673         board[fromY][BOARD_RGHT-1] = EmptySquare;
9674     } else if (board[fromY][fromX] == king
9675         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9676                && toY == fromY && toX < fromX-1) {
9677         board[fromY][fromX] = EmptySquare;
9678         board[toY][toX] = king;
9679         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9680         board[fromY][BOARD_LEFT] = EmptySquare;
9681     } else if (fromY == 7 && fromX == 3
9682                && board[fromY][fromX] == BlackKing
9683                && toY == 7 && toX == 5) {
9684         board[fromY][fromX] = EmptySquare;
9685         board[toY][toX] = BlackKing;
9686         board[fromY][7] = EmptySquare;
9687         board[toY][4] = BlackRook;
9688     } else if (fromY == 7 && fromX == 3
9689                && board[fromY][fromX] == BlackKing
9690                && toY == 7 && toX == 1) {
9691         board[fromY][fromX] = EmptySquare;
9692         board[toY][toX] = BlackKing;
9693         board[fromY][0] = EmptySquare;
9694         board[toY][2] = BlackRook;
9695     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9696                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9697                && toY < promoRank && promoChar
9698                ) {
9699         /* black pawn promotion */
9700         board[toY][toX] = CharToPiece(ToLower(promoChar));
9701         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9702             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9703         board[fromY][fromX] = EmptySquare;
9704     } else if ((fromY < BOARD_HEIGHT>>1)
9705                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9706                && (toX != fromX)
9707                && gameInfo.variant != VariantXiangqi
9708                && gameInfo.variant != VariantBerolina
9709                && (board[fromY][fromX] == BlackPawn)
9710                && (board[toY][toX] == EmptySquare)) {
9711         board[fromY][fromX] = EmptySquare;
9712         board[toY][toX] = BlackPawn;
9713         captured = board[toY + 1][toX];
9714         board[toY + 1][toX] = EmptySquare;
9715     } else if ((fromY == 3)
9716                && (toX == fromX)
9717                && gameInfo.variant == VariantBerolina
9718                && (board[fromY][fromX] == BlackPawn)
9719                && (board[toY][toX] == EmptySquare)) {
9720         board[fromY][fromX] = EmptySquare;
9721         board[toY][toX] = BlackPawn;
9722         if(oldEP & EP_BEROLIN_A) {
9723                 captured = board[fromY][fromX-1];
9724                 board[fromY][fromX-1] = EmptySquare;
9725         }else{  captured = board[fromY][fromX+1];
9726                 board[fromY][fromX+1] = EmptySquare;
9727         }
9728     } else {
9729         board[toY][toX] = board[fromY][fromX];
9730         board[fromY][fromX] = EmptySquare;
9731     }
9732   }
9733
9734     if (gameInfo.holdingsWidth != 0) {
9735
9736       /* !!A lot more code needs to be written to support holdings  */
9737       /* [HGM] OK, so I have written it. Holdings are stored in the */
9738       /* penultimate board files, so they are automaticlly stored   */
9739       /* in the game history.                                       */
9740       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9741                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9742         /* Delete from holdings, by decreasing count */
9743         /* and erasing image if necessary            */
9744         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9745         if(p < (int) BlackPawn) { /* white drop */
9746              p -= (int)WhitePawn;
9747                  p = PieceToNumber((ChessSquare)p);
9748              if(p >= gameInfo.holdingsSize) p = 0;
9749              if(--board[p][BOARD_WIDTH-2] <= 0)
9750                   board[p][BOARD_WIDTH-1] = EmptySquare;
9751              if((int)board[p][BOARD_WIDTH-2] < 0)
9752                         board[p][BOARD_WIDTH-2] = 0;
9753         } else {                  /* black drop */
9754              p -= (int)BlackPawn;
9755                  p = PieceToNumber((ChessSquare)p);
9756              if(p >= gameInfo.holdingsSize) p = 0;
9757              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9758                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9759              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9760                         board[BOARD_HEIGHT-1-p][1] = 0;
9761         }
9762       }
9763       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9764           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9765         /* [HGM] holdings: Add to holdings, if holdings exist */
9766         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9767                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9768                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9769         }
9770         p = (int) captured;
9771         if (p >= (int) BlackPawn) {
9772           p -= (int)BlackPawn;
9773           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9774                   /* in Shogi restore piece to its original  first */
9775                   captured = (ChessSquare) (DEMOTED captured);
9776                   p = DEMOTED p;
9777           }
9778           p = PieceToNumber((ChessSquare)p);
9779           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9780           board[p][BOARD_WIDTH-2]++;
9781           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9782         } else {
9783           p -= (int)WhitePawn;
9784           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9785                   captured = (ChessSquare) (DEMOTED captured);
9786                   p = DEMOTED p;
9787           }
9788           p = PieceToNumber((ChessSquare)p);
9789           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9790           board[BOARD_HEIGHT-1-p][1]++;
9791           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9792         }
9793       }
9794     } else if (gameInfo.variant == VariantAtomic) {
9795       if (captured != EmptySquare) {
9796         int y, x;
9797         for (y = toY-1; y <= toY+1; y++) {
9798           for (x = toX-1; x <= toX+1; x++) {
9799             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9800                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9801               board[y][x] = EmptySquare;
9802             }
9803           }
9804         }
9805         board[toY][toX] = EmptySquare;
9806       }
9807     }
9808     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9809         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9810     } else
9811     if(promoChar == '+') {
9812         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
9813         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9814     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9815         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9816         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
9817            && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
9818         board[toY][toX] = newPiece;
9819     }
9820     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9821                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9822         // [HGM] superchess: take promotion piece out of holdings
9823         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9824         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9825             if(!--board[k][BOARD_WIDTH-2])
9826                 board[k][BOARD_WIDTH-1] = EmptySquare;
9827         } else {
9828             if(!--board[BOARD_HEIGHT-1-k][1])
9829                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9830         }
9831     }
9832
9833 }
9834
9835 /* Updates forwardMostMove */
9836 void
9837 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
9838 {
9839 //    forwardMostMove++; // [HGM] bare: moved downstream
9840
9841     (void) CoordsToAlgebraic(boards[forwardMostMove],
9842                              PosFlags(forwardMostMove),
9843                              fromY, fromX, toY, toX, promoChar,
9844                              parseList[forwardMostMove]);
9845
9846     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9847         int timeLeft; static int lastLoadFlag=0; int king, piece;
9848         piece = boards[forwardMostMove][fromY][fromX];
9849         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9850         if(gameInfo.variant == VariantKnightmate)
9851             king += (int) WhiteUnicorn - (int) WhiteKing;
9852         if(forwardMostMove == 0) {
9853             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9854                 fprintf(serverMoves, "%s;", UserName());
9855             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9856                 fprintf(serverMoves, "%s;", second.tidy);
9857             fprintf(serverMoves, "%s;", first.tidy);
9858             if(gameMode == MachinePlaysWhite)
9859                 fprintf(serverMoves, "%s;", UserName());
9860             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9861                 fprintf(serverMoves, "%s;", second.tidy);
9862         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9863         lastLoadFlag = loadFlag;
9864         // print base move
9865         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9866         // print castling suffix
9867         if( toY == fromY && piece == king ) {
9868             if(toX-fromX > 1)
9869                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9870             if(fromX-toX >1)
9871                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9872         }
9873         // e.p. suffix
9874         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9875              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9876              boards[forwardMostMove][toY][toX] == EmptySquare
9877              && fromX != toX && fromY != toY)
9878                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9879         // promotion suffix
9880         if(promoChar != NULLCHAR) {
9881             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
9882                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
9883                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
9884             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
9885         }
9886         if(!loadFlag) {
9887                 char buf[MOVE_LEN*2], *p; int len;
9888             fprintf(serverMoves, "/%d/%d",
9889                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9890             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9891             else                      timeLeft = blackTimeRemaining/1000;
9892             fprintf(serverMoves, "/%d", timeLeft);
9893                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
9894                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
9895                 if(p = strchr(buf, '=')) *p = NULLCHAR;
9896                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
9897             fprintf(serverMoves, "/%s", buf);
9898         }
9899         fflush(serverMoves);
9900     }
9901
9902     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
9903         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
9904       return;
9905     }
9906     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9907     if (commentList[forwardMostMove+1] != NULL) {
9908         free(commentList[forwardMostMove+1]);
9909         commentList[forwardMostMove+1] = NULL;
9910     }
9911     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9912     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9913     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9914     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9915     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9916     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9917     adjustedClock = FALSE;
9918     gameInfo.result = GameUnfinished;
9919     if (gameInfo.resultDetails != NULL) {
9920         free(gameInfo.resultDetails);
9921         gameInfo.resultDetails = NULL;
9922     }
9923     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9924                               moveList[forwardMostMove - 1]);
9925     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9926       case MT_NONE:
9927       case MT_STALEMATE:
9928       default:
9929         break;
9930       case MT_CHECK:
9931         if(gameInfo.variant != VariantShogi)
9932             strcat(parseList[forwardMostMove - 1], "+");
9933         break;
9934       case MT_CHECKMATE:
9935       case MT_STAINMATE:
9936         strcat(parseList[forwardMostMove - 1], "#");
9937         break;
9938     }
9939
9940 }
9941
9942 /* Updates currentMove if not pausing */
9943 void
9944 ShowMove (int fromX, int fromY, int toX, int toY)
9945 {
9946     int instant = (gameMode == PlayFromGameFile) ?
9947         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9948     if(appData.noGUI) return;
9949     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9950         if (!instant) {
9951             if (forwardMostMove == currentMove + 1) {
9952                 AnimateMove(boards[forwardMostMove - 1],
9953                             fromX, fromY, toX, toY);
9954             }
9955         }
9956         currentMove = forwardMostMove;
9957     }
9958
9959     if (instant) return;
9960
9961     DisplayMove(currentMove - 1);
9962     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9963             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
9964                 SetHighlights(fromX, fromY, toX, toY);
9965             }
9966     }
9967     DrawPosition(FALSE, boards[currentMove]);
9968     DisplayBothClocks();
9969     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9970 }
9971
9972 void
9973 SendEgtPath (ChessProgramState *cps)
9974 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9975         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9976
9977         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9978
9979         while(*p) {
9980             char c, *q = name+1, *r, *s;
9981
9982             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9983             while(*p && *p != ',') *q++ = *p++;
9984             *q++ = ':'; *q = 0;
9985             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9986                 strcmp(name, ",nalimov:") == 0 ) {
9987                 // take nalimov path from the menu-changeable option first, if it is defined
9988               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9989                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9990             } else
9991             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9992                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9993                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9994                 s = r = StrStr(s, ":") + 1; // beginning of path info
9995                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9996                 c = *r; *r = 0;             // temporarily null-terminate path info
9997                     *--q = 0;               // strip of trailig ':' from name
9998                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9999                 *r = c;
10000                 SendToProgram(buf,cps);     // send egtbpath command for this format
10001             }
10002             if(*p == ',') p++; // read away comma to position for next format name
10003         }
10004 }
10005
10006 static int
10007 NonStandardBoardSize ()
10008 {
10009       /* [HGM] Awkward testing. Should really be a table */
10010       int overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
10011       if( gameInfo.variant == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10012       if( gameInfo.variant == VariantXiangqi )
10013            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
10014       if( gameInfo.variant == VariantShogi )
10015            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
10016       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
10017            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
10018       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
10019           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
10020            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
10021       if( gameInfo.variant == VariantCourier )
10022            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
10023       if( gameInfo.variant == VariantSuper )
10024            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
10025       if( gameInfo.variant == VariantGreat )
10026            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
10027       if( gameInfo.variant == VariantSChess )
10028            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
10029       if( gameInfo.variant == VariantGrand )
10030            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
10031       return overruled;
10032 }
10033
10034 void
10035 InitChessProgram (ChessProgramState *cps, int setup)
10036 /* setup needed to setup FRC opening position */
10037 {
10038     char buf[MSG_SIZ], b[MSG_SIZ];
10039     if (appData.noChessProgram) return;
10040     hintRequested = FALSE;
10041     bookRequested = FALSE;
10042
10043     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10044     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10045     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10046     if(cps->memSize) { /* [HGM] memory */
10047       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10048         SendToProgram(buf, cps);
10049     }
10050     SendEgtPath(cps); /* [HGM] EGT */
10051     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10052       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10053         SendToProgram(buf, cps);
10054     }
10055
10056     SendToProgram(cps->initString, cps);
10057     if (gameInfo.variant != VariantNormal &&
10058         gameInfo.variant != VariantLoadable
10059         /* [HGM] also send variant if board size non-standard */
10060         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
10061                                             ) {
10062       char *v = VariantName(gameInfo.variant);
10063       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
10064         /* [HGM] in protocol 1 we have to assume all variants valid */
10065         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
10066         DisplayFatalError(buf, 0, 1);
10067         return;
10068       }
10069
10070       if(NonStandardBoardSize()) { /* [HGM] make prefix for non-standard board size. */
10071         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
10072                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
10073            /* [HGM] varsize: try first if this defiant size variant is specifically known */
10074            if(StrStr(cps->variants, b) == NULL) {
10075                // specific sized variant not known, check if general sizing allowed
10076                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
10077                    if(StrStr(cps->variants, "boardsize") == NULL) {
10078                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10079                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
10080                        DisplayFatalError(buf, 0, 1);
10081                        return;
10082                    }
10083                    /* [HGM] here we really should compare with the maximum supported board size */
10084                }
10085            }
10086       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
10087       snprintf(buf, MSG_SIZ, "variant %s\n", b);
10088       SendToProgram(buf, cps);
10089     }
10090     currentlyInitializedVariant = gameInfo.variant;
10091
10092     /* [HGM] send opening position in FRC to first engine */
10093     if(setup) {
10094           SendToProgram("force\n", cps);
10095           SendBoard(cps, 0);
10096           /* engine is now in force mode! Set flag to wake it up after first move. */
10097           setboardSpoiledMachineBlack = 1;
10098     }
10099
10100     if (cps->sendICS) {
10101       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10102       SendToProgram(buf, cps);
10103     }
10104     cps->maybeThinking = FALSE;
10105     cps->offeredDraw = 0;
10106     if (!appData.icsActive) {
10107         SendTimeControl(cps, movesPerSession, timeControl,
10108                         timeIncrement, appData.searchDepth,
10109                         searchTime);
10110     }
10111     if (appData.showThinking
10112         // [HGM] thinking: four options require thinking output to be sent
10113         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10114                                 ) {
10115         SendToProgram("post\n", cps);
10116     }
10117     SendToProgram("hard\n", cps);
10118     if (!appData.ponderNextMove) {
10119         /* Warning: "easy" is a toggle in GNU Chess, so don't send
10120            it without being sure what state we are in first.  "hard"
10121            is not a toggle, so that one is OK.
10122          */
10123         SendToProgram("easy\n", cps);
10124     }
10125     if (cps->usePing) {
10126       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
10127       SendToProgram(buf, cps);
10128     }
10129     cps->initDone = TRUE;
10130     ClearEngineOutputPane(cps == &second);
10131 }
10132
10133
10134 void
10135 ResendOptions (ChessProgramState *cps)
10136 { // send the stored value of the options
10137   int i;
10138   char buf[MSG_SIZ];
10139   Option *opt = cps->option;
10140   for(i=0; i<cps->nrOptions; i++, opt++) {
10141       switch(opt->type) {
10142         case Spin:
10143         case Slider:
10144         case CheckBox:
10145             snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10146           break;
10147         case ComboBox:
10148           snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10149           break;
10150         default:
10151             snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10152           break;
10153         case Button:
10154         case SaveButton:
10155           continue;
10156       }
10157       SendToProgram(buf, cps);
10158   }
10159 }
10160
10161 void
10162 StartChessProgram (ChessProgramState *cps)
10163 {
10164     char buf[MSG_SIZ];
10165     int err;
10166
10167     if (appData.noChessProgram) return;
10168     cps->initDone = FALSE;
10169
10170     if (strcmp(cps->host, "localhost") == 0) {
10171         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10172     } else if (*appData.remoteShell == NULLCHAR) {
10173         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10174     } else {
10175         if (*appData.remoteUser == NULLCHAR) {
10176           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10177                     cps->program);
10178         } else {
10179           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10180                     cps->host, appData.remoteUser, cps->program);
10181         }
10182         err = StartChildProcess(buf, "", &cps->pr);
10183     }
10184
10185     if (err != 0) {
10186       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10187         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10188         if(cps != &first) return;
10189         appData.noChessProgram = TRUE;
10190         ThawUI();
10191         SetNCPMode();
10192 //      DisplayFatalError(buf, err, 1);
10193 //      cps->pr = NoProc;
10194 //      cps->isr = NULL;
10195         return;
10196     }
10197
10198     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10199     if (cps->protocolVersion > 1) {
10200       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10201       if(!cps->reload) { // do not clear options when reloading because of -xreuse
10202         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10203         cps->comboCnt = 0;  //                and values of combo boxes
10204       }
10205       SendToProgram(buf, cps);
10206       if(cps->reload) ResendOptions(cps);
10207     } else {
10208       SendToProgram("xboard\n", cps);
10209     }
10210 }
10211
10212 void
10213 TwoMachinesEventIfReady P((void))
10214 {
10215   static int curMess = 0;
10216   if (first.lastPing != first.lastPong) {
10217     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10218     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10219     return;
10220   }
10221   if (second.lastPing != second.lastPong) {
10222     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10223     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10224     return;
10225   }
10226   DisplayMessage("", ""); curMess = 0;
10227   TwoMachinesEvent();
10228 }
10229
10230 char *
10231 MakeName (char *template)
10232 {
10233     time_t clock;
10234     struct tm *tm;
10235     static char buf[MSG_SIZ];
10236     char *p = buf;
10237     int i;
10238
10239     clock = time((time_t *)NULL);
10240     tm = localtime(&clock);
10241
10242     while(*p++ = *template++) if(p[-1] == '%') {
10243         switch(*template++) {
10244           case 0:   *p = 0; return buf;
10245           case 'Y': i = tm->tm_year+1900; break;
10246           case 'y': i = tm->tm_year-100; break;
10247           case 'M': i = tm->tm_mon+1; break;
10248           case 'd': i = tm->tm_mday; break;
10249           case 'h': i = tm->tm_hour; break;
10250           case 'm': i = tm->tm_min; break;
10251           case 's': i = tm->tm_sec; break;
10252           default:  i = 0;
10253         }
10254         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10255     }
10256     return buf;
10257 }
10258
10259 int
10260 CountPlayers (char *p)
10261 {
10262     int n = 0;
10263     while(p = strchr(p, '\n')) p++, n++; // count participants
10264     return n;
10265 }
10266
10267 FILE *
10268 WriteTourneyFile (char *results, FILE *f)
10269 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10270     if(f == NULL) f = fopen(appData.tourneyFile, "w");
10271     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10272         // create a file with tournament description
10273         fprintf(f, "-participants {%s}\n", appData.participants);
10274         fprintf(f, "-seedBase %d\n", appData.seedBase);
10275         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10276         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10277         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10278         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10279         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10280         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10281         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10282         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10283         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10284         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10285         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10286         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10287         fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
10288         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10289         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10290         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10291         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10292         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10293         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10294         fprintf(f, "-smpCores %d\n", appData.smpCores);
10295         if(searchTime > 0)
10296                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10297         else {
10298                 fprintf(f, "-mps %d\n", appData.movesPerSession);
10299                 fprintf(f, "-tc %s\n", appData.timeControl);
10300                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10301         }
10302         fprintf(f, "-results \"%s\"\n", results);
10303     }
10304     return f;
10305 }
10306
10307 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10308
10309 void
10310 Substitute (char *participants, int expunge)
10311 {
10312     int i, changed, changes=0, nPlayers=0;
10313     char *p, *q, *r, buf[MSG_SIZ];
10314     if(participants == NULL) return;
10315     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10316     r = p = participants; q = appData.participants;
10317     while(*p && *p == *q) {
10318         if(*p == '\n') r = p+1, nPlayers++;
10319         p++; q++;
10320     }
10321     if(*p) { // difference
10322         while(*p && *p++ != '\n');
10323         while(*q && *q++ != '\n');
10324       changed = nPlayers;
10325         changes = 1 + (strcmp(p, q) != 0);
10326     }
10327     if(changes == 1) { // a single engine mnemonic was changed
10328         q = r; while(*q) nPlayers += (*q++ == '\n');
10329         p = buf; while(*r && (*p = *r++) != '\n') p++;
10330         *p = NULLCHAR;
10331         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10332         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10333         if(mnemonic[i]) { // The substitute is valid
10334             FILE *f;
10335             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10336                 flock(fileno(f), LOCK_EX);
10337                 ParseArgsFromFile(f);
10338                 fseek(f, 0, SEEK_SET);
10339                 FREE(appData.participants); appData.participants = participants;
10340                 if(expunge) { // erase results of replaced engine
10341                     int len = strlen(appData.results), w, b, dummy;
10342                     for(i=0; i<len; i++) {
10343                         Pairing(i, nPlayers, &w, &b, &dummy);
10344                         if((w == changed || b == changed) && appData.results[i] == '*') {
10345                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10346                             fclose(f);
10347                             return;
10348                         }
10349                     }
10350                     for(i=0; i<len; i++) {
10351                         Pairing(i, nPlayers, &w, &b, &dummy);
10352                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10353                     }
10354                 }
10355                 WriteTourneyFile(appData.results, f);
10356                 fclose(f); // release lock
10357                 return;
10358             }
10359         } else DisplayError(_("No engine with the name you gave is installed"), 0);
10360     }
10361     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10362     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
10363     free(participants);
10364     return;
10365 }
10366
10367 int
10368 CheckPlayers (char *participants)
10369 {
10370         int i;
10371         char buf[MSG_SIZ], *p;
10372         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10373         while(p = strchr(participants, '\n')) {
10374             *p = NULLCHAR;
10375             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10376             if(!mnemonic[i]) {
10377                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10378                 *p = '\n';
10379                 DisplayError(buf, 0);
10380                 return 1;
10381             }
10382             *p = '\n';
10383             participants = p + 1;
10384         }
10385         return 0;
10386 }
10387
10388 int
10389 CreateTourney (char *name)
10390 {
10391         FILE *f;
10392         if(matchMode && strcmp(name, appData.tourneyFile)) {
10393              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10394         }
10395         if(name[0] == NULLCHAR) {
10396             if(appData.participants[0])
10397                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10398             return 0;
10399         }
10400         f = fopen(name, "r");
10401         if(f) { // file exists
10402             ASSIGN(appData.tourneyFile, name);
10403             ParseArgsFromFile(f); // parse it
10404         } else {
10405             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10406             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10407                 DisplayError(_("Not enough participants"), 0);
10408                 return 0;
10409             }
10410             if(CheckPlayers(appData.participants)) return 0;
10411             ASSIGN(appData.tourneyFile, name);
10412             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10413             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10414         }
10415         fclose(f);
10416         appData.noChessProgram = FALSE;
10417         appData.clockMode = TRUE;
10418         SetGNUMode();
10419         return 1;
10420 }
10421
10422 int
10423 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10424 {
10425     char buf[MSG_SIZ], *p, *q;
10426     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10427     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10428     skip = !all && group[0]; // if group requested, we start in skip mode
10429     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10430         p = names; q = buf; header = 0;
10431         while(*p && *p != '\n') *q++ = *p++;
10432         *q = 0;
10433         if(*p == '\n') p++;
10434         if(buf[0] == '#') {
10435             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10436             depth++; // we must be entering a new group
10437             if(all) continue; // suppress printing group headers when complete list requested
10438             header = 1;
10439             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10440         }
10441         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10442         if(engineList[i]) free(engineList[i]);
10443         engineList[i] = strdup(buf);
10444         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10445         if(engineMnemonic[i]) free(engineMnemonic[i]);
10446         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10447             strcat(buf, " (");
10448             sscanf(q + 8, "%s", buf + strlen(buf));
10449             strcat(buf, ")");
10450         }
10451         engineMnemonic[i] = strdup(buf);
10452         i++;
10453     }
10454     engineList[i] = engineMnemonic[i] = NULL;
10455     return i;
10456 }
10457
10458 // following implemented as macro to avoid type limitations
10459 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10460
10461 void
10462 SwapEngines (int n)
10463 {   // swap settings for first engine and other engine (so far only some selected options)
10464     int h;
10465     char *p;
10466     if(n == 0) return;
10467     SWAP(directory, p)
10468     SWAP(chessProgram, p)
10469     SWAP(isUCI, h)
10470     SWAP(hasOwnBookUCI, h)
10471     SWAP(protocolVersion, h)
10472     SWAP(reuse, h)
10473     SWAP(scoreIsAbsolute, h)
10474     SWAP(timeOdds, h)
10475     SWAP(logo, p)
10476     SWAP(pgnName, p)
10477     SWAP(pvSAN, h)
10478     SWAP(engOptions, p)
10479     SWAP(engInitString, p)
10480     SWAP(computerString, p)
10481     SWAP(features, p)
10482     SWAP(fenOverride, p)
10483     SWAP(NPS, h)
10484     SWAP(accumulateTC, h)
10485     SWAP(host, p)
10486 }
10487
10488 int
10489 GetEngineLine (char *s, int n)
10490 {
10491     int i;
10492     char buf[MSG_SIZ];
10493     extern char *icsNames;
10494     if(!s || !*s) return 0;
10495     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
10496     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
10497     if(!mnemonic[i]) return 0;
10498     if(n == 11) return 1; // just testing if there was a match
10499     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
10500     if(n == 1) SwapEngines(n);
10501     ParseArgsFromString(buf);
10502     if(n == 1) SwapEngines(n);
10503     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
10504         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
10505         ParseArgsFromString(buf);
10506     }
10507     return 1;
10508 }
10509
10510 int
10511 SetPlayer (int player, char *p)
10512 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
10513     int i;
10514     char buf[MSG_SIZ], *engineName;
10515     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10516     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10517     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10518     if(mnemonic[i]) {
10519         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10520         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10521         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10522         ParseArgsFromString(buf);
10523     } else { // no engine with this nickname is installed!
10524         snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
10525         ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
10526         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10527         ModeHighlight();
10528         DisplayError(buf, 0);
10529         return 0;
10530     }
10531     free(engineName);
10532     return i;
10533 }
10534
10535 char *recentEngines;
10536
10537 void
10538 RecentEngineEvent (int nr)
10539 {
10540     int n;
10541 //    SwapEngines(1); // bump first to second
10542 //    ReplaceEngine(&second, 1); // and load it there
10543     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10544     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10545     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10546         ReplaceEngine(&first, 0);
10547         FloatToFront(&appData.recentEngineList, command[n]);
10548     }
10549 }
10550
10551 int
10552 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10553 {   // determine players from game number
10554     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10555
10556     if(appData.tourneyType == 0) {
10557         roundsPerCycle = (nPlayers - 1) | 1;
10558         pairingsPerRound = nPlayers / 2;
10559     } else if(appData.tourneyType > 0) {
10560         roundsPerCycle = nPlayers - appData.tourneyType;
10561         pairingsPerRound = appData.tourneyType;
10562     }
10563     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10564     gamesPerCycle = gamesPerRound * roundsPerCycle;
10565     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10566     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10567     curRound = nr / gamesPerRound; nr %= gamesPerRound;
10568     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10569     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10570     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10571
10572     if(appData.cycleSync) *syncInterval = gamesPerCycle;
10573     if(appData.roundSync) *syncInterval = gamesPerRound;
10574
10575     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10576
10577     if(appData.tourneyType == 0) {
10578         if(curPairing == (nPlayers-1)/2 ) {
10579             *whitePlayer = curRound;
10580             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10581         } else {
10582             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10583             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10584             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10585             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10586         }
10587     } else if(appData.tourneyType > 1) {
10588         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10589         *whitePlayer = curRound + appData.tourneyType;
10590     } else if(appData.tourneyType > 0) {
10591         *whitePlayer = curPairing;
10592         *blackPlayer = curRound + appData.tourneyType;
10593     }
10594
10595     // take care of white/black alternation per round.
10596     // For cycles and games this is already taken care of by default, derived from matchGame!
10597     return curRound & 1;
10598 }
10599
10600 int
10601 NextTourneyGame (int nr, int *swapColors)
10602 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10603     char *p, *q;
10604     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
10605     FILE *tf;
10606     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10607     tf = fopen(appData.tourneyFile, "r");
10608     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10609     ParseArgsFromFile(tf); fclose(tf);
10610     InitTimeControls(); // TC might be altered from tourney file
10611
10612     nPlayers = CountPlayers(appData.participants); // count participants
10613     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10614     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10615
10616     if(syncInterval) {
10617         p = q = appData.results;
10618         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10619         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10620             DisplayMessage(_("Waiting for other game(s)"),"");
10621             waitingForGame = TRUE;
10622             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10623             return 0;
10624         }
10625         waitingForGame = FALSE;
10626     }
10627
10628     if(appData.tourneyType < 0) {
10629         if(nr>=0 && !pairingReceived) {
10630             char buf[1<<16];
10631             if(pairing.pr == NoProc) {
10632                 if(!appData.pairingEngine[0]) {
10633                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10634                     return 0;
10635                 }
10636                 StartChessProgram(&pairing); // starts the pairing engine
10637             }
10638             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10639             SendToProgram(buf, &pairing);
10640             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10641             SendToProgram(buf, &pairing);
10642             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10643         }
10644         pairingReceived = 0;                              // ... so we continue here
10645         *swapColors = 0;
10646         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10647         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10648         matchGame = 1; roundNr = nr / syncInterval + 1;
10649     }
10650
10651     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10652
10653     // redefine engines, engine dir, etc.
10654     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10655     if(first.pr == NoProc) {
10656       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
10657       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10658     }
10659     if(second.pr == NoProc) {
10660       SwapEngines(1);
10661       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
10662       SwapEngines(1);         // and make that valid for second engine by swapping
10663       InitEngine(&second, 1);
10664     }
10665     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10666     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10667     return OK;
10668 }
10669
10670 void
10671 NextMatchGame ()
10672 {   // performs game initialization that does not invoke engines, and then tries to start the game
10673     int res, firstWhite, swapColors = 0;
10674     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10675     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
10676         char buf[MSG_SIZ];
10677         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
10678         if(strcmp(buf, currentDebugFile)) { // name has changed
10679             FILE *f = fopen(buf, "w");
10680             if(f) { // if opening the new file failed, just keep using the old one
10681                 ASSIGN(currentDebugFile, buf);
10682                 fclose(debugFP);
10683                 debugFP = f;
10684             }
10685             if(appData.serverFileName) {
10686                 if(serverFP) fclose(serverFP);
10687                 serverFP = fopen(appData.serverFileName, "w");
10688                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
10689                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
10690             }
10691         }
10692     }
10693     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10694     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10695     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
10696     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10697     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10698     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10699     Reset(FALSE, first.pr != NoProc);
10700     res = LoadGameOrPosition(matchGame); // setup game
10701     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10702     if(!res) return; // abort when bad game/pos file
10703     TwoMachinesEvent();
10704 }
10705
10706 void
10707 UserAdjudicationEvent (int result)
10708 {
10709     ChessMove gameResult = GameIsDrawn;
10710
10711     if( result > 0 ) {
10712         gameResult = WhiteWins;
10713     }
10714     else if( result < 0 ) {
10715         gameResult = BlackWins;
10716     }
10717
10718     if( gameMode == TwoMachinesPlay ) {
10719         GameEnds( gameResult, "User adjudication", GE_XBOARD );
10720     }
10721 }
10722
10723
10724 // [HGM] save: calculate checksum of game to make games easily identifiable
10725 int
10726 StringCheckSum (char *s)
10727 {
10728         int i = 0;
10729         if(s==NULL) return 0;
10730         while(*s) i = i*259 + *s++;
10731         return i;
10732 }
10733
10734 int
10735 GameCheckSum ()
10736 {
10737         int i, sum=0;
10738         for(i=backwardMostMove; i<forwardMostMove; i++) {
10739                 sum += pvInfoList[i].depth;
10740                 sum += StringCheckSum(parseList[i]);
10741                 sum += StringCheckSum(commentList[i]);
10742                 sum *= 261;
10743         }
10744         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10745         return sum + StringCheckSum(commentList[i]);
10746 } // end of save patch
10747
10748 void
10749 GameEnds (ChessMove result, char *resultDetails, int whosays)
10750 {
10751     GameMode nextGameMode;
10752     int isIcsGame;
10753     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10754
10755     if(endingGame) return; /* [HGM] crash: forbid recursion */
10756     endingGame = 1;
10757     if(twoBoards) { // [HGM] dual: switch back to one board
10758         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10759         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10760     }
10761     if (appData.debugMode) {
10762       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10763               result, resultDetails ? resultDetails : "(null)", whosays);
10764     }
10765
10766     fromX = fromY = -1; // [HGM] abort any move the user is entering.
10767
10768     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
10769
10770     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10771         /* If we are playing on ICS, the server decides when the
10772            game is over, but the engine can offer to draw, claim
10773            a draw, or resign.
10774          */
10775 #if ZIPPY
10776         if (appData.zippyPlay && first.initDone) {
10777             if (result == GameIsDrawn) {
10778                 /* In case draw still needs to be claimed */
10779                 SendToICS(ics_prefix);
10780                 SendToICS("draw\n");
10781             } else if (StrCaseStr(resultDetails, "resign")) {
10782                 SendToICS(ics_prefix);
10783                 SendToICS("resign\n");
10784             }
10785         }
10786 #endif
10787         endingGame = 0; /* [HGM] crash */
10788         return;
10789     }
10790
10791     /* If we're loading the game from a file, stop */
10792     if (whosays == GE_FILE) {
10793       (void) StopLoadGameTimer();
10794       gameFileFP = NULL;
10795     }
10796
10797     /* Cancel draw offers */
10798     first.offeredDraw = second.offeredDraw = 0;
10799
10800     /* If this is an ICS game, only ICS can really say it's done;
10801        if not, anyone can. */
10802     isIcsGame = (gameMode == IcsPlayingWhite ||
10803                  gameMode == IcsPlayingBlack ||
10804                  gameMode == IcsObserving    ||
10805                  gameMode == IcsExamining);
10806
10807     if (!isIcsGame || whosays == GE_ICS) {
10808         /* OK -- not an ICS game, or ICS said it was done */
10809         StopClocks();
10810         if (!isIcsGame && !appData.noChessProgram)
10811           SetUserThinkingEnables();
10812
10813         /* [HGM] if a machine claims the game end we verify this claim */
10814         if(gameMode == TwoMachinesPlay && appData.testClaims) {
10815             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10816                 char claimer;
10817                 ChessMove trueResult = (ChessMove) -1;
10818
10819                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
10820                                             first.twoMachinesColor[0] :
10821                                             second.twoMachinesColor[0] ;
10822
10823                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10824                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10825                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10826                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10827                 } else
10828                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10829                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10830                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10831                 } else
10832                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10833                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10834                 }
10835
10836                 // now verify win claims, but not in drop games, as we don't understand those yet
10837                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10838                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10839                     (result == WhiteWins && claimer == 'w' ||
10840                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
10841                       if (appData.debugMode) {
10842                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
10843                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10844                       }
10845                       if(result != trueResult) {
10846                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10847                               result = claimer == 'w' ? BlackWins : WhiteWins;
10848                               resultDetails = buf;
10849                       }
10850                 } else
10851                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10852                     && (forwardMostMove <= backwardMostMove ||
10853                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10854                         (claimer=='b')==(forwardMostMove&1))
10855                                                                                   ) {
10856                       /* [HGM] verify: draws that were not flagged are false claims */
10857                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10858                       result = claimer == 'w' ? BlackWins : WhiteWins;
10859                       resultDetails = buf;
10860                 }
10861                 /* (Claiming a loss is accepted no questions asked!) */
10862             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
10863                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
10864                 result = GameUnfinished;
10865                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
10866             }
10867             /* [HGM] bare: don't allow bare King to win */
10868             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10869                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10870                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10871                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10872                && result != GameIsDrawn)
10873             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10874                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10875                         int p = (signed char)boards[forwardMostMove][i][j] - color;
10876                         if(p >= 0 && p <= (int)WhiteKing) k++;
10877                 }
10878                 if (appData.debugMode) {
10879                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10880                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10881                 }
10882                 if(k <= 1) {
10883                         result = GameIsDrawn;
10884                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10885                         resultDetails = buf;
10886                 }
10887             }
10888         }
10889
10890
10891         if(serverMoves != NULL && !loadFlag) { char c = '=';
10892             if(result==WhiteWins) c = '+';
10893             if(result==BlackWins) c = '-';
10894             if(resultDetails != NULL)
10895                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
10896         }
10897         if (resultDetails != NULL) {
10898             gameInfo.result = result;
10899             gameInfo.resultDetails = StrSave(resultDetails);
10900
10901             /* display last move only if game was not loaded from file */
10902             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10903                 DisplayMove(currentMove - 1);
10904
10905             if (forwardMostMove != 0) {
10906                 if (gameMode != PlayFromGameFile && gameMode != EditGame
10907                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10908                                                                 ) {
10909                     if (*appData.saveGameFile != NULLCHAR) {
10910                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
10911                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
10912                         else
10913                         SaveGameToFile(appData.saveGameFile, TRUE);
10914                     } else if (appData.autoSaveGames) {
10915                         if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
10916                     }
10917                     if (*appData.savePositionFile != NULLCHAR) {
10918                         SavePositionToFile(appData.savePositionFile);
10919                     }
10920                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
10921                 }
10922             }
10923
10924             /* Tell program how game ended in case it is learning */
10925             /* [HGM] Moved this to after saving the PGN, just in case */
10926             /* engine died and we got here through time loss. In that */
10927             /* case we will get a fatal error writing the pipe, which */
10928             /* would otherwise lose us the PGN.                       */
10929             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
10930             /* output during GameEnds should never be fatal anymore   */
10931             if (gameMode == MachinePlaysWhite ||
10932                 gameMode == MachinePlaysBlack ||
10933                 gameMode == TwoMachinesPlay ||
10934                 gameMode == IcsPlayingWhite ||
10935                 gameMode == IcsPlayingBlack ||
10936                 gameMode == BeginningOfGame) {
10937                 char buf[MSG_SIZ];
10938                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10939                         resultDetails);
10940                 if (first.pr != NoProc) {
10941                     SendToProgram(buf, &first);
10942                 }
10943                 if (second.pr != NoProc &&
10944                     gameMode == TwoMachinesPlay) {
10945                     SendToProgram(buf, &second);
10946                 }
10947             }
10948         }
10949
10950         if (appData.icsActive) {
10951             if (appData.quietPlay &&
10952                 (gameMode == IcsPlayingWhite ||
10953                  gameMode == IcsPlayingBlack)) {
10954                 SendToICS(ics_prefix);
10955                 SendToICS("set shout 1\n");
10956             }
10957             nextGameMode = IcsIdle;
10958             ics_user_moved = FALSE;
10959             /* clean up premove.  It's ugly when the game has ended and the
10960              * premove highlights are still on the board.
10961              */
10962             if (gotPremove) {
10963               gotPremove = FALSE;
10964               ClearPremoveHighlights();
10965               DrawPosition(FALSE, boards[currentMove]);
10966             }
10967             if (whosays == GE_ICS) {
10968                 switch (result) {
10969                 case WhiteWins:
10970                     if (gameMode == IcsPlayingWhite)
10971                         PlayIcsWinSound();
10972                     else if(gameMode == IcsPlayingBlack)
10973                         PlayIcsLossSound();
10974                     break;
10975                 case BlackWins:
10976                     if (gameMode == IcsPlayingBlack)
10977                         PlayIcsWinSound();
10978                     else if(gameMode == IcsPlayingWhite)
10979                         PlayIcsLossSound();
10980                     break;
10981                 case GameIsDrawn:
10982                     PlayIcsDrawSound();
10983                     break;
10984                 default:
10985                     PlayIcsUnfinishedSound();
10986                 }
10987             }
10988             if(appData.quitNext) { ExitEvent(0); return; }
10989         } else if (gameMode == EditGame ||
10990                    gameMode == PlayFromGameFile ||
10991                    gameMode == AnalyzeMode ||
10992                    gameMode == AnalyzeFile) {
10993             nextGameMode = gameMode;
10994         } else {
10995             nextGameMode = EndOfGame;
10996         }
10997         pausing = FALSE;
10998         ModeHighlight();
10999     } else {
11000         nextGameMode = gameMode;
11001     }
11002
11003     if (appData.noChessProgram) {
11004         gameMode = nextGameMode;
11005         ModeHighlight();
11006         endingGame = 0; /* [HGM] crash */
11007         return;
11008     }
11009
11010     if (first.reuse) {
11011         /* Put first chess program into idle state */
11012         if (first.pr != NoProc &&
11013             (gameMode == MachinePlaysWhite ||
11014              gameMode == MachinePlaysBlack ||
11015              gameMode == TwoMachinesPlay ||
11016              gameMode == IcsPlayingWhite ||
11017              gameMode == IcsPlayingBlack ||
11018              gameMode == BeginningOfGame)) {
11019             SendToProgram("force\n", &first);
11020             if (first.usePing) {
11021               char buf[MSG_SIZ];
11022               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11023               SendToProgram(buf, &first);
11024             }
11025         }
11026     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11027         /* Kill off first chess program */
11028         if (first.isr != NULL)
11029           RemoveInputSource(first.isr);
11030         first.isr = NULL;
11031
11032         if (first.pr != NoProc) {
11033             ExitAnalyzeMode();
11034             DoSleep( appData.delayBeforeQuit );
11035             SendToProgram("quit\n", &first);
11036             DoSleep( appData.delayAfterQuit );
11037             DestroyChildProcess(first.pr, first.useSigterm);
11038             first.reload = TRUE;
11039         }
11040         first.pr = NoProc;
11041     }
11042     if (second.reuse) {
11043         /* Put second chess program into idle state */
11044         if (second.pr != NoProc &&
11045             gameMode == TwoMachinesPlay) {
11046             SendToProgram("force\n", &second);
11047             if (second.usePing) {
11048               char buf[MSG_SIZ];
11049               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11050               SendToProgram(buf, &second);
11051             }
11052         }
11053     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11054         /* Kill off second chess program */
11055         if (second.isr != NULL)
11056           RemoveInputSource(second.isr);
11057         second.isr = NULL;
11058
11059         if (second.pr != NoProc) {
11060             DoSleep( appData.delayBeforeQuit );
11061             SendToProgram("quit\n", &second);
11062             DoSleep( appData.delayAfterQuit );
11063             DestroyChildProcess(second.pr, second.useSigterm);
11064             second.reload = TRUE;
11065         }
11066         second.pr = NoProc;
11067     }
11068
11069     if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11070         char resChar = '=';
11071         switch (result) {
11072         case WhiteWins:
11073           resChar = '+';
11074           if (first.twoMachinesColor[0] == 'w') {
11075             first.matchWins++;
11076           } else {
11077             second.matchWins++;
11078           }
11079           break;
11080         case BlackWins:
11081           resChar = '-';
11082           if (first.twoMachinesColor[0] == 'b') {
11083             first.matchWins++;
11084           } else {
11085             second.matchWins++;
11086           }
11087           break;
11088         case GameUnfinished:
11089           resChar = ' ';
11090         default:
11091           break;
11092         }
11093
11094         if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11095         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11096             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11097             ReserveGame(nextGame, resChar); // sets nextGame
11098             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11099             else ranking = strdup("busy"); //suppress popup when aborted but not finished
11100         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11101
11102         if (nextGame <= appData.matchGames && !abortMatch) {
11103             gameMode = nextGameMode;
11104             matchGame = nextGame; // this will be overruled in tourney mode!
11105             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11106             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11107             endingGame = 0; /* [HGM] crash */
11108             return;
11109         } else {
11110             gameMode = nextGameMode;
11111             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11112                      first.tidy, second.tidy,
11113                      first.matchWins, second.matchWins,
11114                      appData.matchGames - (first.matchWins + second.matchWins));
11115             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11116             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11117             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11118             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11119                 first.twoMachinesColor = "black\n";
11120                 second.twoMachinesColor = "white\n";
11121             } else {
11122                 first.twoMachinesColor = "white\n";
11123                 second.twoMachinesColor = "black\n";
11124             }
11125         }
11126     }
11127     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11128         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11129       ExitAnalyzeMode();
11130     gameMode = nextGameMode;
11131     ModeHighlight();
11132     endingGame = 0;  /* [HGM] crash */
11133     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11134         if(matchMode == TRUE) { // match through command line: exit with or without popup
11135             if(ranking) {
11136                 ToNrEvent(forwardMostMove);
11137                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11138                 else ExitEvent(0);
11139             } else DisplayFatalError(buf, 0, 0);
11140         } else { // match through menu; just stop, with or without popup
11141             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11142             ModeHighlight();
11143             if(ranking){
11144                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11145             } else DisplayNote(buf);
11146       }
11147       if(ranking) free(ranking);
11148     }
11149 }
11150
11151 /* Assumes program was just initialized (initString sent).
11152    Leaves program in force mode. */
11153 void
11154 FeedMovesToProgram (ChessProgramState *cps, int upto)
11155 {
11156     int i;
11157
11158     if (appData.debugMode)
11159       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11160               startedFromSetupPosition ? "position and " : "",
11161               backwardMostMove, upto, cps->which);
11162     if(currentlyInitializedVariant != gameInfo.variant) {
11163       char buf[MSG_SIZ];
11164         // [HGM] variantswitch: make engine aware of new variant
11165         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
11166                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11167         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11168         SendToProgram(buf, cps);
11169         currentlyInitializedVariant = gameInfo.variant;
11170     }
11171     SendToProgram("force\n", cps);
11172     if (startedFromSetupPosition) {
11173         SendBoard(cps, backwardMostMove);
11174     if (appData.debugMode) {
11175         fprintf(debugFP, "feedMoves\n");
11176     }
11177     }
11178     for (i = backwardMostMove; i < upto; i++) {
11179         SendMoveToProgram(i, cps);
11180     }
11181 }
11182
11183
11184 int
11185 ResurrectChessProgram ()
11186 {
11187      /* The chess program may have exited.
11188         If so, restart it and feed it all the moves made so far. */
11189     static int doInit = 0;
11190
11191     if (appData.noChessProgram) return 1;
11192
11193     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11194         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11195         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11196         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11197     } else {
11198         if (first.pr != NoProc) return 1;
11199         StartChessProgram(&first);
11200     }
11201     InitChessProgram(&first, FALSE);
11202     FeedMovesToProgram(&first, currentMove);
11203
11204     if (!first.sendTime) {
11205         /* can't tell gnuchess what its clock should read,
11206            so we bow to its notion. */
11207         ResetClocks();
11208         timeRemaining[0][currentMove] = whiteTimeRemaining;
11209         timeRemaining[1][currentMove] = blackTimeRemaining;
11210     }
11211
11212     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11213                 appData.icsEngineAnalyze) && first.analysisSupport) {
11214       SendToProgram("analyze\n", &first);
11215       first.analyzing = TRUE;
11216     }
11217     return 1;
11218 }
11219
11220 /*
11221  * Button procedures
11222  */
11223 void
11224 Reset (int redraw, int init)
11225 {
11226     int i;
11227
11228     if (appData.debugMode) {
11229         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11230                 redraw, init, gameMode);
11231     }
11232     CleanupTail(); // [HGM] vari: delete any stored variations
11233     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11234     pausing = pauseExamInvalid = FALSE;
11235     startedFromSetupPosition = blackPlaysFirst = FALSE;
11236     firstMove = TRUE;
11237     whiteFlag = blackFlag = FALSE;
11238     userOfferedDraw = FALSE;
11239     hintRequested = bookRequested = FALSE;
11240     first.maybeThinking = FALSE;
11241     second.maybeThinking = FALSE;
11242     first.bookSuspend = FALSE; // [HGM] book
11243     second.bookSuspend = FALSE;
11244     thinkOutput[0] = NULLCHAR;
11245     lastHint[0] = NULLCHAR;
11246     ClearGameInfo(&gameInfo);
11247     gameInfo.variant = StringToVariant(appData.variant);
11248     if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) gameInfo.variant = VariantUnknown;
11249     ics_user_moved = ics_clock_paused = FALSE;
11250     ics_getting_history = H_FALSE;
11251     ics_gamenum = -1;
11252     white_holding[0] = black_holding[0] = NULLCHAR;
11253     ClearProgramStats();
11254     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11255
11256     ResetFrontEnd();
11257     ClearHighlights();
11258     flipView = appData.flipView;
11259     ClearPremoveHighlights();
11260     gotPremove = FALSE;
11261     alarmSounded = FALSE;
11262
11263     GameEnds(EndOfFile, NULL, GE_PLAYER);
11264     if(appData.serverMovesName != NULL) {
11265         /* [HGM] prepare to make moves file for broadcasting */
11266         clock_t t = clock();
11267         if(serverMoves != NULL) fclose(serverMoves);
11268         serverMoves = fopen(appData.serverMovesName, "r");
11269         if(serverMoves != NULL) {
11270             fclose(serverMoves);
11271             /* delay 15 sec before overwriting, so all clients can see end */
11272             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11273         }
11274         serverMoves = fopen(appData.serverMovesName, "w");
11275     }
11276
11277     ExitAnalyzeMode();
11278     gameMode = BeginningOfGame;
11279     ModeHighlight();
11280     if(appData.icsActive) gameInfo.variant = VariantNormal;
11281     currentMove = forwardMostMove = backwardMostMove = 0;
11282     MarkTargetSquares(1);
11283     InitPosition(redraw);
11284     for (i = 0; i < MAX_MOVES; i++) {
11285         if (commentList[i] != NULL) {
11286             free(commentList[i]);
11287             commentList[i] = NULL;
11288         }
11289     }
11290     ResetClocks();
11291     timeRemaining[0][0] = whiteTimeRemaining;
11292     timeRemaining[1][0] = blackTimeRemaining;
11293
11294     if (first.pr == NoProc) {
11295         StartChessProgram(&first);
11296     }
11297     if (init) {
11298             InitChessProgram(&first, startedFromSetupPosition);
11299     }
11300     DisplayTitle("");
11301     DisplayMessage("", "");
11302     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11303     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11304     ClearMap();        // [HGM] exclude: invalidate map
11305 }
11306
11307 void
11308 AutoPlayGameLoop ()
11309 {
11310     for (;;) {
11311         if (!AutoPlayOneMove())
11312           return;
11313         if (matchMode || appData.timeDelay == 0)
11314           continue;
11315         if (appData.timeDelay < 0)
11316           return;
11317         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11318         break;
11319     }
11320 }
11321
11322 void
11323 AnalyzeNextGame()
11324 {
11325     ReloadGame(1); // next game
11326 }
11327
11328 int
11329 AutoPlayOneMove ()
11330 {
11331     int fromX, fromY, toX, toY;
11332
11333     if (appData.debugMode) {
11334       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11335     }
11336
11337     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11338       return FALSE;
11339
11340     if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
11341       pvInfoList[currentMove].depth = programStats.depth;
11342       pvInfoList[currentMove].score = programStats.score;
11343       pvInfoList[currentMove].time  = 0;
11344       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11345       else { // append analysis of final position as comment
11346         char buf[MSG_SIZ];
11347         snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
11348         AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
11349       }
11350       programStats.depth = 0;
11351     }
11352
11353     if (currentMove >= forwardMostMove) {
11354       if(gameMode == AnalyzeFile) {
11355           if(appData.loadGameIndex == -1) {
11356             GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
11357           ScheduleDelayedEvent(AnalyzeNextGame, 10);
11358           } else {
11359           ExitAnalyzeMode(); SendToProgram("force\n", &first);
11360         }
11361       }
11362 //      gameMode = EndOfGame;
11363 //      ModeHighlight();
11364
11365       /* [AS] Clear current move marker at the end of a game */
11366       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11367
11368       return FALSE;
11369     }
11370
11371     toX = moveList[currentMove][2] - AAA;
11372     toY = moveList[currentMove][3] - ONE;
11373
11374     if (moveList[currentMove][1] == '@') {
11375         if (appData.highlightLastMove) {
11376             SetHighlights(-1, -1, toX, toY);
11377         }
11378     } else {
11379         fromX = moveList[currentMove][0] - AAA;
11380         fromY = moveList[currentMove][1] - ONE;
11381
11382         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11383
11384         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11385
11386         if (appData.highlightLastMove) {
11387             SetHighlights(fromX, fromY, toX, toY);
11388         }
11389     }
11390     DisplayMove(currentMove);
11391     SendMoveToProgram(currentMove++, &first);
11392     DisplayBothClocks();
11393     DrawPosition(FALSE, boards[currentMove]);
11394     // [HGM] PV info: always display, routine tests if empty
11395     DisplayComment(currentMove - 1, commentList[currentMove]);
11396     return TRUE;
11397 }
11398
11399
11400 int
11401 LoadGameOneMove (ChessMove readAhead)
11402 {
11403     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11404     char promoChar = NULLCHAR;
11405     ChessMove moveType;
11406     char move[MSG_SIZ];
11407     char *p, *q;
11408
11409     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11410         gameMode != AnalyzeMode && gameMode != Training) {
11411         gameFileFP = NULL;
11412         return FALSE;
11413     }
11414
11415     yyboardindex = forwardMostMove;
11416     if (readAhead != EndOfFile) {
11417       moveType = readAhead;
11418     } else {
11419       if (gameFileFP == NULL)
11420           return FALSE;
11421       moveType = (ChessMove) Myylex();
11422     }
11423
11424     done = FALSE;
11425     switch (moveType) {
11426       case Comment:
11427         if (appData.debugMode)
11428           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11429         p = yy_text;
11430
11431         /* append the comment but don't display it */
11432         AppendComment(currentMove, p, FALSE);
11433         return TRUE;
11434
11435       case WhiteCapturesEnPassant:
11436       case BlackCapturesEnPassant:
11437       case WhitePromotion:
11438       case BlackPromotion:
11439       case WhiteNonPromotion:
11440       case BlackNonPromotion:
11441       case NormalMove:
11442       case WhiteKingSideCastle:
11443       case WhiteQueenSideCastle:
11444       case BlackKingSideCastle:
11445       case BlackQueenSideCastle:
11446       case WhiteKingSideCastleWild:
11447       case WhiteQueenSideCastleWild:
11448       case BlackKingSideCastleWild:
11449       case BlackQueenSideCastleWild:
11450       /* PUSH Fabien */
11451       case WhiteHSideCastleFR:
11452       case WhiteASideCastleFR:
11453       case BlackHSideCastleFR:
11454       case BlackASideCastleFR:
11455       /* POP Fabien */
11456         if (appData.debugMode)
11457           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11458         fromX = currentMoveString[0] - AAA;
11459         fromY = currentMoveString[1] - ONE;
11460         toX = currentMoveString[2] - AAA;
11461         toY = currentMoveString[3] - ONE;
11462         promoChar = currentMoveString[4];
11463         break;
11464
11465       case WhiteDrop:
11466       case BlackDrop:
11467         if (appData.debugMode)
11468           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11469         fromX = moveType == WhiteDrop ?
11470           (int) CharToPiece(ToUpper(currentMoveString[0])) :
11471         (int) CharToPiece(ToLower(currentMoveString[0]));
11472         fromY = DROP_RANK;
11473         toX = currentMoveString[2] - AAA;
11474         toY = currentMoveString[3] - ONE;
11475         break;
11476
11477       case WhiteWins:
11478       case BlackWins:
11479       case GameIsDrawn:
11480       case GameUnfinished:
11481         if (appData.debugMode)
11482           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11483         p = strchr(yy_text, '{');
11484         if (p == NULL) p = strchr(yy_text, '(');
11485         if (p == NULL) {
11486             p = yy_text;
11487             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11488         } else {
11489             q = strchr(p, *p == '{' ? '}' : ')');
11490             if (q != NULL) *q = NULLCHAR;
11491             p++;
11492         }
11493         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11494         GameEnds(moveType, p, GE_FILE);
11495         done = TRUE;
11496         if (cmailMsgLoaded) {
11497             ClearHighlights();
11498             flipView = WhiteOnMove(currentMove);
11499             if (moveType == GameUnfinished) flipView = !flipView;
11500             if (appData.debugMode)
11501               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11502         }
11503         break;
11504
11505       case EndOfFile:
11506         if (appData.debugMode)
11507           fprintf(debugFP, "Parser hit end of file\n");
11508         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11509           case MT_NONE:
11510           case MT_CHECK:
11511             break;
11512           case MT_CHECKMATE:
11513           case MT_STAINMATE:
11514             if (WhiteOnMove(currentMove)) {
11515                 GameEnds(BlackWins, "Black mates", GE_FILE);
11516             } else {
11517                 GameEnds(WhiteWins, "White mates", GE_FILE);
11518             }
11519             break;
11520           case MT_STALEMATE:
11521             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11522             break;
11523         }
11524         done = TRUE;
11525         break;
11526
11527       case MoveNumberOne:
11528         if (lastLoadGameStart == GNUChessGame) {
11529             /* GNUChessGames have numbers, but they aren't move numbers */
11530             if (appData.debugMode)
11531               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11532                       yy_text, (int) moveType);
11533             return LoadGameOneMove(EndOfFile); /* tail recursion */
11534         }
11535         /* else fall thru */
11536
11537       case XBoardGame:
11538       case GNUChessGame:
11539       case PGNTag:
11540         /* Reached start of next game in file */
11541         if (appData.debugMode)
11542           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11543         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11544           case MT_NONE:
11545           case MT_CHECK:
11546             break;
11547           case MT_CHECKMATE:
11548           case MT_STAINMATE:
11549             if (WhiteOnMove(currentMove)) {
11550                 GameEnds(BlackWins, "Black mates", GE_FILE);
11551             } else {
11552                 GameEnds(WhiteWins, "White mates", GE_FILE);
11553             }
11554             break;
11555           case MT_STALEMATE:
11556             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11557             break;
11558         }
11559         done = TRUE;
11560         break;
11561
11562       case PositionDiagram:     /* should not happen; ignore */
11563       case ElapsedTime:         /* ignore */
11564       case NAG:                 /* ignore */
11565         if (appData.debugMode)
11566           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11567                   yy_text, (int) moveType);
11568         return LoadGameOneMove(EndOfFile); /* tail recursion */
11569
11570       case IllegalMove:
11571         if (appData.testLegality) {
11572             if (appData.debugMode)
11573               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11574             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11575                     (forwardMostMove / 2) + 1,
11576                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11577             DisplayError(move, 0);
11578             done = TRUE;
11579         } else {
11580             if (appData.debugMode)
11581               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11582                       yy_text, currentMoveString);
11583             fromX = currentMoveString[0] - AAA;
11584             fromY = currentMoveString[1] - ONE;
11585             toX = currentMoveString[2] - AAA;
11586             toY = currentMoveString[3] - ONE;
11587             promoChar = currentMoveString[4];
11588         }
11589         break;
11590
11591       case AmbiguousMove:
11592         if (appData.debugMode)
11593           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11594         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11595                 (forwardMostMove / 2) + 1,
11596                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11597         DisplayError(move, 0);
11598         done = TRUE;
11599         break;
11600
11601       default:
11602       case ImpossibleMove:
11603         if (appData.debugMode)
11604           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11605         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11606                 (forwardMostMove / 2) + 1,
11607                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11608         DisplayError(move, 0);
11609         done = TRUE;
11610         break;
11611     }
11612
11613     if (done) {
11614         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11615             DrawPosition(FALSE, boards[currentMove]);
11616             DisplayBothClocks();
11617             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11618               DisplayComment(currentMove - 1, commentList[currentMove]);
11619         }
11620         (void) StopLoadGameTimer();
11621         gameFileFP = NULL;
11622         cmailOldMove = forwardMostMove;
11623         return FALSE;
11624     } else {
11625         /* currentMoveString is set as a side-effect of yylex */
11626
11627         thinkOutput[0] = NULLCHAR;
11628         MakeMove(fromX, fromY, toX, toY, promoChar);
11629         currentMove = forwardMostMove;
11630         return TRUE;
11631     }
11632 }
11633
11634 /* Load the nth game from the given file */
11635 int
11636 LoadGameFromFile (char *filename, int n, char *title, int useList)
11637 {
11638     FILE *f;
11639     char buf[MSG_SIZ];
11640
11641     if (strcmp(filename, "-") == 0) {
11642         f = stdin;
11643         title = "stdin";
11644     } else {
11645         f = fopen(filename, "rb");
11646         if (f == NULL) {
11647           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
11648             DisplayError(buf, errno);
11649             return FALSE;
11650         }
11651     }
11652     if (fseek(f, 0, 0) == -1) {
11653         /* f is not seekable; probably a pipe */
11654         useList = FALSE;
11655     }
11656     if (useList && n == 0) {
11657         int error = GameListBuild(f);
11658         if (error) {
11659             DisplayError(_("Cannot build game list"), error);
11660         } else if (!ListEmpty(&gameList) &&
11661                    ((ListGame *) gameList.tailPred)->number > 1) {
11662             GameListPopUp(f, title);
11663             return TRUE;
11664         }
11665         GameListDestroy();
11666         n = 1;
11667     }
11668     if (n == 0) n = 1;
11669     return LoadGame(f, n, title, FALSE);
11670 }
11671
11672
11673 void
11674 MakeRegisteredMove ()
11675 {
11676     int fromX, fromY, toX, toY;
11677     char promoChar;
11678     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11679         switch (cmailMoveType[lastLoadGameNumber - 1]) {
11680           case CMAIL_MOVE:
11681           case CMAIL_DRAW:
11682             if (appData.debugMode)
11683               fprintf(debugFP, "Restoring %s for game %d\n",
11684                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11685
11686             thinkOutput[0] = NULLCHAR;
11687             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11688             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11689             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11690             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11691             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11692             promoChar = cmailMove[lastLoadGameNumber - 1][4];
11693             MakeMove(fromX, fromY, toX, toY, promoChar);
11694             ShowMove(fromX, fromY, toX, toY);
11695
11696             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11697               case MT_NONE:
11698               case MT_CHECK:
11699                 break;
11700
11701               case MT_CHECKMATE:
11702               case MT_STAINMATE:
11703                 if (WhiteOnMove(currentMove)) {
11704                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
11705                 } else {
11706                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
11707                 }
11708                 break;
11709
11710               case MT_STALEMATE:
11711                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11712                 break;
11713             }
11714
11715             break;
11716
11717           case CMAIL_RESIGN:
11718             if (WhiteOnMove(currentMove)) {
11719                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11720             } else {
11721                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11722             }
11723             break;
11724
11725           case CMAIL_ACCEPT:
11726             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11727             break;
11728
11729           default:
11730             break;
11731         }
11732     }
11733
11734     return;
11735 }
11736
11737 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11738 int
11739 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11740 {
11741     int retVal;
11742
11743     if (gameNumber > nCmailGames) {
11744         DisplayError(_("No more games in this message"), 0);
11745         return FALSE;
11746     }
11747     if (f == lastLoadGameFP) {
11748         int offset = gameNumber - lastLoadGameNumber;
11749         if (offset == 0) {
11750             cmailMsg[0] = NULLCHAR;
11751             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11752                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11753                 nCmailMovesRegistered--;
11754             }
11755             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11756             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11757                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11758             }
11759         } else {
11760             if (! RegisterMove()) return FALSE;
11761         }
11762     }
11763
11764     retVal = LoadGame(f, gameNumber, title, useList);
11765
11766     /* Make move registered during previous look at this game, if any */
11767     MakeRegisteredMove();
11768
11769     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11770         commentList[currentMove]
11771           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11772         DisplayComment(currentMove - 1, commentList[currentMove]);
11773     }
11774
11775     return retVal;
11776 }
11777
11778 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11779 int
11780 ReloadGame (int offset)
11781 {
11782     int gameNumber = lastLoadGameNumber + offset;
11783     if (lastLoadGameFP == NULL) {
11784         DisplayError(_("No game has been loaded yet"), 0);
11785         return FALSE;
11786     }
11787     if (gameNumber <= 0) {
11788         DisplayError(_("Can't back up any further"), 0);
11789         return FALSE;
11790     }
11791     if (cmailMsgLoaded) {
11792         return CmailLoadGame(lastLoadGameFP, gameNumber,
11793                              lastLoadGameTitle, lastLoadGameUseList);
11794     } else {
11795         return LoadGame(lastLoadGameFP, gameNumber,
11796                         lastLoadGameTitle, lastLoadGameUseList);
11797     }
11798 }
11799
11800 int keys[EmptySquare+1];
11801
11802 int
11803 PositionMatches (Board b1, Board b2)
11804 {
11805     int r, f, sum=0;
11806     switch(appData.searchMode) {
11807         case 1: return CompareWithRights(b1, b2);
11808         case 2:
11809             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11810                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11811             }
11812             return TRUE;
11813         case 3:
11814             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11815               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11816                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11817             }
11818             return sum==0;
11819         case 4:
11820             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11821                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11822             }
11823             return sum==0;
11824     }
11825     return TRUE;
11826 }
11827
11828 #define Q_PROMO  4
11829 #define Q_EP     3
11830 #define Q_BCASTL 2
11831 #define Q_WCASTL 1
11832
11833 int pieceList[256], quickBoard[256];
11834 ChessSquare pieceType[256] = { EmptySquare };
11835 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11836 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11837 int soughtTotal, turn;
11838 Boolean epOK, flipSearch;
11839
11840 typedef struct {
11841     unsigned char piece, to;
11842 } Move;
11843
11844 #define DSIZE (250000)
11845
11846 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11847 Move *moveDatabase = initialSpace;
11848 unsigned int movePtr, dataSize = DSIZE;
11849
11850 int
11851 MakePieceList (Board board, int *counts)
11852 {
11853     int r, f, n=Q_PROMO, total=0;
11854     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11855     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11856         int sq = f + (r<<4);
11857         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
11858             quickBoard[sq] = ++n;
11859             pieceList[n] = sq;
11860             pieceType[n] = board[r][f];
11861             counts[board[r][f]]++;
11862             if(board[r][f] == WhiteKing) pieceList[1] = n; else
11863             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
11864             total++;
11865         }
11866     }
11867     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
11868     return total;
11869 }
11870
11871 void
11872 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
11873 {
11874     int sq = fromX + (fromY<<4);
11875     int piece = quickBoard[sq];
11876     quickBoard[sq] = 0;
11877     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
11878     if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11879         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
11880         moveDatabase[movePtr++].piece = Q_WCASTL;
11881         quickBoard[sq] = piece;
11882         piece = quickBoard[from]; quickBoard[from] = 0;
11883         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11884     } else
11885     if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11886         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
11887         moveDatabase[movePtr++].piece = Q_BCASTL;
11888         quickBoard[sq] = piece;
11889         piece = quickBoard[from]; quickBoard[from] = 0;
11890         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11891     } else
11892     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
11893         quickBoard[(fromY<<4)+toX] = 0;
11894         moveDatabase[movePtr].piece = Q_EP;
11895         moveDatabase[movePtr++].to = (fromY<<4)+toX;
11896         moveDatabase[movePtr].to = sq;
11897     } else
11898     if(promoPiece != pieceType[piece]) {
11899         moveDatabase[movePtr++].piece = Q_PROMO;
11900         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
11901     }
11902     moveDatabase[movePtr].piece = piece;
11903     quickBoard[sq] = piece;
11904     movePtr++;
11905 }
11906
11907 int
11908 PackGame (Board board)
11909 {
11910     Move *newSpace = NULL;
11911     moveDatabase[movePtr].piece = 0; // terminate previous game
11912     if(movePtr > dataSize) {
11913         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
11914         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
11915         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
11916         if(newSpace) {
11917             int i;
11918             Move *p = moveDatabase, *q = newSpace;
11919             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
11920             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
11921             moveDatabase = newSpace;
11922         } else { // calloc failed, we must be out of memory. Too bad...
11923             dataSize = 0; // prevent calloc events for all subsequent games
11924             return 0;     // and signal this one isn't cached
11925         }
11926     }
11927     movePtr++;
11928     MakePieceList(board, counts);
11929     return movePtr;
11930 }
11931
11932 int
11933 QuickCompare (Board board, int *minCounts, int *maxCounts)
11934 {   // compare according to search mode
11935     int r, f;
11936     switch(appData.searchMode)
11937     {
11938       case 1: // exact position match
11939         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
11940         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11941             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11942         }
11943         break;
11944       case 2: // can have extra material on empty squares
11945         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11946             if(board[r][f] == EmptySquare) continue;
11947             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11948         }
11949         break;
11950       case 3: // material with exact Pawn structure
11951         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11952             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
11953             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11954         } // fall through to material comparison
11955       case 4: // exact material
11956         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
11957         break;
11958       case 6: // material range with given imbalance
11959         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
11960         // fall through to range comparison
11961       case 5: // material range
11962         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
11963     }
11964     return TRUE;
11965 }
11966
11967 int
11968 QuickScan (Board board, Move *move)
11969 {   // reconstruct game,and compare all positions in it
11970     int cnt=0, stretch=0, total = MakePieceList(board, counts);
11971     do {
11972         int piece = move->piece;
11973         int to = move->to, from = pieceList[piece];
11974         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
11975           if(!piece) return -1;
11976           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
11977             piece = (++move)->piece;
11978             from = pieceList[piece];
11979             counts[pieceType[piece]]--;
11980             pieceType[piece] = (ChessSquare) move->to;
11981             counts[move->to]++;
11982           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
11983             counts[pieceType[quickBoard[to]]]--;
11984             quickBoard[to] = 0; total--;
11985             move++;
11986             continue;
11987           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
11988             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
11989             from  = pieceList[piece]; // so this must be King
11990             quickBoard[from] = 0;
11991             pieceList[piece] = to;
11992             from = pieceList[(++move)->piece]; // for FRC this has to be done here
11993             quickBoard[from] = 0; // rook
11994             quickBoard[to] = piece;
11995             to = move->to; piece = move->piece;
11996             goto aftercastle;
11997           }
11998         }
11999         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12000         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
12001         quickBoard[from] = 0;
12002       aftercastle:
12003         quickBoard[to] = piece;
12004         pieceList[piece] = to;
12005         cnt++; turn ^= 3;
12006         if(QuickCompare(soughtBoard, minSought, maxSought) ||
12007            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12008            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12009                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12010           ) {
12011             static int lastCounts[EmptySquare+1];
12012             int i;
12013             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12014             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12015         } else stretch = 0;
12016         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
12017         move++;
12018     } while(1);
12019 }
12020
12021 void
12022 InitSearch ()
12023 {
12024     int r, f;
12025     flipSearch = FALSE;
12026     CopyBoard(soughtBoard, boards[currentMove]);
12027     soughtTotal = MakePieceList(soughtBoard, maxSought);
12028     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12029     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12030     CopyBoard(reverseBoard, boards[currentMove]);
12031     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12032         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12033         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12034         reverseBoard[r][f] = piece;
12035     }
12036     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12037     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12038     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12039                  || (boards[currentMove][CASTLING][2] == NoRights ||
12040                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12041                  && (boards[currentMove][CASTLING][5] == NoRights ||
12042                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12043       ) {
12044         flipSearch = TRUE;
12045         CopyBoard(flipBoard, soughtBoard);
12046         CopyBoard(rotateBoard, reverseBoard);
12047         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12048             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
12049             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12050         }
12051     }
12052     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12053     if(appData.searchMode >= 5) {
12054         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12055         MakePieceList(soughtBoard, minSought);
12056         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12057     }
12058     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12059         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12060 }
12061
12062 GameInfo dummyInfo;
12063 static int creatingBook;
12064
12065 int
12066 GameContainsPosition (FILE *f, ListGame *lg)
12067 {
12068     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12069     int fromX, fromY, toX, toY;
12070     char promoChar;
12071     static int initDone=FALSE;
12072
12073     // weed out games based on numerical tag comparison
12074     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12075     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12076     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12077     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12078     if(!initDone) {
12079         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12080         initDone = TRUE;
12081     }
12082     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen);
12083     else CopyBoard(boards[scratch], initialPosition); // default start position
12084     if(lg->moves) {
12085         turn = btm + 1;
12086         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12087         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12088     }
12089     if(btm) plyNr++;
12090     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12091     fseek(f, lg->offset, 0);
12092     yynewfile(f);
12093     while(1) {
12094         yyboardindex = scratch;
12095         quickFlag = plyNr+1;
12096         next = Myylex();
12097         quickFlag = 0;
12098         switch(next) {
12099             case PGNTag:
12100                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12101             default:
12102                 continue;
12103
12104             case XBoardGame:
12105             case GNUChessGame:
12106                 if(plyNr) return -1; // after we have seen moves, this is for new game
12107               continue;
12108
12109             case AmbiguousMove: // we cannot reconstruct the game beyond these two
12110             case ImpossibleMove:
12111             case WhiteWins: // game ends here with these four
12112             case BlackWins:
12113             case GameIsDrawn:
12114             case GameUnfinished:
12115                 return -1;
12116
12117             case IllegalMove:
12118                 if(appData.testLegality) return -1;
12119             case WhiteCapturesEnPassant:
12120             case BlackCapturesEnPassant:
12121             case WhitePromotion:
12122             case BlackPromotion:
12123             case WhiteNonPromotion:
12124             case BlackNonPromotion:
12125             case NormalMove:
12126             case WhiteKingSideCastle:
12127             case WhiteQueenSideCastle:
12128             case BlackKingSideCastle:
12129             case BlackQueenSideCastle:
12130             case WhiteKingSideCastleWild:
12131             case WhiteQueenSideCastleWild:
12132             case BlackKingSideCastleWild:
12133             case BlackQueenSideCastleWild:
12134             case WhiteHSideCastleFR:
12135             case WhiteASideCastleFR:
12136             case BlackHSideCastleFR:
12137             case BlackASideCastleFR:
12138                 fromX = currentMoveString[0] - AAA;
12139                 fromY = currentMoveString[1] - ONE;
12140                 toX = currentMoveString[2] - AAA;
12141                 toY = currentMoveString[3] - ONE;
12142                 promoChar = currentMoveString[4];
12143                 break;
12144             case WhiteDrop:
12145             case BlackDrop:
12146                 fromX = next == WhiteDrop ?
12147                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
12148                   (int) CharToPiece(ToLower(currentMoveString[0]));
12149                 fromY = DROP_RANK;
12150                 toX = currentMoveString[2] - AAA;
12151                 toY = currentMoveString[3] - ONE;
12152                 promoChar = 0;
12153                 break;
12154         }
12155         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12156         plyNr++;
12157         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12158         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12159         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12160         if(appData.findMirror) {
12161             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12162             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12163         }
12164     }
12165 }
12166
12167 /* Load the nth game from open file f */
12168 int
12169 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12170 {
12171     ChessMove cm;
12172     char buf[MSG_SIZ];
12173     int gn = gameNumber;
12174     ListGame *lg = NULL;
12175     int numPGNTags = 0;
12176     int err, pos = -1;
12177     GameMode oldGameMode;
12178     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12179
12180     if (appData.debugMode)
12181         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12182
12183     if (gameMode == Training )
12184         SetTrainingModeOff();
12185
12186     oldGameMode = gameMode;
12187     if (gameMode != BeginningOfGame) {
12188       Reset(FALSE, TRUE);
12189     }
12190
12191     gameFileFP = f;
12192     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12193         fclose(lastLoadGameFP);
12194     }
12195
12196     if (useList) {
12197         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12198
12199         if (lg) {
12200             fseek(f, lg->offset, 0);
12201             GameListHighlight(gameNumber);
12202             pos = lg->position;
12203             gn = 1;
12204         }
12205         else {
12206             if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
12207               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12208             else
12209             DisplayError(_("Game number out of range"), 0);
12210             return FALSE;
12211         }
12212     } else {
12213         GameListDestroy();
12214         if (fseek(f, 0, 0) == -1) {
12215             if (f == lastLoadGameFP ?
12216                 gameNumber == lastLoadGameNumber + 1 :
12217                 gameNumber == 1) {
12218                 gn = 1;
12219             } else {
12220                 DisplayError(_("Can't seek on game file"), 0);
12221                 return FALSE;
12222             }
12223         }
12224     }
12225     lastLoadGameFP = f;
12226     lastLoadGameNumber = gameNumber;
12227     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12228     lastLoadGameUseList = useList;
12229
12230     yynewfile(f);
12231
12232     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12233       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12234                 lg->gameInfo.black);
12235             DisplayTitle(buf);
12236     } else if (*title != NULLCHAR) {
12237         if (gameNumber > 1) {
12238           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12239             DisplayTitle(buf);
12240         } else {
12241             DisplayTitle(title);
12242         }
12243     }
12244
12245     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12246         gameMode = PlayFromGameFile;
12247         ModeHighlight();
12248     }
12249
12250     currentMove = forwardMostMove = backwardMostMove = 0;
12251     CopyBoard(boards[0], initialPosition);
12252     StopClocks();
12253
12254     /*
12255      * Skip the first gn-1 games in the file.
12256      * Also skip over anything that precedes an identifiable
12257      * start of game marker, to avoid being confused by
12258      * garbage at the start of the file.  Currently
12259      * recognized start of game markers are the move number "1",
12260      * the pattern "gnuchess .* game", the pattern
12261      * "^[#;%] [^ ]* game file", and a PGN tag block.
12262      * A game that starts with one of the latter two patterns
12263      * will also have a move number 1, possibly
12264      * following a position diagram.
12265      * 5-4-02: Let's try being more lenient and allowing a game to
12266      * start with an unnumbered move.  Does that break anything?
12267      */
12268     cm = lastLoadGameStart = EndOfFile;
12269     while (gn > 0) {
12270         yyboardindex = forwardMostMove;
12271         cm = (ChessMove) Myylex();
12272         switch (cm) {
12273           case EndOfFile:
12274             if (cmailMsgLoaded) {
12275                 nCmailGames = CMAIL_MAX_GAMES - gn;
12276             } else {
12277                 Reset(TRUE, TRUE);
12278                 DisplayError(_("Game not found in file"), 0);
12279             }
12280             return FALSE;
12281
12282           case GNUChessGame:
12283           case XBoardGame:
12284             gn--;
12285             lastLoadGameStart = cm;
12286             break;
12287
12288           case MoveNumberOne:
12289             switch (lastLoadGameStart) {
12290               case GNUChessGame:
12291               case XBoardGame:
12292               case PGNTag:
12293                 break;
12294               case MoveNumberOne:
12295               case EndOfFile:
12296                 gn--;           /* count this game */
12297                 lastLoadGameStart = cm;
12298                 break;
12299               default:
12300                 /* impossible */
12301                 break;
12302             }
12303             break;
12304
12305           case PGNTag:
12306             switch (lastLoadGameStart) {
12307               case GNUChessGame:
12308               case PGNTag:
12309               case MoveNumberOne:
12310               case EndOfFile:
12311                 gn--;           /* count this game */
12312                 lastLoadGameStart = cm;
12313                 break;
12314               case XBoardGame:
12315                 lastLoadGameStart = cm; /* game counted already */
12316                 break;
12317               default:
12318                 /* impossible */
12319                 break;
12320             }
12321             if (gn > 0) {
12322                 do {
12323                     yyboardindex = forwardMostMove;
12324                     cm = (ChessMove) Myylex();
12325                 } while (cm == PGNTag || cm == Comment);
12326             }
12327             break;
12328
12329           case WhiteWins:
12330           case BlackWins:
12331           case GameIsDrawn:
12332             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
12333                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
12334                     != CMAIL_OLD_RESULT) {
12335                     nCmailResults ++ ;
12336                     cmailResult[  CMAIL_MAX_GAMES
12337                                 - gn - 1] = CMAIL_OLD_RESULT;
12338                 }
12339             }
12340             break;
12341
12342           case NormalMove:
12343             /* Only a NormalMove can be at the start of a game
12344              * without a position diagram. */
12345             if (lastLoadGameStart == EndOfFile ) {
12346               gn--;
12347               lastLoadGameStart = MoveNumberOne;
12348             }
12349             break;
12350
12351           default:
12352             break;
12353         }
12354     }
12355
12356     if (appData.debugMode)
12357       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
12358
12359     if (cm == XBoardGame) {
12360         /* Skip any header junk before position diagram and/or move 1 */
12361         for (;;) {
12362             yyboardindex = forwardMostMove;
12363             cm = (ChessMove) Myylex();
12364
12365             if (cm == EndOfFile ||
12366                 cm == GNUChessGame || cm == XBoardGame) {
12367                 /* Empty game; pretend end-of-file and handle later */
12368                 cm = EndOfFile;
12369                 break;
12370             }
12371
12372             if (cm == MoveNumberOne || cm == PositionDiagram ||
12373                 cm == PGNTag || cm == Comment)
12374               break;
12375         }
12376     } else if (cm == GNUChessGame) {
12377         if (gameInfo.event != NULL) {
12378             free(gameInfo.event);
12379         }
12380         gameInfo.event = StrSave(yy_text);
12381     }
12382
12383     startedFromSetupPosition = FALSE;
12384     while (cm == PGNTag) {
12385         if (appData.debugMode)
12386           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
12387         err = ParsePGNTag(yy_text, &gameInfo);
12388         if (!err) numPGNTags++;
12389
12390         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
12391         if(gameInfo.variant != oldVariant) {
12392             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
12393             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
12394             InitPosition(TRUE);
12395             oldVariant = gameInfo.variant;
12396             if (appData.debugMode)
12397               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
12398         }
12399
12400
12401         if (gameInfo.fen != NULL) {
12402           Board initial_position;
12403           startedFromSetupPosition = TRUE;
12404           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
12405             Reset(TRUE, TRUE);
12406             DisplayError(_("Bad FEN position in file"), 0);
12407             return FALSE;
12408           }
12409           CopyBoard(boards[0], initial_position);
12410           if (blackPlaysFirst) {
12411             currentMove = forwardMostMove = backwardMostMove = 1;
12412             CopyBoard(boards[1], initial_position);
12413             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12414             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12415             timeRemaining[0][1] = whiteTimeRemaining;
12416             timeRemaining[1][1] = blackTimeRemaining;
12417             if (commentList[0] != NULL) {
12418               commentList[1] = commentList[0];
12419               commentList[0] = NULL;
12420             }
12421           } else {
12422             currentMove = forwardMostMove = backwardMostMove = 0;
12423           }
12424           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12425           {   int i;
12426               initialRulePlies = FENrulePlies;
12427               for( i=0; i< nrCastlingRights; i++ )
12428                   initialRights[i] = initial_position[CASTLING][i];
12429           }
12430           yyboardindex = forwardMostMove;
12431           free(gameInfo.fen);
12432           gameInfo.fen = NULL;
12433         }
12434
12435         yyboardindex = forwardMostMove;
12436         cm = (ChessMove) Myylex();
12437
12438         /* Handle comments interspersed among the tags */
12439         while (cm == Comment) {
12440             char *p;
12441             if (appData.debugMode)
12442               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12443             p = yy_text;
12444             AppendComment(currentMove, p, FALSE);
12445             yyboardindex = forwardMostMove;
12446             cm = (ChessMove) Myylex();
12447         }
12448     }
12449
12450     /* don't rely on existence of Event tag since if game was
12451      * pasted from clipboard the Event tag may not exist
12452      */
12453     if (numPGNTags > 0){
12454         char *tags;
12455         if (gameInfo.variant == VariantNormal) {
12456           VariantClass v = StringToVariant(gameInfo.event);
12457           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12458           if(v < VariantShogi) gameInfo.variant = v;
12459         }
12460         if (!matchMode) {
12461           if( appData.autoDisplayTags ) {
12462             tags = PGNTags(&gameInfo);
12463             TagsPopUp(tags, CmailMsg());
12464             free(tags);
12465           }
12466         }
12467     } else {
12468         /* Make something up, but don't display it now */
12469         SetGameInfo();
12470         TagsPopDown();
12471     }
12472
12473     if (cm == PositionDiagram) {
12474         int i, j;
12475         char *p;
12476         Board initial_position;
12477
12478         if (appData.debugMode)
12479           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12480
12481         if (!startedFromSetupPosition) {
12482             p = yy_text;
12483             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12484               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12485                 switch (*p) {
12486                   case '{':
12487                   case '[':
12488                   case '-':
12489                   case ' ':
12490                   case '\t':
12491                   case '\n':
12492                   case '\r':
12493                     break;
12494                   default:
12495                     initial_position[i][j++] = CharToPiece(*p);
12496                     break;
12497                 }
12498             while (*p == ' ' || *p == '\t' ||
12499                    *p == '\n' || *p == '\r') p++;
12500
12501             if (strncmp(p, "black", strlen("black"))==0)
12502               blackPlaysFirst = TRUE;
12503             else
12504               blackPlaysFirst = FALSE;
12505             startedFromSetupPosition = TRUE;
12506
12507             CopyBoard(boards[0], initial_position);
12508             if (blackPlaysFirst) {
12509                 currentMove = forwardMostMove = backwardMostMove = 1;
12510                 CopyBoard(boards[1], initial_position);
12511                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12512                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12513                 timeRemaining[0][1] = whiteTimeRemaining;
12514                 timeRemaining[1][1] = blackTimeRemaining;
12515                 if (commentList[0] != NULL) {
12516                     commentList[1] = commentList[0];
12517                     commentList[0] = NULL;
12518                 }
12519             } else {
12520                 currentMove = forwardMostMove = backwardMostMove = 0;
12521             }
12522         }
12523         yyboardindex = forwardMostMove;
12524         cm = (ChessMove) Myylex();
12525     }
12526
12527   if(!creatingBook) {
12528     if (first.pr == NoProc) {
12529         StartChessProgram(&first);
12530     }
12531     InitChessProgram(&first, FALSE);
12532     SendToProgram("force\n", &first);
12533     if (startedFromSetupPosition) {
12534         SendBoard(&first, forwardMostMove);
12535     if (appData.debugMode) {
12536         fprintf(debugFP, "Load Game\n");
12537     }
12538         DisplayBothClocks();
12539     }
12540   }
12541
12542     /* [HGM] server: flag to write setup moves in broadcast file as one */
12543     loadFlag = appData.suppressLoadMoves;
12544
12545     while (cm == Comment) {
12546         char *p;
12547         if (appData.debugMode)
12548           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12549         p = yy_text;
12550         AppendComment(currentMove, p, FALSE);
12551         yyboardindex = forwardMostMove;
12552         cm = (ChessMove) Myylex();
12553     }
12554
12555     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12556         cm == WhiteWins || cm == BlackWins ||
12557         cm == GameIsDrawn || cm == GameUnfinished) {
12558         DisplayMessage("", _("No moves in game"));
12559         if (cmailMsgLoaded) {
12560             if (appData.debugMode)
12561               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12562             ClearHighlights();
12563             flipView = FALSE;
12564         }
12565         DrawPosition(FALSE, boards[currentMove]);
12566         DisplayBothClocks();
12567         gameMode = EditGame;
12568         ModeHighlight();
12569         gameFileFP = NULL;
12570         cmailOldMove = 0;
12571         return TRUE;
12572     }
12573
12574     // [HGM] PV info: routine tests if comment empty
12575     if (!matchMode && (pausing || appData.timeDelay != 0)) {
12576         DisplayComment(currentMove - 1, commentList[currentMove]);
12577     }
12578     if (!matchMode && appData.timeDelay != 0)
12579       DrawPosition(FALSE, boards[currentMove]);
12580
12581     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12582       programStats.ok_to_send = 1;
12583     }
12584
12585     /* if the first token after the PGN tags is a move
12586      * and not move number 1, retrieve it from the parser
12587      */
12588     if (cm != MoveNumberOne)
12589         LoadGameOneMove(cm);
12590
12591     /* load the remaining moves from the file */
12592     while (LoadGameOneMove(EndOfFile)) {
12593       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12594       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12595     }
12596
12597     /* rewind to the start of the game */
12598     currentMove = backwardMostMove;
12599
12600     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12601
12602     if (oldGameMode == AnalyzeFile) {
12603       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
12604       AnalyzeFileEvent();
12605     } else
12606     if (oldGameMode == AnalyzeMode) {
12607       AnalyzeFileEvent();
12608     }
12609
12610     if(creatingBook) return TRUE;
12611     if (!matchMode && pos > 0) {
12612         ToNrEvent(pos); // [HGM] no autoplay if selected on position
12613     } else
12614     if (matchMode || appData.timeDelay == 0) {
12615       ToEndEvent();
12616     } else if (appData.timeDelay > 0) {
12617       AutoPlayGameLoop();
12618     }
12619
12620     if (appData.debugMode)
12621         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
12622
12623     loadFlag = 0; /* [HGM] true game starts */
12624     return TRUE;
12625 }
12626
12627 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
12628 int
12629 ReloadPosition (int offset)
12630 {
12631     int positionNumber = lastLoadPositionNumber + offset;
12632     if (lastLoadPositionFP == NULL) {
12633         DisplayError(_("No position has been loaded yet"), 0);
12634         return FALSE;
12635     }
12636     if (positionNumber <= 0) {
12637         DisplayError(_("Can't back up any further"), 0);
12638         return FALSE;
12639     }
12640     return LoadPosition(lastLoadPositionFP, positionNumber,
12641                         lastLoadPositionTitle);
12642 }
12643
12644 /* Load the nth position from the given file */
12645 int
12646 LoadPositionFromFile (char *filename, int n, char *title)
12647 {
12648     FILE *f;
12649     char buf[MSG_SIZ];
12650
12651     if (strcmp(filename, "-") == 0) {
12652         return LoadPosition(stdin, n, "stdin");
12653     } else {
12654         f = fopen(filename, "rb");
12655         if (f == NULL) {
12656             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12657             DisplayError(buf, errno);
12658             return FALSE;
12659         } else {
12660             return LoadPosition(f, n, title);
12661         }
12662     }
12663 }
12664
12665 /* Load the nth position from the given open file, and close it */
12666 int
12667 LoadPosition (FILE *f, int positionNumber, char *title)
12668 {
12669     char *p, line[MSG_SIZ];
12670     Board initial_position;
12671     int i, j, fenMode, pn;
12672
12673     if (gameMode == Training )
12674         SetTrainingModeOff();
12675
12676     if (gameMode != BeginningOfGame) {
12677         Reset(FALSE, TRUE);
12678     }
12679     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12680         fclose(lastLoadPositionFP);
12681     }
12682     if (positionNumber == 0) positionNumber = 1;
12683     lastLoadPositionFP = f;
12684     lastLoadPositionNumber = positionNumber;
12685     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12686     if (first.pr == NoProc && !appData.noChessProgram) {
12687       StartChessProgram(&first);
12688       InitChessProgram(&first, FALSE);
12689     }
12690     pn = positionNumber;
12691     if (positionNumber < 0) {
12692         /* Negative position number means to seek to that byte offset */
12693         if (fseek(f, -positionNumber, 0) == -1) {
12694             DisplayError(_("Can't seek on position file"), 0);
12695             return FALSE;
12696         };
12697         pn = 1;
12698     } else {
12699         if (fseek(f, 0, 0) == -1) {
12700             if (f == lastLoadPositionFP ?
12701                 positionNumber == lastLoadPositionNumber + 1 :
12702                 positionNumber == 1) {
12703                 pn = 1;
12704             } else {
12705                 DisplayError(_("Can't seek on position file"), 0);
12706                 return FALSE;
12707             }
12708         }
12709     }
12710     /* See if this file is FEN or old-style xboard */
12711     if (fgets(line, MSG_SIZ, f) == NULL) {
12712         DisplayError(_("Position not found in file"), 0);
12713         return FALSE;
12714     }
12715     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12716     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12717
12718     if (pn >= 2) {
12719         if (fenMode || line[0] == '#') pn--;
12720         while (pn > 0) {
12721             /* skip positions before number pn */
12722             if (fgets(line, MSG_SIZ, f) == NULL) {
12723                 Reset(TRUE, TRUE);
12724                 DisplayError(_("Position not found in file"), 0);
12725                 return FALSE;
12726             }
12727             if (fenMode || line[0] == '#') pn--;
12728         }
12729     }
12730
12731     if (fenMode) {
12732         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
12733             DisplayError(_("Bad FEN position in file"), 0);
12734             return FALSE;
12735         }
12736     } else {
12737         (void) fgets(line, MSG_SIZ, f);
12738         (void) fgets(line, MSG_SIZ, f);
12739
12740         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12741             (void) fgets(line, MSG_SIZ, f);
12742             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12743                 if (*p == ' ')
12744                   continue;
12745                 initial_position[i][j++] = CharToPiece(*p);
12746             }
12747         }
12748
12749         blackPlaysFirst = FALSE;
12750         if (!feof(f)) {
12751             (void) fgets(line, MSG_SIZ, f);
12752             if (strncmp(line, "black", strlen("black"))==0)
12753               blackPlaysFirst = TRUE;
12754         }
12755     }
12756     startedFromSetupPosition = TRUE;
12757
12758     CopyBoard(boards[0], initial_position);
12759     if (blackPlaysFirst) {
12760         currentMove = forwardMostMove = backwardMostMove = 1;
12761         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12762         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12763         CopyBoard(boards[1], initial_position);
12764         DisplayMessage("", _("Black to play"));
12765     } else {
12766         currentMove = forwardMostMove = backwardMostMove = 0;
12767         DisplayMessage("", _("White to play"));
12768     }
12769     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12770     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12771         SendToProgram("force\n", &first);
12772         SendBoard(&first, forwardMostMove);
12773     }
12774     if (appData.debugMode) {
12775 int i, j;
12776   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12777   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12778         fprintf(debugFP, "Load Position\n");
12779     }
12780
12781     if (positionNumber > 1) {
12782       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12783         DisplayTitle(line);
12784     } else {
12785         DisplayTitle(title);
12786     }
12787     gameMode = EditGame;
12788     ModeHighlight();
12789     ResetClocks();
12790     timeRemaining[0][1] = whiteTimeRemaining;
12791     timeRemaining[1][1] = blackTimeRemaining;
12792     DrawPosition(FALSE, boards[currentMove]);
12793
12794     return TRUE;
12795 }
12796
12797
12798 void
12799 CopyPlayerNameIntoFileName (char **dest, char *src)
12800 {
12801     while (*src != NULLCHAR && *src != ',') {
12802         if (*src == ' ') {
12803             *(*dest)++ = '_';
12804             src++;
12805         } else {
12806             *(*dest)++ = *src++;
12807         }
12808     }
12809 }
12810
12811 char *
12812 DefaultFileName (char *ext)
12813 {
12814     static char def[MSG_SIZ];
12815     char *p;
12816
12817     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12818         p = def;
12819         CopyPlayerNameIntoFileName(&p, gameInfo.white);
12820         *p++ = '-';
12821         CopyPlayerNameIntoFileName(&p, gameInfo.black);
12822         *p++ = '.';
12823         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12824     } else {
12825         def[0] = NULLCHAR;
12826     }
12827     return def;
12828 }
12829
12830 /* Save the current game to the given file */
12831 int
12832 SaveGameToFile (char *filename, int append)
12833 {
12834     FILE *f;
12835     char buf[MSG_SIZ];
12836     int result, i, t,tot=0;
12837
12838     if (strcmp(filename, "-") == 0) {
12839         return SaveGame(stdout, 0, NULL);
12840     } else {
12841         for(i=0; i<10; i++) { // upto 10 tries
12842              f = fopen(filename, append ? "a" : "w");
12843              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12844              if(f || errno != 13) break;
12845              DoSleep(t = 5 + random()%11); // wait 5-15 msec
12846              tot += t;
12847         }
12848         if (f == NULL) {
12849             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12850             DisplayError(buf, errno);
12851             return FALSE;
12852         } else {
12853             safeStrCpy(buf, lastMsg, MSG_SIZ);
12854             DisplayMessage(_("Waiting for access to save file"), "");
12855             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
12856             DisplayMessage(_("Saving game"), "");
12857             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
12858             result = SaveGame(f, 0, NULL);
12859             DisplayMessage(buf, "");
12860             return result;
12861         }
12862     }
12863 }
12864
12865 char *
12866 SavePart (char *str)
12867 {
12868     static char buf[MSG_SIZ];
12869     char *p;
12870
12871     p = strchr(str, ' ');
12872     if (p == NULL) return str;
12873     strncpy(buf, str, p - str);
12874     buf[p - str] = NULLCHAR;
12875     return buf;
12876 }
12877
12878 #define PGN_MAX_LINE 75
12879
12880 #define PGN_SIDE_WHITE  0
12881 #define PGN_SIDE_BLACK  1
12882
12883 static int
12884 FindFirstMoveOutOfBook (int side)
12885 {
12886     int result = -1;
12887
12888     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12889         int index = backwardMostMove;
12890         int has_book_hit = 0;
12891
12892         if( (index % 2) != side ) {
12893             index++;
12894         }
12895
12896         while( index < forwardMostMove ) {
12897             /* Check to see if engine is in book */
12898             int depth = pvInfoList[index].depth;
12899             int score = pvInfoList[index].score;
12900             int in_book = 0;
12901
12902             if( depth <= 2 ) {
12903                 in_book = 1;
12904             }
12905             else if( score == 0 && depth == 63 ) {
12906                 in_book = 1; /* Zappa */
12907             }
12908             else if( score == 2 && depth == 99 ) {
12909                 in_book = 1; /* Abrok */
12910             }
12911
12912             has_book_hit += in_book;
12913
12914             if( ! in_book ) {
12915                 result = index;
12916
12917                 break;
12918             }
12919
12920             index += 2;
12921         }
12922     }
12923
12924     return result;
12925 }
12926
12927 void
12928 GetOutOfBookInfo (char * buf)
12929 {
12930     int oob[2];
12931     int i;
12932     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12933
12934     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12935     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12936
12937     *buf = '\0';
12938
12939     if( oob[0] >= 0 || oob[1] >= 0 ) {
12940         for( i=0; i<2; i++ ) {
12941             int idx = oob[i];
12942
12943             if( idx >= 0 ) {
12944                 if( i > 0 && oob[0] >= 0 ) {
12945                     strcat( buf, "   " );
12946                 }
12947
12948                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12949                 sprintf( buf+strlen(buf), "%s%.2f",
12950                     pvInfoList[idx].score >= 0 ? "+" : "",
12951                     pvInfoList[idx].score / 100.0 );
12952             }
12953         }
12954     }
12955 }
12956
12957 /* Save game in PGN style and close the file */
12958 int
12959 SaveGamePGN (FILE *f)
12960 {
12961     int i, offset, linelen, newblock;
12962 //    char *movetext;
12963     char numtext[32];
12964     int movelen, numlen, blank;
12965     char move_buffer[100]; /* [AS] Buffer for move+PV info */
12966
12967     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12968
12969     PrintPGNTags(f, &gameInfo);
12970
12971     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
12972
12973     if (backwardMostMove > 0 || startedFromSetupPosition) {
12974         char *fen = PositionToFEN(backwardMostMove, NULL, 1);
12975         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12976         fprintf(f, "\n{--------------\n");
12977         PrintPosition(f, backwardMostMove);
12978         fprintf(f, "--------------}\n");
12979         free(fen);
12980     }
12981     else {
12982         /* [AS] Out of book annotation */
12983         if( appData.saveOutOfBookInfo ) {
12984             char buf[64];
12985
12986             GetOutOfBookInfo( buf );
12987
12988             if( buf[0] != '\0' ) {
12989                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12990             }
12991         }
12992
12993         fprintf(f, "\n");
12994     }
12995
12996     i = backwardMostMove;
12997     linelen = 0;
12998     newblock = TRUE;
12999
13000     while (i < forwardMostMove) {
13001         /* Print comments preceding this move */
13002         if (commentList[i] != NULL) {
13003             if (linelen > 0) fprintf(f, "\n");
13004             fprintf(f, "%s", commentList[i]);
13005             linelen = 0;
13006             newblock = TRUE;
13007         }
13008
13009         /* Format move number */
13010         if ((i % 2) == 0)
13011           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13012         else
13013           if (newblock)
13014             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13015           else
13016             numtext[0] = NULLCHAR;
13017
13018         numlen = strlen(numtext);
13019         newblock = FALSE;
13020
13021         /* Print move number */
13022         blank = linelen > 0 && numlen > 0;
13023         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13024             fprintf(f, "\n");
13025             linelen = 0;
13026             blank = 0;
13027         }
13028         if (blank) {
13029             fprintf(f, " ");
13030             linelen++;
13031         }
13032         fprintf(f, "%s", numtext);
13033         linelen += numlen;
13034
13035         /* Get move */
13036         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13037         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13038
13039         /* Print move */
13040         blank = linelen > 0 && movelen > 0;
13041         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13042             fprintf(f, "\n");
13043             linelen = 0;
13044             blank = 0;
13045         }
13046         if (blank) {
13047             fprintf(f, " ");
13048             linelen++;
13049         }
13050         fprintf(f, "%s", move_buffer);
13051         linelen += movelen;
13052
13053         /* [AS] Add PV info if present */
13054         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13055             /* [HGM] add time */
13056             char buf[MSG_SIZ]; int seconds;
13057
13058             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13059
13060             if( seconds <= 0)
13061               buf[0] = 0;
13062             else
13063               if( seconds < 30 )
13064                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13065               else
13066                 {
13067                   seconds = (seconds + 4)/10; // round to full seconds
13068                   if( seconds < 60 )
13069                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
13070                   else
13071                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
13072                 }
13073
13074             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
13075                       pvInfoList[i].score >= 0 ? "+" : "",
13076                       pvInfoList[i].score / 100.0,
13077                       pvInfoList[i].depth,
13078                       buf );
13079
13080             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
13081
13082             /* Print score/depth */
13083             blank = linelen > 0 && movelen > 0;
13084             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13085                 fprintf(f, "\n");
13086                 linelen = 0;
13087                 blank = 0;
13088             }
13089             if (blank) {
13090                 fprintf(f, " ");
13091                 linelen++;
13092             }
13093             fprintf(f, "%s", move_buffer);
13094             linelen += movelen;
13095         }
13096
13097         i++;
13098     }
13099
13100     /* Start a new line */
13101     if (linelen > 0) fprintf(f, "\n");
13102
13103     /* Print comments after last move */
13104     if (commentList[i] != NULL) {
13105         fprintf(f, "%s\n", commentList[i]);
13106     }
13107
13108     /* Print result */
13109     if (gameInfo.resultDetails != NULL &&
13110         gameInfo.resultDetails[0] != NULLCHAR) {
13111         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
13112                 PGNResult(gameInfo.result));
13113     } else {
13114         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13115     }
13116
13117     fclose(f);
13118     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13119     return TRUE;
13120 }
13121
13122 /* Save game in old style and close the file */
13123 int
13124 SaveGameOldStyle (FILE *f)
13125 {
13126     int i, offset;
13127     time_t tm;
13128
13129     tm = time((time_t *) NULL);
13130
13131     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
13132     PrintOpponents(f);
13133
13134     if (backwardMostMove > 0 || startedFromSetupPosition) {
13135         fprintf(f, "\n[--------------\n");
13136         PrintPosition(f, backwardMostMove);
13137         fprintf(f, "--------------]\n");
13138     } else {
13139         fprintf(f, "\n");
13140     }
13141
13142     i = backwardMostMove;
13143     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13144
13145     while (i < forwardMostMove) {
13146         if (commentList[i] != NULL) {
13147             fprintf(f, "[%s]\n", commentList[i]);
13148         }
13149
13150         if ((i % 2) == 1) {
13151             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
13152             i++;
13153         } else {
13154             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
13155             i++;
13156             if (commentList[i] != NULL) {
13157                 fprintf(f, "\n");
13158                 continue;
13159             }
13160             if (i >= forwardMostMove) {
13161                 fprintf(f, "\n");
13162                 break;
13163             }
13164             fprintf(f, "%s\n", parseList[i]);
13165             i++;
13166         }
13167     }
13168
13169     if (commentList[i] != NULL) {
13170         fprintf(f, "[%s]\n", commentList[i]);
13171     }
13172
13173     /* This isn't really the old style, but it's close enough */
13174     if (gameInfo.resultDetails != NULL &&
13175         gameInfo.resultDetails[0] != NULLCHAR) {
13176         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13177                 gameInfo.resultDetails);
13178     } else {
13179         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13180     }
13181
13182     fclose(f);
13183     return TRUE;
13184 }
13185
13186 /* Save the current game to open file f and close the file */
13187 int
13188 SaveGame (FILE *f, int dummy, char *dummy2)
13189 {
13190     if (gameMode == EditPosition) EditPositionDone(TRUE);
13191     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13192     if (appData.oldSaveStyle)
13193       return SaveGameOldStyle(f);
13194     else
13195       return SaveGamePGN(f);
13196 }
13197
13198 /* Save the current position to the given file */
13199 int
13200 SavePositionToFile (char *filename)
13201 {
13202     FILE *f;
13203     char buf[MSG_SIZ];
13204
13205     if (strcmp(filename, "-") == 0) {
13206         return SavePosition(stdout, 0, NULL);
13207     } else {
13208         f = fopen(filename, "a");
13209         if (f == NULL) {
13210             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13211             DisplayError(buf, errno);
13212             return FALSE;
13213         } else {
13214             safeStrCpy(buf, lastMsg, MSG_SIZ);
13215             DisplayMessage(_("Waiting for access to save file"), "");
13216             flock(fileno(f), LOCK_EX); // [HGM] lock
13217             DisplayMessage(_("Saving position"), "");
13218             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
13219             SavePosition(f, 0, NULL);
13220             DisplayMessage(buf, "");
13221             return TRUE;
13222         }
13223     }
13224 }
13225
13226 /* Save the current position to the given open file and close the file */
13227 int
13228 SavePosition (FILE *f, int dummy, char *dummy2)
13229 {
13230     time_t tm;
13231     char *fen;
13232
13233     if (gameMode == EditPosition) EditPositionDone(TRUE);
13234     if (appData.oldSaveStyle) {
13235         tm = time((time_t *) NULL);
13236
13237         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
13238         PrintOpponents(f);
13239         fprintf(f, "[--------------\n");
13240         PrintPosition(f, currentMove);
13241         fprintf(f, "--------------]\n");
13242     } else {
13243         fen = PositionToFEN(currentMove, NULL, 1);
13244         fprintf(f, "%s\n", fen);
13245         free(fen);
13246     }
13247     fclose(f);
13248     return TRUE;
13249 }
13250
13251 void
13252 ReloadCmailMsgEvent (int unregister)
13253 {
13254 #if !WIN32
13255     static char *inFilename = NULL;
13256     static char *outFilename;
13257     int i;
13258     struct stat inbuf, outbuf;
13259     int status;
13260
13261     /* Any registered moves are unregistered if unregister is set, */
13262     /* i.e. invoked by the signal handler */
13263     if (unregister) {
13264         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13265             cmailMoveRegistered[i] = FALSE;
13266             if (cmailCommentList[i] != NULL) {
13267                 free(cmailCommentList[i]);
13268                 cmailCommentList[i] = NULL;
13269             }
13270         }
13271         nCmailMovesRegistered = 0;
13272     }
13273
13274     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13275         cmailResult[i] = CMAIL_NOT_RESULT;
13276     }
13277     nCmailResults = 0;
13278
13279     if (inFilename == NULL) {
13280         /* Because the filenames are static they only get malloced once  */
13281         /* and they never get freed                                      */
13282         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
13283         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
13284
13285         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
13286         sprintf(outFilename, "%s.out", appData.cmailGameName);
13287     }
13288
13289     status = stat(outFilename, &outbuf);
13290     if (status < 0) {
13291         cmailMailedMove = FALSE;
13292     } else {
13293         status = stat(inFilename, &inbuf);
13294         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
13295     }
13296
13297     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
13298        counts the games, notes how each one terminated, etc.
13299
13300        It would be nice to remove this kludge and instead gather all
13301        the information while building the game list.  (And to keep it
13302        in the game list nodes instead of having a bunch of fixed-size
13303        parallel arrays.)  Note this will require getting each game's
13304        termination from the PGN tags, as the game list builder does
13305        not process the game moves.  --mann
13306        */
13307     cmailMsgLoaded = TRUE;
13308     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
13309
13310     /* Load first game in the file or popup game menu */
13311     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
13312
13313 #endif /* !WIN32 */
13314     return;
13315 }
13316
13317 int
13318 RegisterMove ()
13319 {
13320     FILE *f;
13321     char string[MSG_SIZ];
13322
13323     if (   cmailMailedMove
13324         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
13325         return TRUE;            /* Allow free viewing  */
13326     }
13327
13328     /* Unregister move to ensure that we don't leave RegisterMove        */
13329     /* with the move registered when the conditions for registering no   */
13330     /* longer hold                                                       */
13331     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
13332         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
13333         nCmailMovesRegistered --;
13334
13335         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
13336           {
13337               free(cmailCommentList[lastLoadGameNumber - 1]);
13338               cmailCommentList[lastLoadGameNumber - 1] = NULL;
13339           }
13340     }
13341
13342     if (cmailOldMove == -1) {
13343         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
13344         return FALSE;
13345     }
13346
13347     if (currentMove > cmailOldMove + 1) {
13348         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
13349         return FALSE;
13350     }
13351
13352     if (currentMove < cmailOldMove) {
13353         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
13354         return FALSE;
13355     }
13356
13357     if (forwardMostMove > currentMove) {
13358         /* Silently truncate extra moves */
13359         TruncateGame();
13360     }
13361
13362     if (   (currentMove == cmailOldMove + 1)
13363         || (   (currentMove == cmailOldMove)
13364             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
13365                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
13366         if (gameInfo.result != GameUnfinished) {
13367             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
13368         }
13369
13370         if (commentList[currentMove] != NULL) {
13371             cmailCommentList[lastLoadGameNumber - 1]
13372               = StrSave(commentList[currentMove]);
13373         }
13374         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
13375
13376         if (appData.debugMode)
13377           fprintf(debugFP, "Saving %s for game %d\n",
13378                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
13379
13380         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
13381
13382         f = fopen(string, "w");
13383         if (appData.oldSaveStyle) {
13384             SaveGameOldStyle(f); /* also closes the file */
13385
13386             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
13387             f = fopen(string, "w");
13388             SavePosition(f, 0, NULL); /* also closes the file */
13389         } else {
13390             fprintf(f, "{--------------\n");
13391             PrintPosition(f, currentMove);
13392             fprintf(f, "--------------}\n\n");
13393
13394             SaveGame(f, 0, NULL); /* also closes the file*/
13395         }
13396
13397         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
13398         nCmailMovesRegistered ++;
13399     } else if (nCmailGames == 1) {
13400         DisplayError(_("You have not made a move yet"), 0);
13401         return FALSE;
13402     }
13403
13404     return TRUE;
13405 }
13406
13407 void
13408 MailMoveEvent ()
13409 {
13410 #if !WIN32
13411     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
13412     FILE *commandOutput;
13413     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
13414     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
13415     int nBuffers;
13416     int i;
13417     int archived;
13418     char *arcDir;
13419
13420     if (! cmailMsgLoaded) {
13421         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
13422         return;
13423     }
13424
13425     if (nCmailGames == nCmailResults) {
13426         DisplayError(_("No unfinished games"), 0);
13427         return;
13428     }
13429
13430 #if CMAIL_PROHIBIT_REMAIL
13431     if (cmailMailedMove) {
13432       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);
13433         DisplayError(msg, 0);
13434         return;
13435     }
13436 #endif
13437
13438     if (! (cmailMailedMove || RegisterMove())) return;
13439
13440     if (   cmailMailedMove
13441         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13442       snprintf(string, MSG_SIZ, partCommandString,
13443                appData.debugMode ? " -v" : "", appData.cmailGameName);
13444         commandOutput = popen(string, "r");
13445
13446         if (commandOutput == NULL) {
13447             DisplayError(_("Failed to invoke cmail"), 0);
13448         } else {
13449             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13450                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13451             }
13452             if (nBuffers > 1) {
13453                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13454                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13455                 nBytes = MSG_SIZ - 1;
13456             } else {
13457                 (void) memcpy(msg, buffer, nBytes);
13458             }
13459             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13460
13461             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13462                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
13463
13464                 archived = TRUE;
13465                 for (i = 0; i < nCmailGames; i ++) {
13466                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
13467                         archived = FALSE;
13468                     }
13469                 }
13470                 if (   archived
13471                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13472                         != NULL)) {
13473                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13474                            arcDir,
13475                            appData.cmailGameName,
13476                            gameInfo.date);
13477                     LoadGameFromFile(buffer, 1, buffer, FALSE);
13478                     cmailMsgLoaded = FALSE;
13479                 }
13480             }
13481
13482             DisplayInformation(msg);
13483             pclose(commandOutput);
13484         }
13485     } else {
13486         if ((*cmailMsg) != '\0') {
13487             DisplayInformation(cmailMsg);
13488         }
13489     }
13490
13491     return;
13492 #endif /* !WIN32 */
13493 }
13494
13495 char *
13496 CmailMsg ()
13497 {
13498 #if WIN32
13499     return NULL;
13500 #else
13501     int  prependComma = 0;
13502     char number[5];
13503     char string[MSG_SIZ];       /* Space for game-list */
13504     int  i;
13505
13506     if (!cmailMsgLoaded) return "";
13507
13508     if (cmailMailedMove) {
13509       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13510     } else {
13511         /* Create a list of games left */
13512       snprintf(string, MSG_SIZ, "[");
13513         for (i = 0; i < nCmailGames; i ++) {
13514             if (! (   cmailMoveRegistered[i]
13515                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13516                 if (prependComma) {
13517                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13518                 } else {
13519                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13520                     prependComma = 1;
13521                 }
13522
13523                 strcat(string, number);
13524             }
13525         }
13526         strcat(string, "]");
13527
13528         if (nCmailMovesRegistered + nCmailResults == 0) {
13529             switch (nCmailGames) {
13530               case 1:
13531                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13532                 break;
13533
13534               case 2:
13535                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13536                 break;
13537
13538               default:
13539                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13540                          nCmailGames);
13541                 break;
13542             }
13543         } else {
13544             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13545               case 1:
13546                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13547                          string);
13548                 break;
13549
13550               case 0:
13551                 if (nCmailResults == nCmailGames) {
13552                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13553                 } else {
13554                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13555                 }
13556                 break;
13557
13558               default:
13559                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13560                          string);
13561             }
13562         }
13563     }
13564     return cmailMsg;
13565 #endif /* WIN32 */
13566 }
13567
13568 void
13569 ResetGameEvent ()
13570 {
13571     if (gameMode == Training)
13572       SetTrainingModeOff();
13573
13574     Reset(TRUE, TRUE);
13575     cmailMsgLoaded = FALSE;
13576     if (appData.icsActive) {
13577       SendToICS(ics_prefix);
13578       SendToICS("refresh\n");
13579     }
13580 }
13581
13582 void
13583 ExitEvent (int status)
13584 {
13585     exiting++;
13586     if (exiting > 2) {
13587       /* Give up on clean exit */
13588       exit(status);
13589     }
13590     if (exiting > 1) {
13591       /* Keep trying for clean exit */
13592       return;
13593     }
13594
13595     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
13596
13597     if (telnetISR != NULL) {
13598       RemoveInputSource(telnetISR);
13599     }
13600     if (icsPR != NoProc) {
13601       DestroyChildProcess(icsPR, TRUE);
13602     }
13603
13604     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
13605     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
13606
13607     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
13608     /* make sure this other one finishes before killing it!                  */
13609     if(endingGame) { int count = 0;
13610         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
13611         while(endingGame && count++ < 10) DoSleep(1);
13612         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
13613     }
13614
13615     /* Kill off chess programs */
13616     if (first.pr != NoProc) {
13617         ExitAnalyzeMode();
13618
13619         DoSleep( appData.delayBeforeQuit );
13620         SendToProgram("quit\n", &first);
13621         DoSleep( appData.delayAfterQuit );
13622         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
13623     }
13624     if (second.pr != NoProc) {
13625         DoSleep( appData.delayBeforeQuit );
13626         SendToProgram("quit\n", &second);
13627         DoSleep( appData.delayAfterQuit );
13628         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
13629     }
13630     if (first.isr != NULL) {
13631         RemoveInputSource(first.isr);
13632     }
13633     if (second.isr != NULL) {
13634         RemoveInputSource(second.isr);
13635     }
13636
13637     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13638     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13639
13640     ShutDownFrontEnd();
13641     exit(status);
13642 }
13643
13644 void
13645 PauseEngine (ChessProgramState *cps)
13646 {
13647     SendToProgram("pause\n", cps);
13648     cps->pause = 2;
13649 }
13650
13651 void
13652 UnPauseEngine (ChessProgramState *cps)
13653 {
13654     SendToProgram("resume\n", cps);
13655     cps->pause = 1;
13656 }
13657
13658 void
13659 PauseEvent ()
13660 {
13661     if (appData.debugMode)
13662         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13663     if (pausing) {
13664         pausing = FALSE;
13665         ModeHighlight();
13666         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
13667             StartClocks();
13668             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
13669                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
13670                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
13671             }
13672             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
13673             HandleMachineMove(stashedInputMove, stalledEngine);
13674             stalledEngine = NULL;
13675             return;
13676         }
13677         if (gameMode == MachinePlaysWhite ||
13678             gameMode == TwoMachinesPlay   ||
13679             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
13680             if(first.pause)  UnPauseEngine(&first);
13681             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
13682             if(second.pause) UnPauseEngine(&second);
13683             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
13684             StartClocks();
13685         } else {
13686             DisplayBothClocks();
13687         }
13688         if (gameMode == PlayFromGameFile) {
13689             if (appData.timeDelay >= 0)
13690                 AutoPlayGameLoop();
13691         } else if (gameMode == IcsExamining && pauseExamInvalid) {
13692             Reset(FALSE, TRUE);
13693             SendToICS(ics_prefix);
13694             SendToICS("refresh\n");
13695         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
13696             ForwardInner(forwardMostMove);
13697         }
13698         pauseExamInvalid = FALSE;
13699     } else {
13700         switch (gameMode) {
13701           default:
13702             return;
13703           case IcsExamining:
13704             pauseExamForwardMostMove = forwardMostMove;
13705             pauseExamInvalid = FALSE;
13706             /* fall through */
13707           case IcsObserving:
13708           case IcsPlayingWhite:
13709           case IcsPlayingBlack:
13710             pausing = TRUE;
13711             ModeHighlight();
13712             return;
13713           case PlayFromGameFile:
13714             (void) StopLoadGameTimer();
13715             pausing = TRUE;
13716             ModeHighlight();
13717             break;
13718           case BeginningOfGame:
13719             if (appData.icsActive) return;
13720             /* else fall through */
13721           case MachinePlaysWhite:
13722           case MachinePlaysBlack:
13723           case TwoMachinesPlay:
13724             if (forwardMostMove == 0)
13725               return;           /* don't pause if no one has moved */
13726             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
13727                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
13728                 if(onMove->pause) {           // thinking engine can be paused
13729                     PauseEngine(onMove);      // do it
13730                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
13731                         PauseEngine(onMove->other);
13732                     else
13733                         SendToProgram("easy\n", onMove->other);
13734                     StopClocks();
13735                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
13736             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
13737                 if(first.pause) {
13738                     PauseEngine(&first);
13739                     StopClocks();
13740                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
13741             } else { // human on move, pause pondering by either method
13742                 if(first.pause)
13743                     PauseEngine(&first);
13744                 else if(appData.ponderNextMove)
13745                     SendToProgram("easy\n", &first);
13746                 StopClocks();
13747             }
13748             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
13749           case AnalyzeMode:
13750             pausing = TRUE;
13751             ModeHighlight();
13752             break;
13753         }
13754     }
13755 }
13756
13757 void
13758 EditCommentEvent ()
13759 {
13760     char title[MSG_SIZ];
13761
13762     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13763       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13764     } else {
13765       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13766                WhiteOnMove(currentMove - 1) ? " " : ".. ",
13767                parseList[currentMove - 1]);
13768     }
13769
13770     EditCommentPopUp(currentMove, title, commentList[currentMove]);
13771 }
13772
13773
13774 void
13775 EditTagsEvent ()
13776 {
13777     char *tags = PGNTags(&gameInfo);
13778     bookUp = FALSE;
13779     EditTagsPopUp(tags, NULL);
13780     free(tags);
13781 }
13782
13783 void
13784 ToggleSecond ()
13785 {
13786   if(second.analyzing) {
13787     SendToProgram("exit\n", &second);
13788     second.analyzing = FALSE;
13789   } else {
13790     if (second.pr == NoProc) StartChessProgram(&second);
13791     InitChessProgram(&second, FALSE);
13792     FeedMovesToProgram(&second, currentMove);
13793
13794     SendToProgram("analyze\n", &second);
13795     second.analyzing = TRUE;
13796   }
13797 }
13798
13799 /* Toggle ShowThinking */
13800 void
13801 ToggleShowThinking()
13802 {
13803   appData.showThinking = !appData.showThinking;
13804   ShowThinkingEvent();
13805 }
13806
13807 int
13808 AnalyzeModeEvent ()
13809 {
13810     char buf[MSG_SIZ];
13811
13812     if (!first.analysisSupport) {
13813       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
13814       DisplayError(buf, 0);
13815       return 0;
13816     }
13817     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
13818     if (appData.icsActive) {
13819         if (gameMode != IcsObserving) {
13820           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
13821             DisplayError(buf, 0);
13822             /* secure check */
13823             if (appData.icsEngineAnalyze) {
13824                 if (appData.debugMode)
13825                     fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
13826                 ExitAnalyzeMode();
13827                 ModeHighlight();
13828             }
13829             return 0;
13830         }
13831         /* if enable, user wants to disable icsEngineAnalyze */
13832         if (appData.icsEngineAnalyze) {
13833                 ExitAnalyzeMode();
13834                 ModeHighlight();
13835                 return 0;
13836         }
13837         appData.icsEngineAnalyze = TRUE;
13838         if (appData.debugMode)
13839             fprintf(debugFP, "ICS engine analyze starting... \n");
13840     }
13841
13842     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
13843     if (appData.noChessProgram || gameMode == AnalyzeMode)
13844       return 0;
13845
13846     if (gameMode != AnalyzeFile) {
13847         if (!appData.icsEngineAnalyze) {
13848                EditGameEvent();
13849                if (gameMode != EditGame) return 0;
13850         }
13851         if (!appData.showThinking) ToggleShowThinking();
13852         ResurrectChessProgram();
13853         SendToProgram("analyze\n", &first);
13854         first.analyzing = TRUE;
13855         /*first.maybeThinking = TRUE;*/
13856         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13857         EngineOutputPopUp();
13858     }
13859     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
13860     pausing = FALSE;
13861     ModeHighlight();
13862     SetGameInfo();
13863
13864     StartAnalysisClock();
13865     GetTimeMark(&lastNodeCountTime);
13866     lastNodeCount = 0;
13867     return 1;
13868 }
13869
13870 void
13871 AnalyzeFileEvent ()
13872 {
13873     if (appData.noChessProgram || gameMode == AnalyzeFile)
13874       return;
13875
13876     if (!first.analysisSupport) {
13877       char buf[MSG_SIZ];
13878       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
13879       DisplayError(buf, 0);
13880       return;
13881     }
13882
13883     if (gameMode != AnalyzeMode) {
13884         keepInfo = 1; // mere annotating should not alter PGN tags
13885         EditGameEvent();
13886         keepInfo = 0;
13887         if (gameMode != EditGame) return;
13888         if (!appData.showThinking) ToggleShowThinking();
13889         ResurrectChessProgram();
13890         SendToProgram("analyze\n", &first);
13891         first.analyzing = TRUE;
13892         /*first.maybeThinking = TRUE;*/
13893         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13894         EngineOutputPopUp();
13895     }
13896     gameMode = AnalyzeFile;
13897     pausing = FALSE;
13898     ModeHighlight();
13899
13900     StartAnalysisClock();
13901     GetTimeMark(&lastNodeCountTime);
13902     lastNodeCount = 0;
13903     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
13904     AnalysisPeriodicEvent(1);
13905 }
13906
13907 void
13908 MachineWhiteEvent ()
13909 {
13910     char buf[MSG_SIZ];
13911     char *bookHit = NULL;
13912
13913     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
13914       return;
13915
13916
13917     if (gameMode == PlayFromGameFile ||
13918         gameMode == TwoMachinesPlay  ||
13919         gameMode == Training         ||
13920         gameMode == AnalyzeMode      ||
13921         gameMode == EndOfGame)
13922         EditGameEvent();
13923
13924     if (gameMode == EditPosition)
13925         EditPositionDone(TRUE);
13926
13927     if (!WhiteOnMove(currentMove)) {
13928         DisplayError(_("It is not White's turn"), 0);
13929         return;
13930     }
13931
13932     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13933       ExitAnalyzeMode();
13934
13935     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13936         gameMode == AnalyzeFile)
13937         TruncateGame();
13938
13939     ResurrectChessProgram();    /* in case it isn't running */
13940     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
13941         gameMode = MachinePlaysWhite;
13942         ResetClocks();
13943     } else
13944     gameMode = MachinePlaysWhite;
13945     pausing = FALSE;
13946     ModeHighlight();
13947     SetGameInfo();
13948     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13949     DisplayTitle(buf);
13950     if (first.sendName) {
13951       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
13952       SendToProgram(buf, &first);
13953     }
13954     if (first.sendTime) {
13955       if (first.useColors) {
13956         SendToProgram("black\n", &first); /*gnu kludge*/
13957       }
13958       SendTimeRemaining(&first, TRUE);
13959     }
13960     if (first.useColors) {
13961       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
13962     }
13963     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13964     SetMachineThinkingEnables();
13965     first.maybeThinking = TRUE;
13966     StartClocks();
13967     firstMove = FALSE;
13968
13969     if (appData.autoFlipView && !flipView) {
13970       flipView = !flipView;
13971       DrawPosition(FALSE, NULL);
13972       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13973     }
13974
13975     if(bookHit) { // [HGM] book: simulate book reply
13976         static char bookMove[MSG_SIZ]; // a bit generous?
13977
13978         programStats.nodes = programStats.depth = programStats.time =
13979         programStats.score = programStats.got_only_move = 0;
13980         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13981
13982         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13983         strcat(bookMove, bookHit);
13984         HandleMachineMove(bookMove, &first);
13985     }
13986 }
13987
13988 void
13989 MachineBlackEvent ()
13990 {
13991   char buf[MSG_SIZ];
13992   char *bookHit = NULL;
13993
13994     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
13995         return;
13996
13997
13998     if (gameMode == PlayFromGameFile ||
13999         gameMode == TwoMachinesPlay  ||
14000         gameMode == Training         ||
14001         gameMode == AnalyzeMode      ||
14002         gameMode == EndOfGame)
14003         EditGameEvent();
14004
14005     if (gameMode == EditPosition)
14006         EditPositionDone(TRUE);
14007
14008     if (WhiteOnMove(currentMove)) {
14009         DisplayError(_("It is not Black's turn"), 0);
14010         return;
14011     }
14012
14013     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14014       ExitAnalyzeMode();
14015
14016     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14017         gameMode == AnalyzeFile)
14018         TruncateGame();
14019
14020     ResurrectChessProgram();    /* in case it isn't running */
14021     gameMode = MachinePlaysBlack;
14022     pausing = FALSE;
14023     ModeHighlight();
14024     SetGameInfo();
14025     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14026     DisplayTitle(buf);
14027     if (first.sendName) {
14028       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14029       SendToProgram(buf, &first);
14030     }
14031     if (first.sendTime) {
14032       if (first.useColors) {
14033         SendToProgram("white\n", &first); /*gnu kludge*/
14034       }
14035       SendTimeRemaining(&first, FALSE);
14036     }
14037     if (first.useColors) {
14038       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14039     }
14040     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14041     SetMachineThinkingEnables();
14042     first.maybeThinking = TRUE;
14043     StartClocks();
14044
14045     if (appData.autoFlipView && flipView) {
14046       flipView = !flipView;
14047       DrawPosition(FALSE, NULL);
14048       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14049     }
14050     if(bookHit) { // [HGM] book: simulate book reply
14051         static char bookMove[MSG_SIZ]; // a bit generous?
14052
14053         programStats.nodes = programStats.depth = programStats.time =
14054         programStats.score = programStats.got_only_move = 0;
14055         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14056
14057         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14058         strcat(bookMove, bookHit);
14059         HandleMachineMove(bookMove, &first);
14060     }
14061 }
14062
14063
14064 void
14065 DisplayTwoMachinesTitle ()
14066 {
14067     char buf[MSG_SIZ];
14068     if (appData.matchGames > 0) {
14069         if(appData.tourneyFile[0]) {
14070           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14071                    gameInfo.white, _("vs."), gameInfo.black,
14072                    nextGame+1, appData.matchGames+1,
14073                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14074         } else
14075         if (first.twoMachinesColor[0] == 'w') {
14076           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14077                    gameInfo.white, _("vs."),  gameInfo.black,
14078                    first.matchWins, second.matchWins,
14079                    matchGame - 1 - (first.matchWins + second.matchWins));
14080         } else {
14081           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14082                    gameInfo.white, _("vs."), gameInfo.black,
14083                    second.matchWins, first.matchWins,
14084                    matchGame - 1 - (first.matchWins + second.matchWins));
14085         }
14086     } else {
14087       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14088     }
14089     DisplayTitle(buf);
14090 }
14091
14092 void
14093 SettingsMenuIfReady ()
14094 {
14095   if (second.lastPing != second.lastPong) {
14096     DisplayMessage("", _("Waiting for second chess program"));
14097     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14098     return;
14099   }
14100   ThawUI();
14101   DisplayMessage("", "");
14102   SettingsPopUp(&second);
14103 }
14104
14105 int
14106 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
14107 {
14108     char buf[MSG_SIZ];
14109     if (cps->pr == NoProc) {
14110         StartChessProgram(cps);
14111         if (cps->protocolVersion == 1) {
14112           retry();
14113           ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
14114         } else {
14115           /* kludge: allow timeout for initial "feature" command */
14116           if(retry != TwoMachinesEventIfReady) FreezeUI();
14117           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
14118           DisplayMessage("", buf);
14119           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
14120         }
14121         return 1;
14122     }
14123     return 0;
14124 }
14125
14126 void
14127 TwoMachinesEvent P((void))
14128 {
14129     int i;
14130     char buf[MSG_SIZ];
14131     ChessProgramState *onmove;
14132     char *bookHit = NULL;
14133     static int stalling = 0;
14134     TimeMark now;
14135     long wait;
14136
14137     if (appData.noChessProgram) return;
14138
14139     switch (gameMode) {
14140       case TwoMachinesPlay:
14141         return;
14142       case MachinePlaysWhite:
14143       case MachinePlaysBlack:
14144         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14145             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
14146             return;
14147         }
14148         /* fall through */
14149       case BeginningOfGame:
14150       case PlayFromGameFile:
14151       case EndOfGame:
14152         EditGameEvent();
14153         if (gameMode != EditGame) return;
14154         break;
14155       case EditPosition:
14156         EditPositionDone(TRUE);
14157         break;
14158       case AnalyzeMode:
14159       case AnalyzeFile:
14160         ExitAnalyzeMode();
14161         break;
14162       case EditGame:
14163       default:
14164         break;
14165     }
14166
14167 //    forwardMostMove = currentMove;
14168     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
14169     startingEngine = TRUE;
14170
14171     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
14172
14173     if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14174     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14175       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14176       return;
14177     }
14178     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14179
14180     if(second.protocolVersion >= 2 && !strstr(second.variants, VariantName(gameInfo.variant))) {
14181         startingEngine = FALSE;
14182         DisplayError("second engine does not play this", 0);
14183         return;
14184     }
14185
14186     if(!stalling) {
14187       InitChessProgram(&second, FALSE); // unbalances ping of second engine
14188       SendToProgram("force\n", &second);
14189       stalling = 1;
14190       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14191       return;
14192     }
14193     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14194     if(appData.matchPause>10000 || appData.matchPause<10)
14195                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14196     wait = SubtractTimeMarks(&now, &pauseStart);
14197     if(wait < appData.matchPause) {
14198         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14199         return;
14200     }
14201     // we are now committed to starting the game
14202     stalling = 0;
14203     DisplayMessage("", "");
14204     if (startedFromSetupPosition) {
14205         SendBoard(&second, backwardMostMove);
14206     if (appData.debugMode) {
14207         fprintf(debugFP, "Two Machines\n");
14208     }
14209     }
14210     for (i = backwardMostMove; i < forwardMostMove; i++) {
14211         SendMoveToProgram(i, &second);
14212     }
14213
14214     gameMode = TwoMachinesPlay;
14215     pausing = startingEngine = FALSE;
14216     ModeHighlight(); // [HGM] logo: this triggers display update of logos
14217     SetGameInfo();
14218     DisplayTwoMachinesTitle();
14219     firstMove = TRUE;
14220     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14221         onmove = &first;
14222     } else {
14223         onmove = &second;
14224     }
14225     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14226     SendToProgram(first.computerString, &first);
14227     if (first.sendName) {
14228       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14229       SendToProgram(buf, &first);
14230     }
14231     SendToProgram(second.computerString, &second);
14232     if (second.sendName) {
14233       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
14234       SendToProgram(buf, &second);
14235     }
14236
14237     ResetClocks();
14238     if (!first.sendTime || !second.sendTime) {
14239         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14240         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14241     }
14242     if (onmove->sendTime) {
14243       if (onmove->useColors) {
14244         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
14245       }
14246       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
14247     }
14248     if (onmove->useColors) {
14249       SendToProgram(onmove->twoMachinesColor, onmove);
14250     }
14251     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
14252 //    SendToProgram("go\n", onmove);
14253     onmove->maybeThinking = TRUE;
14254     SetMachineThinkingEnables();
14255
14256     StartClocks();
14257
14258     if(bookHit) { // [HGM] book: simulate book reply
14259         static char bookMove[MSG_SIZ]; // a bit generous?
14260
14261         programStats.nodes = programStats.depth = programStats.time =
14262         programStats.score = programStats.got_only_move = 0;
14263         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14264
14265         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14266         strcat(bookMove, bookHit);
14267         savedMessage = bookMove; // args for deferred call
14268         savedState = onmove;
14269         ScheduleDelayedEvent(DeferredBookMove, 1);
14270     }
14271 }
14272
14273 void
14274 TrainingEvent ()
14275 {
14276     if (gameMode == Training) {
14277       SetTrainingModeOff();
14278       gameMode = PlayFromGameFile;
14279       DisplayMessage("", _("Training mode off"));
14280     } else {
14281       gameMode = Training;
14282       animateTraining = appData.animate;
14283
14284       /* make sure we are not already at the end of the game */
14285       if (currentMove < forwardMostMove) {
14286         SetTrainingModeOn();
14287         DisplayMessage("", _("Training mode on"));
14288       } else {
14289         gameMode = PlayFromGameFile;
14290         DisplayError(_("Already at end of game"), 0);
14291       }
14292     }
14293     ModeHighlight();
14294 }
14295
14296 void
14297 IcsClientEvent ()
14298 {
14299     if (!appData.icsActive) return;
14300     switch (gameMode) {
14301       case IcsPlayingWhite:
14302       case IcsPlayingBlack:
14303       case IcsObserving:
14304       case IcsIdle:
14305       case BeginningOfGame:
14306       case IcsExamining:
14307         return;
14308
14309       case EditGame:
14310         break;
14311
14312       case EditPosition:
14313         EditPositionDone(TRUE);
14314         break;
14315
14316       case AnalyzeMode:
14317       case AnalyzeFile:
14318         ExitAnalyzeMode();
14319         break;
14320
14321       default:
14322         EditGameEvent();
14323         break;
14324     }
14325
14326     gameMode = IcsIdle;
14327     ModeHighlight();
14328     return;
14329 }
14330
14331 void
14332 EditGameEvent ()
14333 {
14334     int i;
14335
14336     switch (gameMode) {
14337       case Training:
14338         SetTrainingModeOff();
14339         break;
14340       case MachinePlaysWhite:
14341       case MachinePlaysBlack:
14342       case BeginningOfGame:
14343         SendToProgram("force\n", &first);
14344         SetUserThinkingEnables();
14345         break;
14346       case PlayFromGameFile:
14347         (void) StopLoadGameTimer();
14348         if (gameFileFP != NULL) {
14349             gameFileFP = NULL;
14350         }
14351         break;
14352       case EditPosition:
14353         EditPositionDone(TRUE);
14354         break;
14355       case AnalyzeMode:
14356       case AnalyzeFile:
14357         ExitAnalyzeMode();
14358         SendToProgram("force\n", &first);
14359         break;
14360       case TwoMachinesPlay:
14361         GameEnds(EndOfFile, NULL, GE_PLAYER);
14362         ResurrectChessProgram();
14363         SetUserThinkingEnables();
14364         break;
14365       case EndOfGame:
14366         ResurrectChessProgram();
14367         break;
14368       case IcsPlayingBlack:
14369       case IcsPlayingWhite:
14370         DisplayError(_("Warning: You are still playing a game"), 0);
14371         break;
14372       case IcsObserving:
14373         DisplayError(_("Warning: You are still observing a game"), 0);
14374         break;
14375       case IcsExamining:
14376         DisplayError(_("Warning: You are still examining a game"), 0);
14377         break;
14378       case IcsIdle:
14379         break;
14380       case EditGame:
14381       default:
14382         return;
14383     }
14384
14385     pausing = FALSE;
14386     StopClocks();
14387     first.offeredDraw = second.offeredDraw = 0;
14388
14389     if (gameMode == PlayFromGameFile) {
14390         whiteTimeRemaining = timeRemaining[0][currentMove];
14391         blackTimeRemaining = timeRemaining[1][currentMove];
14392         DisplayTitle("");
14393     }
14394
14395     if (gameMode == MachinePlaysWhite ||
14396         gameMode == MachinePlaysBlack ||
14397         gameMode == TwoMachinesPlay ||
14398         gameMode == EndOfGame) {
14399         i = forwardMostMove;
14400         while (i > currentMove) {
14401             SendToProgram("undo\n", &first);
14402             i--;
14403         }
14404         if(!adjustedClock) {
14405         whiteTimeRemaining = timeRemaining[0][currentMove];
14406         blackTimeRemaining = timeRemaining[1][currentMove];
14407         DisplayBothClocks();
14408         }
14409         if (whiteFlag || blackFlag) {
14410             whiteFlag = blackFlag = 0;
14411         }
14412         DisplayTitle("");
14413     }
14414
14415     gameMode = EditGame;
14416     ModeHighlight();
14417     SetGameInfo();
14418 }
14419
14420
14421 void
14422 EditPositionEvent ()
14423 {
14424     if (gameMode == EditPosition) {
14425         EditGameEvent();
14426         return;
14427     }
14428
14429     EditGameEvent();
14430     if (gameMode != EditGame) return;
14431
14432     gameMode = EditPosition;
14433     ModeHighlight();
14434     SetGameInfo();
14435     if (currentMove > 0)
14436       CopyBoard(boards[0], boards[currentMove]);
14437
14438     blackPlaysFirst = !WhiteOnMove(currentMove);
14439     ResetClocks();
14440     currentMove = forwardMostMove = backwardMostMove = 0;
14441     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14442     DisplayMove(-1);
14443     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
14444 }
14445
14446 void
14447 ExitAnalyzeMode ()
14448 {
14449     /* [DM] icsEngineAnalyze - possible call from other functions */
14450     if (appData.icsEngineAnalyze) {
14451         appData.icsEngineAnalyze = FALSE;
14452
14453         DisplayMessage("",_("Close ICS engine analyze..."));
14454     }
14455     if (first.analysisSupport && first.analyzing) {
14456       SendToBoth("exit\n");
14457       first.analyzing = second.analyzing = FALSE;
14458     }
14459     thinkOutput[0] = NULLCHAR;
14460 }
14461
14462 void
14463 EditPositionDone (Boolean fakeRights)
14464 {
14465     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
14466
14467     startedFromSetupPosition = TRUE;
14468     InitChessProgram(&first, FALSE);
14469     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
14470       boards[0][EP_STATUS] = EP_NONE;
14471       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
14472       if(boards[0][0][BOARD_WIDTH>>1] == king) {
14473         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
14474         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
14475       } else boards[0][CASTLING][2] = NoRights;
14476       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
14477         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
14478         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
14479       } else boards[0][CASTLING][5] = NoRights;
14480       if(gameInfo.variant == VariantSChess) {
14481         int i;
14482         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
14483           boards[0][VIRGIN][i] = 0;
14484           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
14485           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
14486         }
14487       }
14488     }
14489     SendToProgram("force\n", &first);
14490     if (blackPlaysFirst) {
14491         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
14492         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
14493         currentMove = forwardMostMove = backwardMostMove = 1;
14494         CopyBoard(boards[1], boards[0]);
14495     } else {
14496         currentMove = forwardMostMove = backwardMostMove = 0;
14497     }
14498     SendBoard(&first, forwardMostMove);
14499     if (appData.debugMode) {
14500         fprintf(debugFP, "EditPosDone\n");
14501     }
14502     DisplayTitle("");
14503     DisplayMessage("", "");
14504     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14505     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14506     gameMode = EditGame;
14507     ModeHighlight();
14508     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14509     ClearHighlights(); /* [AS] */
14510 }
14511
14512 /* Pause for `ms' milliseconds */
14513 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14514 void
14515 TimeDelay (long ms)
14516 {
14517     TimeMark m1, m2;
14518
14519     GetTimeMark(&m1);
14520     do {
14521         GetTimeMark(&m2);
14522     } while (SubtractTimeMarks(&m2, &m1) < ms);
14523 }
14524
14525 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14526 void
14527 SendMultiLineToICS (char *buf)
14528 {
14529     char temp[MSG_SIZ+1], *p;
14530     int len;
14531
14532     len = strlen(buf);
14533     if (len > MSG_SIZ)
14534       len = MSG_SIZ;
14535
14536     strncpy(temp, buf, len);
14537     temp[len] = 0;
14538
14539     p = temp;
14540     while (*p) {
14541         if (*p == '\n' || *p == '\r')
14542           *p = ' ';
14543         ++p;
14544     }
14545
14546     strcat(temp, "\n");
14547     SendToICS(temp);
14548     SendToPlayer(temp, strlen(temp));
14549 }
14550
14551 void
14552 SetWhiteToPlayEvent ()
14553 {
14554     if (gameMode == EditPosition) {
14555         blackPlaysFirst = FALSE;
14556         DisplayBothClocks();    /* works because currentMove is 0 */
14557     } else if (gameMode == IcsExamining) {
14558         SendToICS(ics_prefix);
14559         SendToICS("tomove white\n");
14560     }
14561 }
14562
14563 void
14564 SetBlackToPlayEvent ()
14565 {
14566     if (gameMode == EditPosition) {
14567         blackPlaysFirst = TRUE;
14568         currentMove = 1;        /* kludge */
14569         DisplayBothClocks();
14570         currentMove = 0;
14571     } else if (gameMode == IcsExamining) {
14572         SendToICS(ics_prefix);
14573         SendToICS("tomove black\n");
14574     }
14575 }
14576
14577 void
14578 EditPositionMenuEvent (ChessSquare selection, int x, int y)
14579 {
14580     char buf[MSG_SIZ];
14581     ChessSquare piece = boards[0][y][x];
14582
14583     if (gameMode != EditPosition && gameMode != IcsExamining) return;
14584
14585     switch (selection) {
14586       case ClearBoard:
14587         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
14588             SendToICS(ics_prefix);
14589             SendToICS("bsetup clear\n");
14590         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
14591             SendToICS(ics_prefix);
14592             SendToICS("clearboard\n");
14593         } else {
14594             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
14595                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
14596                 for (y = 0; y < BOARD_HEIGHT; y++) {
14597                     if (gameMode == IcsExamining) {
14598                         if (boards[currentMove][y][x] != EmptySquare) {
14599                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
14600                                     AAA + x, ONE + y);
14601                             SendToICS(buf);
14602                         }
14603                     } else {
14604                         boards[0][y][x] = p;
14605                     }
14606                 }
14607             }
14608         }
14609         if (gameMode == EditPosition) {
14610             DrawPosition(FALSE, boards[0]);
14611         }
14612         break;
14613
14614       case WhitePlay:
14615         SetWhiteToPlayEvent();
14616         break;
14617
14618       case BlackPlay:
14619         SetBlackToPlayEvent();
14620         break;
14621
14622       case EmptySquare:
14623         if (gameMode == IcsExamining) {
14624             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14625             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
14626             SendToICS(buf);
14627         } else {
14628             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14629                 if(x == BOARD_LEFT-2) {
14630                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
14631                     boards[0][y][1] = 0;
14632                 } else
14633                 if(x == BOARD_RGHT+1) {
14634                     if(y >= gameInfo.holdingsSize) break;
14635                     boards[0][y][BOARD_WIDTH-2] = 0;
14636                 } else break;
14637             }
14638             boards[0][y][x] = EmptySquare;
14639             DrawPosition(FALSE, boards[0]);
14640         }
14641         break;
14642
14643       case PromotePiece:
14644         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
14645            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
14646             selection = (ChessSquare) (PROMOTED piece);
14647         } else if(piece == EmptySquare) selection = WhiteSilver;
14648         else selection = (ChessSquare)((int)piece - 1);
14649         goto defaultlabel;
14650
14651       case DemotePiece:
14652         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
14653            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
14654             selection = (ChessSquare) (DEMOTED piece);
14655         } else if(piece == EmptySquare) selection = BlackSilver;
14656         else selection = (ChessSquare)((int)piece + 1);
14657         goto defaultlabel;
14658
14659       case WhiteQueen:
14660       case BlackQueen:
14661         if(gameInfo.variant == VariantShatranj ||
14662            gameInfo.variant == VariantXiangqi  ||
14663            gameInfo.variant == VariantCourier  ||
14664            gameInfo.variant == VariantASEAN    ||
14665            gameInfo.variant == VariantMakruk     )
14666             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
14667         goto defaultlabel;
14668
14669       case WhiteKing:
14670       case BlackKing:
14671         if(gameInfo.variant == VariantXiangqi)
14672             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
14673         if(gameInfo.variant == VariantKnightmate)
14674             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
14675       default:
14676         defaultlabel:
14677         if (gameMode == IcsExamining) {
14678             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14679             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
14680                      PieceToChar(selection), AAA + x, ONE + y);
14681             SendToICS(buf);
14682         } else {
14683             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14684                 int n;
14685                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
14686                     n = PieceToNumber(selection - BlackPawn);
14687                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
14688                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
14689                     boards[0][BOARD_HEIGHT-1-n][1]++;
14690                 } else
14691                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
14692                     n = PieceToNumber(selection);
14693                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
14694                     boards[0][n][BOARD_WIDTH-1] = selection;
14695                     boards[0][n][BOARD_WIDTH-2]++;
14696                 }
14697             } else
14698             boards[0][y][x] = selection;
14699             DrawPosition(TRUE, boards[0]);
14700             ClearHighlights();
14701             fromX = fromY = -1;
14702         }
14703         break;
14704     }
14705 }
14706
14707
14708 void
14709 DropMenuEvent (ChessSquare selection, int x, int y)
14710 {
14711     ChessMove moveType;
14712
14713     switch (gameMode) {
14714       case IcsPlayingWhite:
14715       case MachinePlaysBlack:
14716         if (!WhiteOnMove(currentMove)) {
14717             DisplayMoveError(_("It is Black's turn"));
14718             return;
14719         }
14720         moveType = WhiteDrop;
14721         break;
14722       case IcsPlayingBlack:
14723       case MachinePlaysWhite:
14724         if (WhiteOnMove(currentMove)) {
14725             DisplayMoveError(_("It is White's turn"));
14726             return;
14727         }
14728         moveType = BlackDrop;
14729         break;
14730       case EditGame:
14731         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
14732         break;
14733       default:
14734         return;
14735     }
14736
14737     if (moveType == BlackDrop && selection < BlackPawn) {
14738       selection = (ChessSquare) ((int) selection
14739                                  + (int) BlackPawn - (int) WhitePawn);
14740     }
14741     if (boards[currentMove][y][x] != EmptySquare) {
14742         DisplayMoveError(_("That square is occupied"));
14743         return;
14744     }
14745
14746     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
14747 }
14748
14749 void
14750 AcceptEvent ()
14751 {
14752     /* Accept a pending offer of any kind from opponent */
14753
14754     if (appData.icsActive) {
14755         SendToICS(ics_prefix);
14756         SendToICS("accept\n");
14757     } else if (cmailMsgLoaded) {
14758         if (currentMove == cmailOldMove &&
14759             commentList[cmailOldMove] != NULL &&
14760             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14761                    "Black offers a draw" : "White offers a draw")) {
14762             TruncateGame();
14763             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14764             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14765         } else {
14766             DisplayError(_("There is no pending offer on this move"), 0);
14767             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14768         }
14769     } else {
14770         /* Not used for offers from chess program */
14771     }
14772 }
14773
14774 void
14775 DeclineEvent ()
14776 {
14777     /* Decline a pending offer of any kind from opponent */
14778
14779     if (appData.icsActive) {
14780         SendToICS(ics_prefix);
14781         SendToICS("decline\n");
14782     } else if (cmailMsgLoaded) {
14783         if (currentMove == cmailOldMove &&
14784             commentList[cmailOldMove] != NULL &&
14785             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14786                    "Black offers a draw" : "White offers a draw")) {
14787 #ifdef NOTDEF
14788             AppendComment(cmailOldMove, "Draw declined", TRUE);
14789             DisplayComment(cmailOldMove - 1, "Draw declined");
14790 #endif /*NOTDEF*/
14791         } else {
14792             DisplayError(_("There is no pending offer on this move"), 0);
14793         }
14794     } else {
14795         /* Not used for offers from chess program */
14796     }
14797 }
14798
14799 void
14800 RematchEvent ()
14801 {
14802     /* Issue ICS rematch command */
14803     if (appData.icsActive) {
14804         SendToICS(ics_prefix);
14805         SendToICS("rematch\n");
14806     }
14807 }
14808
14809 void
14810 CallFlagEvent ()
14811 {
14812     /* Call your opponent's flag (claim a win on time) */
14813     if (appData.icsActive) {
14814         SendToICS(ics_prefix);
14815         SendToICS("flag\n");
14816     } else {
14817         switch (gameMode) {
14818           default:
14819             return;
14820           case MachinePlaysWhite:
14821             if (whiteFlag) {
14822                 if (blackFlag)
14823                   GameEnds(GameIsDrawn, "Both players ran out of time",
14824                            GE_PLAYER);
14825                 else
14826                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
14827             } else {
14828                 DisplayError(_("Your opponent is not out of time"), 0);
14829             }
14830             break;
14831           case MachinePlaysBlack:
14832             if (blackFlag) {
14833                 if (whiteFlag)
14834                   GameEnds(GameIsDrawn, "Both players ran out of time",
14835                            GE_PLAYER);
14836                 else
14837                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
14838             } else {
14839                 DisplayError(_("Your opponent is not out of time"), 0);
14840             }
14841             break;
14842         }
14843     }
14844 }
14845
14846 void
14847 ClockClick (int which)
14848 {       // [HGM] code moved to back-end from winboard.c
14849         if(which) { // black clock
14850           if (gameMode == EditPosition || gameMode == IcsExamining) {
14851             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14852             SetBlackToPlayEvent();
14853           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
14854           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
14855           } else if (shiftKey) {
14856             AdjustClock(which, -1);
14857           } else if (gameMode == IcsPlayingWhite ||
14858                      gameMode == MachinePlaysBlack) {
14859             CallFlagEvent();
14860           }
14861         } else { // white clock
14862           if (gameMode == EditPosition || gameMode == IcsExamining) {
14863             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14864             SetWhiteToPlayEvent();
14865           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
14866           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
14867           } else if (shiftKey) {
14868             AdjustClock(which, -1);
14869           } else if (gameMode == IcsPlayingBlack ||
14870                    gameMode == MachinePlaysWhite) {
14871             CallFlagEvent();
14872           }
14873         }
14874 }
14875
14876 void
14877 DrawEvent ()
14878 {
14879     /* Offer draw or accept pending draw offer from opponent */
14880
14881     if (appData.icsActive) {
14882         /* Note: tournament rules require draw offers to be
14883            made after you make your move but before you punch
14884            your clock.  Currently ICS doesn't let you do that;
14885            instead, you immediately punch your clock after making
14886            a move, but you can offer a draw at any time. */
14887
14888         SendToICS(ics_prefix);
14889         SendToICS("draw\n");
14890         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
14891     } else if (cmailMsgLoaded) {
14892         if (currentMove == cmailOldMove &&
14893             commentList[cmailOldMove] != NULL &&
14894             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14895                    "Black offers a draw" : "White offers a draw")) {
14896             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14897             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14898         } else if (currentMove == cmailOldMove + 1) {
14899             char *offer = WhiteOnMove(cmailOldMove) ?
14900               "White offers a draw" : "Black offers a draw";
14901             AppendComment(currentMove, offer, TRUE);
14902             DisplayComment(currentMove - 1, offer);
14903             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
14904         } else {
14905             DisplayError(_("You must make your move before offering a draw"), 0);
14906             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14907         }
14908     } else if (first.offeredDraw) {
14909         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
14910     } else {
14911         if (first.sendDrawOffers) {
14912             SendToProgram("draw\n", &first);
14913             userOfferedDraw = TRUE;
14914         }
14915     }
14916 }
14917
14918 void
14919 AdjournEvent ()
14920 {
14921     /* Offer Adjourn or accept pending Adjourn offer from opponent */
14922
14923     if (appData.icsActive) {
14924         SendToICS(ics_prefix);
14925         SendToICS("adjourn\n");
14926     } else {
14927         /* Currently GNU Chess doesn't offer or accept Adjourns */
14928     }
14929 }
14930
14931
14932 void
14933 AbortEvent ()
14934 {
14935     /* Offer Abort or accept pending Abort offer from opponent */
14936
14937     if (appData.icsActive) {
14938         SendToICS(ics_prefix);
14939         SendToICS("abort\n");
14940     } else {
14941         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
14942     }
14943 }
14944
14945 void
14946 ResignEvent ()
14947 {
14948     /* Resign.  You can do this even if it's not your turn. */
14949
14950     if (appData.icsActive) {
14951         SendToICS(ics_prefix);
14952         SendToICS("resign\n");
14953     } else {
14954         switch (gameMode) {
14955           case MachinePlaysWhite:
14956             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14957             break;
14958           case MachinePlaysBlack:
14959             GameEnds(BlackWins, "White resigns", GE_PLAYER);
14960             break;
14961           case EditGame:
14962             if (cmailMsgLoaded) {
14963                 TruncateGame();
14964                 if (WhiteOnMove(cmailOldMove)) {
14965                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
14966                 } else {
14967                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14968                 }
14969                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
14970             }
14971             break;
14972           default:
14973             break;
14974         }
14975     }
14976 }
14977
14978
14979 void
14980 StopObservingEvent ()
14981 {
14982     /* Stop observing current games */
14983     SendToICS(ics_prefix);
14984     SendToICS("unobserve\n");
14985 }
14986
14987 void
14988 StopExaminingEvent ()
14989 {
14990     /* Stop observing current game */
14991     SendToICS(ics_prefix);
14992     SendToICS("unexamine\n");
14993 }
14994
14995 void
14996 ForwardInner (int target)
14997 {
14998     int limit; int oldSeekGraphUp = seekGraphUp;
14999
15000     if (appData.debugMode)
15001         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
15002                 target, currentMove, forwardMostMove);
15003
15004     if (gameMode == EditPosition)
15005       return;
15006
15007     seekGraphUp = FALSE;
15008     MarkTargetSquares(1);
15009
15010     if (gameMode == PlayFromGameFile && !pausing)
15011       PauseEvent();
15012
15013     if (gameMode == IcsExamining && pausing)
15014       limit = pauseExamForwardMostMove;
15015     else
15016       limit = forwardMostMove;
15017
15018     if (target > limit) target = limit;
15019
15020     if (target > 0 && moveList[target - 1][0]) {
15021         int fromX, fromY, toX, toY;
15022         toX = moveList[target - 1][2] - AAA;
15023         toY = moveList[target - 1][3] - ONE;
15024         if (moveList[target - 1][1] == '@') {
15025             if (appData.highlightLastMove) {
15026                 SetHighlights(-1, -1, toX, toY);
15027             }
15028         } else {
15029             fromX = moveList[target - 1][0] - AAA;
15030             fromY = moveList[target - 1][1] - ONE;
15031             if (target == currentMove + 1) {
15032                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
15033             }
15034             if (appData.highlightLastMove) {
15035                 SetHighlights(fromX, fromY, toX, toY);
15036             }
15037         }
15038     }
15039     if (gameMode == EditGame || gameMode == AnalyzeMode ||
15040         gameMode == Training || gameMode == PlayFromGameFile ||
15041         gameMode == AnalyzeFile) {
15042         while (currentMove < target) {
15043             if(second.analyzing) SendMoveToProgram(currentMove, &second);
15044             SendMoveToProgram(currentMove++, &first);
15045         }
15046     } else {
15047         currentMove = target;
15048     }
15049
15050     if (gameMode == EditGame || gameMode == EndOfGame) {
15051         whiteTimeRemaining = timeRemaining[0][currentMove];
15052         blackTimeRemaining = timeRemaining[1][currentMove];
15053     }
15054     DisplayBothClocks();
15055     DisplayMove(currentMove - 1);
15056     DrawPosition(oldSeekGraphUp, boards[currentMove]);
15057     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15058     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
15059         DisplayComment(currentMove - 1, commentList[currentMove]);
15060     }
15061     ClearMap(); // [HGM] exclude: invalidate map
15062 }
15063
15064
15065 void
15066 ForwardEvent ()
15067 {
15068     if (gameMode == IcsExamining && !pausing) {
15069         SendToICS(ics_prefix);
15070         SendToICS("forward\n");
15071     } else {
15072         ForwardInner(currentMove + 1);
15073     }
15074 }
15075
15076 void
15077 ToEndEvent ()
15078 {
15079     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15080         /* to optimze, we temporarily turn off analysis mode while we feed
15081          * the remaining moves to the engine. Otherwise we get analysis output
15082          * after each move.
15083          */
15084         if (first.analysisSupport) {
15085           SendToProgram("exit\nforce\n", &first);
15086           first.analyzing = FALSE;
15087         }
15088     }
15089
15090     if (gameMode == IcsExamining && !pausing) {
15091         SendToICS(ics_prefix);
15092         SendToICS("forward 999999\n");
15093     } else {
15094         ForwardInner(forwardMostMove);
15095     }
15096
15097     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15098         /* we have fed all the moves, so reactivate analysis mode */
15099         SendToProgram("analyze\n", &first);
15100         first.analyzing = TRUE;
15101         /*first.maybeThinking = TRUE;*/
15102         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15103     }
15104 }
15105
15106 void
15107 BackwardInner (int target)
15108 {
15109     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
15110
15111     if (appData.debugMode)
15112         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
15113                 target, currentMove, forwardMostMove);
15114
15115     if (gameMode == EditPosition) return;
15116     seekGraphUp = FALSE;
15117     MarkTargetSquares(1);
15118     if (currentMove <= backwardMostMove) {
15119         ClearHighlights();
15120         DrawPosition(full_redraw, boards[currentMove]);
15121         return;
15122     }
15123     if (gameMode == PlayFromGameFile && !pausing)
15124       PauseEvent();
15125
15126     if (moveList[target][0]) {
15127         int fromX, fromY, toX, toY;
15128         toX = moveList[target][2] - AAA;
15129         toY = moveList[target][3] - ONE;
15130         if (moveList[target][1] == '@') {
15131             if (appData.highlightLastMove) {
15132                 SetHighlights(-1, -1, toX, toY);
15133             }
15134         } else {
15135             fromX = moveList[target][0] - AAA;
15136             fromY = moveList[target][1] - ONE;
15137             if (target == currentMove - 1) {
15138                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
15139             }
15140             if (appData.highlightLastMove) {
15141                 SetHighlights(fromX, fromY, toX, toY);
15142             }
15143         }
15144     }
15145     if (gameMode == EditGame || gameMode==AnalyzeMode ||
15146         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
15147         while (currentMove > target) {
15148             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
15149                 // null move cannot be undone. Reload program with move history before it.
15150                 int i;
15151                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
15152                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
15153                 }
15154                 SendBoard(&first, i);
15155               if(second.analyzing) SendBoard(&second, i);
15156                 for(currentMove=i; currentMove<target; currentMove++) {
15157                     SendMoveToProgram(currentMove, &first);
15158                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
15159                 }
15160                 break;
15161             }
15162             SendToBoth("undo\n");
15163             currentMove--;
15164         }
15165     } else {
15166         currentMove = target;
15167     }
15168
15169     if (gameMode == EditGame || gameMode == EndOfGame) {
15170         whiteTimeRemaining = timeRemaining[0][currentMove];
15171         blackTimeRemaining = timeRemaining[1][currentMove];
15172     }
15173     DisplayBothClocks();
15174     DisplayMove(currentMove - 1);
15175     DrawPosition(full_redraw, boards[currentMove]);
15176     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15177     // [HGM] PV info: routine tests if comment empty
15178     DisplayComment(currentMove - 1, commentList[currentMove]);
15179     ClearMap(); // [HGM] exclude: invalidate map
15180 }
15181
15182 void
15183 BackwardEvent ()
15184 {
15185     if (gameMode == IcsExamining && !pausing) {
15186         SendToICS(ics_prefix);
15187         SendToICS("backward\n");
15188     } else {
15189         BackwardInner(currentMove - 1);
15190     }
15191 }
15192
15193 void
15194 ToStartEvent ()
15195 {
15196     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15197         /* to optimize, we temporarily turn off analysis mode while we undo
15198          * all the moves. Otherwise we get analysis output after each undo.
15199          */
15200         if (first.analysisSupport) {
15201           SendToProgram("exit\nforce\n", &first);
15202           first.analyzing = FALSE;
15203         }
15204     }
15205
15206     if (gameMode == IcsExamining && !pausing) {
15207         SendToICS(ics_prefix);
15208         SendToICS("backward 999999\n");
15209     } else {
15210         BackwardInner(backwardMostMove);
15211     }
15212
15213     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15214         /* we have fed all the moves, so reactivate analysis mode */
15215         SendToProgram("analyze\n", &first);
15216         first.analyzing = TRUE;
15217         /*first.maybeThinking = TRUE;*/
15218         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15219     }
15220 }
15221
15222 void
15223 ToNrEvent (int to)
15224 {
15225   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
15226   if (to >= forwardMostMove) to = forwardMostMove;
15227   if (to <= backwardMostMove) to = backwardMostMove;
15228   if (to < currentMove) {
15229     BackwardInner(to);
15230   } else {
15231     ForwardInner(to);
15232   }
15233 }
15234
15235 void
15236 RevertEvent (Boolean annotate)
15237 {
15238     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
15239         return;
15240     }
15241     if (gameMode != IcsExamining) {
15242         DisplayError(_("You are not examining a game"), 0);
15243         return;
15244     }
15245     if (pausing) {
15246         DisplayError(_("You can't revert while pausing"), 0);
15247         return;
15248     }
15249     SendToICS(ics_prefix);
15250     SendToICS("revert\n");
15251 }
15252
15253 void
15254 RetractMoveEvent ()
15255 {
15256     switch (gameMode) {
15257       case MachinePlaysWhite:
15258       case MachinePlaysBlack:
15259         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15260             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
15261             return;
15262         }
15263         if (forwardMostMove < 2) return;
15264         currentMove = forwardMostMove = forwardMostMove - 2;
15265         whiteTimeRemaining = timeRemaining[0][currentMove];
15266         blackTimeRemaining = timeRemaining[1][currentMove];
15267         DisplayBothClocks();
15268         DisplayMove(currentMove - 1);
15269         ClearHighlights();/*!! could figure this out*/
15270         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
15271         SendToProgram("remove\n", &first);
15272         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
15273         break;
15274
15275       case BeginningOfGame:
15276       default:
15277         break;
15278
15279       case IcsPlayingWhite:
15280       case IcsPlayingBlack:
15281         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
15282             SendToICS(ics_prefix);
15283             SendToICS("takeback 2\n");
15284         } else {
15285             SendToICS(ics_prefix);
15286             SendToICS("takeback 1\n");
15287         }
15288         break;
15289     }
15290 }
15291
15292 void
15293 MoveNowEvent ()
15294 {
15295     ChessProgramState *cps;
15296
15297     switch (gameMode) {
15298       case MachinePlaysWhite:
15299         if (!WhiteOnMove(forwardMostMove)) {
15300             DisplayError(_("It is your turn"), 0);
15301             return;
15302         }
15303         cps = &first;
15304         break;
15305       case MachinePlaysBlack:
15306         if (WhiteOnMove(forwardMostMove)) {
15307             DisplayError(_("It is your turn"), 0);
15308             return;
15309         }
15310         cps = &first;
15311         break;
15312       case TwoMachinesPlay:
15313         if (WhiteOnMove(forwardMostMove) ==
15314             (first.twoMachinesColor[0] == 'w')) {
15315             cps = &first;
15316         } else {
15317             cps = &second;
15318         }
15319         break;
15320       case BeginningOfGame:
15321       default:
15322         return;
15323     }
15324     SendToProgram("?\n", cps);
15325 }
15326
15327 void
15328 TruncateGameEvent ()
15329 {
15330     EditGameEvent();
15331     if (gameMode != EditGame) return;
15332     TruncateGame();
15333 }
15334
15335 void
15336 TruncateGame ()
15337 {
15338     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
15339     if (forwardMostMove > currentMove) {
15340         if (gameInfo.resultDetails != NULL) {
15341             free(gameInfo.resultDetails);
15342             gameInfo.resultDetails = NULL;
15343             gameInfo.result = GameUnfinished;
15344         }
15345         forwardMostMove = currentMove;
15346         HistorySet(parseList, backwardMostMove, forwardMostMove,
15347                    currentMove-1);
15348     }
15349 }
15350
15351 void
15352 HintEvent ()
15353 {
15354     if (appData.noChessProgram) return;
15355     switch (gameMode) {
15356       case MachinePlaysWhite:
15357         if (WhiteOnMove(forwardMostMove)) {
15358             DisplayError(_("Wait until your turn"), 0);
15359             return;
15360         }
15361         break;
15362       case BeginningOfGame:
15363       case MachinePlaysBlack:
15364         if (!WhiteOnMove(forwardMostMove)) {
15365             DisplayError(_("Wait until your turn"), 0);
15366             return;
15367         }
15368         break;
15369       default:
15370         DisplayError(_("No hint available"), 0);
15371         return;
15372     }
15373     SendToProgram("hint\n", &first);
15374     hintRequested = TRUE;
15375 }
15376
15377 void
15378 CreateBookEvent ()
15379 {
15380     ListGame * lg = (ListGame *) gameList.head;
15381     FILE *f, *g;
15382     int nItem;
15383     static int secondTime = FALSE;
15384
15385     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
15386         DisplayError(_("Game list not loaded or empty"), 0);
15387         return;
15388     }
15389
15390     if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
15391         fclose(g);
15392         secondTime++;
15393         DisplayNote(_("Book file exists! Try again for overwrite."));
15394         return;
15395     }
15396
15397     creatingBook = TRUE;
15398     secondTime = FALSE;
15399
15400     /* Get list size */
15401     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
15402         LoadGame(f, nItem, "", TRUE);
15403         AddGameToBook(TRUE);
15404         lg = (ListGame *) lg->node.succ;
15405     }
15406
15407     creatingBook = FALSE;
15408     FlushBook();
15409 }
15410
15411 void
15412 BookEvent ()
15413 {
15414     if (appData.noChessProgram) return;
15415     switch (gameMode) {
15416       case MachinePlaysWhite:
15417         if (WhiteOnMove(forwardMostMove)) {
15418             DisplayError(_("Wait until your turn"), 0);
15419             return;
15420         }
15421         break;
15422       case BeginningOfGame:
15423       case MachinePlaysBlack:
15424         if (!WhiteOnMove(forwardMostMove)) {
15425             DisplayError(_("Wait until your turn"), 0);
15426             return;
15427         }
15428         break;
15429       case EditPosition:
15430         EditPositionDone(TRUE);
15431         break;
15432       case TwoMachinesPlay:
15433         return;
15434       default:
15435         break;
15436     }
15437     SendToProgram("bk\n", &first);
15438     bookOutput[0] = NULLCHAR;
15439     bookRequested = TRUE;
15440 }
15441
15442 void
15443 AboutGameEvent ()
15444 {
15445     char *tags = PGNTags(&gameInfo);
15446     TagsPopUp(tags, CmailMsg());
15447     free(tags);
15448 }
15449
15450 /* end button procedures */
15451
15452 void
15453 PrintPosition (FILE *fp, int move)
15454 {
15455     int i, j;
15456
15457     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15458         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15459             char c = PieceToChar(boards[move][i][j]);
15460             fputc(c == 'x' ? '.' : c, fp);
15461             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
15462         }
15463     }
15464     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
15465       fprintf(fp, "white to play\n");
15466     else
15467       fprintf(fp, "black to play\n");
15468 }
15469
15470 void
15471 PrintOpponents (FILE *fp)
15472 {
15473     if (gameInfo.white != NULL) {
15474         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
15475     } else {
15476         fprintf(fp, "\n");
15477     }
15478 }
15479
15480 /* Find last component of program's own name, using some heuristics */
15481 void
15482 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
15483 {
15484     char *p, *q, c;
15485     int local = (strcmp(host, "localhost") == 0);
15486     while (!local && (p = strchr(prog, ';')) != NULL) {
15487         p++;
15488         while (*p == ' ') p++;
15489         prog = p;
15490     }
15491     if (*prog == '"' || *prog == '\'') {
15492         q = strchr(prog + 1, *prog);
15493     } else {
15494         q = strchr(prog, ' ');
15495     }
15496     if (q == NULL) q = prog + strlen(prog);
15497     p = q;
15498     while (p >= prog && *p != '/' && *p != '\\') p--;
15499     p++;
15500     if(p == prog && *p == '"') p++;
15501     c = *q; *q = 0;
15502     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
15503     memcpy(buf, p, q - p);
15504     buf[q - p] = NULLCHAR;
15505     if (!local) {
15506         strcat(buf, "@");
15507         strcat(buf, host);
15508     }
15509 }
15510
15511 char *
15512 TimeControlTagValue ()
15513 {
15514     char buf[MSG_SIZ];
15515     if (!appData.clockMode) {
15516       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
15517     } else if (movesPerSession > 0) {
15518       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
15519     } else if (timeIncrement == 0) {
15520       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
15521     } else {
15522       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
15523     }
15524     return StrSave(buf);
15525 }
15526
15527 void
15528 SetGameInfo ()
15529 {
15530     /* This routine is used only for certain modes */
15531     VariantClass v = gameInfo.variant;
15532     ChessMove r = GameUnfinished;
15533     char *p = NULL;
15534
15535     if(keepInfo) return;
15536
15537     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
15538         r = gameInfo.result;
15539         p = gameInfo.resultDetails;
15540         gameInfo.resultDetails = NULL;
15541     }
15542     ClearGameInfo(&gameInfo);
15543     gameInfo.variant = v;
15544
15545     switch (gameMode) {
15546       case MachinePlaysWhite:
15547         gameInfo.event = StrSave( appData.pgnEventHeader );
15548         gameInfo.site = StrSave(HostName());
15549         gameInfo.date = PGNDate();
15550         gameInfo.round = StrSave("-");
15551         gameInfo.white = StrSave(first.tidy);
15552         gameInfo.black = StrSave(UserName());
15553         gameInfo.timeControl = TimeControlTagValue();
15554         break;
15555
15556       case MachinePlaysBlack:
15557         gameInfo.event = StrSave( appData.pgnEventHeader );
15558         gameInfo.site = StrSave(HostName());
15559         gameInfo.date = PGNDate();
15560         gameInfo.round = StrSave("-");
15561         gameInfo.white = StrSave(UserName());
15562         gameInfo.black = StrSave(first.tidy);
15563         gameInfo.timeControl = TimeControlTagValue();
15564         break;
15565
15566       case TwoMachinesPlay:
15567         gameInfo.event = StrSave( appData.pgnEventHeader );
15568         gameInfo.site = StrSave(HostName());
15569         gameInfo.date = PGNDate();
15570         if (roundNr > 0) {
15571             char buf[MSG_SIZ];
15572             snprintf(buf, MSG_SIZ, "%d", roundNr);
15573             gameInfo.round = StrSave(buf);
15574         } else {
15575             gameInfo.round = StrSave("-");
15576         }
15577         if (first.twoMachinesColor[0] == 'w') {
15578             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15579             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15580         } else {
15581             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15582             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15583         }
15584         gameInfo.timeControl = TimeControlTagValue();
15585         break;
15586
15587       case EditGame:
15588         gameInfo.event = StrSave("Edited game");
15589         gameInfo.site = StrSave(HostName());
15590         gameInfo.date = PGNDate();
15591         gameInfo.round = StrSave("-");
15592         gameInfo.white = StrSave("-");
15593         gameInfo.black = StrSave("-");
15594         gameInfo.result = r;
15595         gameInfo.resultDetails = p;
15596         break;
15597
15598       case EditPosition:
15599         gameInfo.event = StrSave("Edited position");
15600         gameInfo.site = StrSave(HostName());
15601         gameInfo.date = PGNDate();
15602         gameInfo.round = StrSave("-");
15603         gameInfo.white = StrSave("-");
15604         gameInfo.black = StrSave("-");
15605         break;
15606
15607       case IcsPlayingWhite:
15608       case IcsPlayingBlack:
15609       case IcsObserving:
15610       case IcsExamining:
15611         break;
15612
15613       case PlayFromGameFile:
15614         gameInfo.event = StrSave("Game from non-PGN file");
15615         gameInfo.site = StrSave(HostName());
15616         gameInfo.date = PGNDate();
15617         gameInfo.round = StrSave("-");
15618         gameInfo.white = StrSave("?");
15619         gameInfo.black = StrSave("?");
15620         break;
15621
15622       default:
15623         break;
15624     }
15625 }
15626
15627 void
15628 ReplaceComment (int index, char *text)
15629 {
15630     int len;
15631     char *p;
15632     float score;
15633
15634     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
15635        pvInfoList[index-1].depth == len &&
15636        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
15637        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
15638     while (*text == '\n') text++;
15639     len = strlen(text);
15640     while (len > 0 && text[len - 1] == '\n') len--;
15641
15642     if (commentList[index] != NULL)
15643       free(commentList[index]);
15644
15645     if (len == 0) {
15646         commentList[index] = NULL;
15647         return;
15648     }
15649   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
15650       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
15651       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
15652     commentList[index] = (char *) malloc(len + 2);
15653     strncpy(commentList[index], text, len);
15654     commentList[index][len] = '\n';
15655     commentList[index][len + 1] = NULLCHAR;
15656   } else {
15657     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
15658     char *p;
15659     commentList[index] = (char *) malloc(len + 7);
15660     safeStrCpy(commentList[index], "{\n", 3);
15661     safeStrCpy(commentList[index]+2, text, len+1);
15662     commentList[index][len+2] = NULLCHAR;
15663     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
15664     strcat(commentList[index], "\n}\n");
15665   }
15666 }
15667
15668 void
15669 CrushCRs (char *text)
15670 {
15671   char *p = text;
15672   char *q = text;
15673   char ch;
15674
15675   do {
15676     ch = *p++;
15677     if (ch == '\r') continue;
15678     *q++ = ch;
15679   } while (ch != '\0');
15680 }
15681
15682 void
15683 AppendComment (int index, char *text, Boolean addBraces)
15684 /* addBraces  tells if we should add {} */
15685 {
15686     int oldlen, len;
15687     char *old;
15688
15689 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
15690     if(addBraces == 3) addBraces = 0; else // force appending literally
15691     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
15692
15693     CrushCRs(text);
15694     while (*text == '\n') text++;
15695     len = strlen(text);
15696     while (len > 0 && text[len - 1] == '\n') len--;
15697     text[len] = NULLCHAR;
15698
15699     if (len == 0) return;
15700
15701     if (commentList[index] != NULL) {
15702       Boolean addClosingBrace = addBraces;
15703         old = commentList[index];
15704         oldlen = strlen(old);
15705         while(commentList[index][oldlen-1] ==  '\n')
15706           commentList[index][--oldlen] = NULLCHAR;
15707         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
15708         safeStrCpy(commentList[index], old, oldlen + len + 6);
15709         free(old);
15710         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
15711         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
15712           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
15713           while (*text == '\n') { text++; len--; }
15714           commentList[index][--oldlen] = NULLCHAR;
15715       }
15716         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
15717         else          strcat(commentList[index], "\n");
15718         strcat(commentList[index], text);
15719         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
15720         else          strcat(commentList[index], "\n");
15721     } else {
15722         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
15723         if(addBraces)
15724           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
15725         else commentList[index][0] = NULLCHAR;
15726         strcat(commentList[index], text);
15727         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
15728         if(addBraces == TRUE) strcat(commentList[index], "}\n");
15729     }
15730 }
15731
15732 static char *
15733 FindStr (char * text, char * sub_text)
15734 {
15735     char * result = strstr( text, sub_text );
15736
15737     if( result != NULL ) {
15738         result += strlen( sub_text );
15739     }
15740
15741     return result;
15742 }
15743
15744 /* [AS] Try to extract PV info from PGN comment */
15745 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
15746 char *
15747 GetInfoFromComment (int index, char * text)
15748 {
15749     char * sep = text, *p;
15750
15751     if( text != NULL && index > 0 ) {
15752         int score = 0;
15753         int depth = 0;
15754         int time = -1, sec = 0, deci;
15755         char * s_eval = FindStr( text, "[%eval " );
15756         char * s_emt = FindStr( text, "[%emt " );
15757 #if 0
15758         if( s_eval != NULL || s_emt != NULL ) {
15759 #else
15760         if(0) { // [HGM] this code is not finished, and could actually be detrimental
15761 #endif
15762             /* New style */
15763             char delim;
15764
15765             if( s_eval != NULL ) {
15766                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
15767                     return text;
15768                 }
15769
15770                 if( delim != ']' ) {
15771                     return text;
15772                 }
15773             }
15774
15775             if( s_emt != NULL ) {
15776             }
15777                 return text;
15778         }
15779         else {
15780             /* We expect something like: [+|-]nnn.nn/dd */
15781             int score_lo = 0;
15782
15783             if(*text != '{') return text; // [HGM] braces: must be normal comment
15784
15785             sep = strchr( text, '/' );
15786             if( sep == NULL || sep < (text+4) ) {
15787                 return text;
15788             }
15789
15790             p = text;
15791             if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
15792             if(p[1] == '(') { // comment starts with PV
15793                p = strchr(p, ')'); // locate end of PV
15794                if(p == NULL || sep < p+5) return text;
15795                // at this point we have something like "{(.*) +0.23/6 ..."
15796                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
15797                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
15798                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
15799             }
15800             time = -1; sec = -1; deci = -1;
15801             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
15802                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
15803                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
15804                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
15805                 return text;
15806             }
15807
15808             if( score_lo < 0 || score_lo >= 100 ) {
15809                 return text;
15810             }
15811
15812             if(sec >= 0) time = 600*time + 10*sec; else
15813             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
15814
15815             score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
15816
15817             /* [HGM] PV time: now locate end of PV info */
15818             while( *++sep >= '0' && *sep <= '9'); // strip depth
15819             if(time >= 0)
15820             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
15821             if(sec >= 0)
15822             while( *++sep >= '0' && *sep <= '9'); // strip seconds
15823             if(deci >= 0)
15824             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
15825             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
15826         }
15827
15828         if( depth <= 0 ) {
15829             return text;
15830         }
15831
15832         if( time < 0 ) {
15833             time = -1;
15834         }
15835
15836         pvInfoList[index-1].depth = depth;
15837         pvInfoList[index-1].score = score;
15838         pvInfoList[index-1].time  = 10*time; // centi-sec
15839         if(*sep == '}') *sep = 0; else *--sep = '{';
15840         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
15841     }
15842     return sep;
15843 }
15844
15845 void
15846 SendToProgram (char *message, ChessProgramState *cps)
15847 {
15848     int count, outCount, error;
15849     char buf[MSG_SIZ];
15850
15851     if (cps->pr == NoProc) return;
15852     Attention(cps);
15853
15854     if (appData.debugMode) {
15855         TimeMark now;
15856         GetTimeMark(&now);
15857         fprintf(debugFP, "%ld >%-6s: %s",
15858                 SubtractTimeMarks(&now, &programStartTime),
15859                 cps->which, message);
15860         if(serverFP)
15861             fprintf(serverFP, "%ld >%-6s: %s",
15862                 SubtractTimeMarks(&now, &programStartTime),
15863                 cps->which, message), fflush(serverFP);
15864     }
15865
15866     count = strlen(message);
15867     outCount = OutputToProcess(cps->pr, message, count, &error);
15868     if (outCount < count && !exiting
15869                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
15870       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15871       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
15872         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15873             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15874                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15875                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15876                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15877             } else {
15878                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15879                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15880                 gameInfo.result = res;
15881             }
15882             gameInfo.resultDetails = StrSave(buf);
15883         }
15884         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15885         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15886     }
15887 }
15888
15889 void
15890 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
15891 {
15892     char *end_str;
15893     char buf[MSG_SIZ];
15894     ChessProgramState *cps = (ChessProgramState *)closure;
15895
15896     if (isr != cps->isr) return; /* Killed intentionally */
15897     if (count <= 0) {
15898         if (count == 0) {
15899             RemoveInputSource(cps->isr);
15900             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
15901                     _(cps->which), cps->program);
15902             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
15903             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15904                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15905                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15906                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15907                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15908                 } else {
15909                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15910                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15911                     gameInfo.result = res;
15912                 }
15913                 gameInfo.resultDetails = StrSave(buf);
15914             }
15915             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15916             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
15917         } else {
15918             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
15919                     _(cps->which), cps->program);
15920             RemoveInputSource(cps->isr);
15921
15922             /* [AS] Program is misbehaving badly... kill it */
15923             if( count == -2 ) {
15924                 DestroyChildProcess( cps->pr, 9 );
15925                 cps->pr = NoProc;
15926             }
15927
15928             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15929         }
15930         return;
15931     }
15932
15933     if ((end_str = strchr(message, '\r')) != NULL)
15934       *end_str = NULLCHAR;
15935     if ((end_str = strchr(message, '\n')) != NULL)
15936       *end_str = NULLCHAR;
15937
15938     if (appData.debugMode) {
15939         TimeMark now; int print = 1;
15940         char *quote = ""; char c; int i;
15941
15942         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
15943                 char start = message[0];
15944                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
15945                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
15946                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
15947                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
15948                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
15949                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
15950                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
15951                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
15952                    sscanf(message, "hint: %c", &c)!=1 &&
15953                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
15954                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
15955                     print = (appData.engineComments >= 2);
15956                 }
15957                 message[0] = start; // restore original message
15958         }
15959         if(print) {
15960                 GetTimeMark(&now);
15961                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
15962                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15963                         quote,
15964                         message);
15965                 if(serverFP)
15966                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
15967                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15968                         quote,
15969                         message), fflush(serverFP);
15970         }
15971     }
15972
15973     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
15974     if (appData.icsEngineAnalyze) {
15975         if (strstr(message, "whisper") != NULL ||
15976              strstr(message, "kibitz") != NULL ||
15977             strstr(message, "tellics") != NULL) return;
15978     }
15979
15980     HandleMachineMove(message, cps);
15981 }
15982
15983
15984 void
15985 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
15986 {
15987     char buf[MSG_SIZ];
15988     int seconds;
15989
15990     if( timeControl_2 > 0 ) {
15991         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
15992             tc = timeControl_2;
15993         }
15994     }
15995     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
15996     inc /= cps->timeOdds;
15997     st  /= cps->timeOdds;
15998
15999     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
16000
16001     if (st > 0) {
16002       /* Set exact time per move, normally using st command */
16003       if (cps->stKludge) {
16004         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
16005         seconds = st % 60;
16006         if (seconds == 0) {
16007           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
16008         } else {
16009           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
16010         }
16011       } else {
16012         snprintf(buf, MSG_SIZ, "st %d\n", st);
16013       }
16014     } else {
16015       /* Set conventional or incremental time control, using level command */
16016       if (seconds == 0) {
16017         /* Note old gnuchess bug -- minutes:seconds used to not work.
16018            Fixed in later versions, but still avoid :seconds
16019            when seconds is 0. */
16020         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
16021       } else {
16022         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
16023                  seconds, inc/1000.);
16024       }
16025     }
16026     SendToProgram(buf, cps);
16027
16028     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
16029     /* Orthogonally, limit search to given depth */
16030     if (sd > 0) {
16031       if (cps->sdKludge) {
16032         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
16033       } else {
16034         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
16035       }
16036       SendToProgram(buf, cps);
16037     }
16038
16039     if(cps->nps >= 0) { /* [HGM] nps */
16040         if(cps->supportsNPS == FALSE)
16041           cps->nps = -1; // don't use if engine explicitly says not supported!
16042         else {
16043           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
16044           SendToProgram(buf, cps);
16045         }
16046     }
16047 }
16048
16049 ChessProgramState *
16050 WhitePlayer ()
16051 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
16052 {
16053     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
16054        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
16055         return &second;
16056     return &first;
16057 }
16058
16059 void
16060 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
16061 {
16062     char message[MSG_SIZ];
16063     long time, otime;
16064
16065     /* Note: this routine must be called when the clocks are stopped
16066        or when they have *just* been set or switched; otherwise
16067        it will be off by the time since the current tick started.
16068     */
16069     if (machineWhite) {
16070         time = whiteTimeRemaining / 10;
16071         otime = blackTimeRemaining / 10;
16072     } else {
16073         time = blackTimeRemaining / 10;
16074         otime = whiteTimeRemaining / 10;
16075     }
16076     /* [HGM] translate opponent's time by time-odds factor */
16077     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
16078
16079     if (time <= 0) time = 1;
16080     if (otime <= 0) otime = 1;
16081
16082     snprintf(message, MSG_SIZ, "time %ld\n", time);
16083     SendToProgram(message, cps);
16084
16085     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
16086     SendToProgram(message, cps);
16087 }
16088
16089 char *
16090 EngineDefinedVariant (ChessProgramState *cps, int n)
16091 {   // return name of n-th unknown variant that engine supports
16092     static char buf[MSG_SIZ];
16093     char *p, *s = cps->variants;
16094     if(!s) return NULL;
16095     do { // parse string from variants feature
16096       VariantClass v;
16097         p = strchr(s, ',');
16098         if(p) *p = NULLCHAR;
16099       v = StringToVariant(s);
16100       if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
16101         if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
16102             if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
16103         }
16104         if(p) *p++ = ',';
16105         if(n < 0) return buf;
16106     } while(s = p);
16107     return NULL;
16108 }
16109
16110 int
16111 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16112 {
16113   char buf[MSG_SIZ];
16114   int len = strlen(name);
16115   int val;
16116
16117   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16118     (*p) += len + 1;
16119     sscanf(*p, "%d", &val);
16120     *loc = (val != 0);
16121     while (**p && **p != ' ')
16122       (*p)++;
16123     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16124     SendToProgram(buf, cps);
16125     return TRUE;
16126   }
16127   return FALSE;
16128 }
16129
16130 int
16131 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16132 {
16133   char buf[MSG_SIZ];
16134   int len = strlen(name);
16135   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16136     (*p) += len + 1;
16137     sscanf(*p, "%d", loc);
16138     while (**p && **p != ' ') (*p)++;
16139     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16140     SendToProgram(buf, cps);
16141     return TRUE;
16142   }
16143   return FALSE;
16144 }
16145
16146 int
16147 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
16148 {
16149   char buf[MSG_SIZ];
16150   int len = strlen(name);
16151   if (strncmp((*p), name, len) == 0
16152       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
16153     (*p) += len + 2;
16154     ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
16155     sscanf(*p, "%[^\"]", *loc);
16156     while (**p && **p != '\"') (*p)++;
16157     if (**p == '\"') (*p)++;
16158     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16159     SendToProgram(buf, cps);
16160     return TRUE;
16161   }
16162   return FALSE;
16163 }
16164
16165 int
16166 ParseOption (Option *opt, ChessProgramState *cps)
16167 // [HGM] options: process the string that defines an engine option, and determine
16168 // name, type, default value, and allowed value range
16169 {
16170         char *p, *q, buf[MSG_SIZ];
16171         int n, min = (-1)<<31, max = 1<<31, def;
16172
16173         if(p = strstr(opt->name, " -spin ")) {
16174             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16175             if(max < min) max = min; // enforce consistency
16176             if(def < min) def = min;
16177             if(def > max) def = max;
16178             opt->value = def;
16179             opt->min = min;
16180             opt->max = max;
16181             opt->type = Spin;
16182         } else if((p = strstr(opt->name, " -slider "))) {
16183             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
16184             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16185             if(max < min) max = min; // enforce consistency
16186             if(def < min) def = min;
16187             if(def > max) def = max;
16188             opt->value = def;
16189             opt->min = min;
16190             opt->max = max;
16191             opt->type = Spin; // Slider;
16192         } else if((p = strstr(opt->name, " -string "))) {
16193             opt->textValue = p+9;
16194             opt->type = TextBox;
16195         } else if((p = strstr(opt->name, " -file "))) {
16196             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16197             opt->textValue = p+7;
16198             opt->type = FileName; // FileName;
16199         } else if((p = strstr(opt->name, " -path "))) {
16200             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16201             opt->textValue = p+7;
16202             opt->type = PathName; // PathName;
16203         } else if(p = strstr(opt->name, " -check ")) {
16204             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
16205             opt->value = (def != 0);
16206             opt->type = CheckBox;
16207         } else if(p = strstr(opt->name, " -combo ")) {
16208             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
16209             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
16210             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
16211             opt->value = n = 0;
16212             while(q = StrStr(q, " /// ")) {
16213                 n++; *q = 0;    // count choices, and null-terminate each of them
16214                 q += 5;
16215                 if(*q == '*') { // remember default, which is marked with * prefix
16216                     q++;
16217                     opt->value = n;
16218                 }
16219                 cps->comboList[cps->comboCnt++] = q;
16220             }
16221             cps->comboList[cps->comboCnt++] = NULL;
16222             opt->max = n + 1;
16223             opt->type = ComboBox;
16224         } else if(p = strstr(opt->name, " -button")) {
16225             opt->type = Button;
16226         } else if(p = strstr(opt->name, " -save")) {
16227             opt->type = SaveButton;
16228         } else return FALSE;
16229         *p = 0; // terminate option name
16230         // now look if the command-line options define a setting for this engine option.
16231         if(cps->optionSettings && cps->optionSettings[0])
16232             p = strstr(cps->optionSettings, opt->name); else p = NULL;
16233         if(p && (p == cps->optionSettings || p[-1] == ',')) {
16234           snprintf(buf, MSG_SIZ, "option %s", p);
16235                 if(p = strstr(buf, ",")) *p = 0;
16236                 if(q = strchr(buf, '=')) switch(opt->type) {
16237                     case ComboBox:
16238                         for(n=0; n<opt->max; n++)
16239                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
16240                         break;
16241                     case TextBox:
16242                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
16243                         break;
16244                     case Spin:
16245                     case CheckBox:
16246                         opt->value = atoi(q+1);
16247                     default:
16248                         break;
16249                 }
16250                 strcat(buf, "\n");
16251                 SendToProgram(buf, cps);
16252         }
16253         return TRUE;
16254 }
16255
16256 void
16257 FeatureDone (ChessProgramState *cps, int val)
16258 {
16259   DelayedEventCallback cb = GetDelayedEvent();
16260   if ((cb == InitBackEnd3 && cps == &first) ||
16261       (cb == SettingsMenuIfReady && cps == &second) ||
16262       (cb == LoadEngine) ||
16263       (cb == TwoMachinesEventIfReady)) {
16264     CancelDelayedEvent();
16265     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
16266   }
16267   cps->initDone = val;
16268   if(val) cps->reload = FALSE;
16269 }
16270
16271 /* Parse feature command from engine */
16272 void
16273 ParseFeatures (char *args, ChessProgramState *cps)
16274 {
16275   char *p = args;
16276   char *q = NULL;
16277   int val;
16278   char buf[MSG_SIZ];
16279
16280   for (;;) {
16281     while (*p == ' ') p++;
16282     if (*p == NULLCHAR) return;
16283
16284     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
16285     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
16286     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
16287     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
16288     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
16289     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
16290     if (BoolFeature(&p, "reuse", &val, cps)) {
16291       /* Engine can disable reuse, but can't enable it if user said no */
16292       if (!val) cps->reuse = FALSE;
16293       continue;
16294     }
16295     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
16296     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
16297       if (gameMode == TwoMachinesPlay) {
16298         DisplayTwoMachinesTitle();
16299       } else {
16300         DisplayTitle("");
16301       }
16302       continue;
16303     }
16304     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
16305     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
16306     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
16307     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
16308     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
16309     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
16310     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
16311     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
16312     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
16313     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
16314     if (IntFeature(&p, "done", &val, cps)) {
16315       FeatureDone(cps, val);
16316       continue;
16317     }
16318     /* Added by Tord: */
16319     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
16320     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
16321     /* End of additions by Tord */
16322
16323     /* [HGM] added features: */
16324     if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
16325     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
16326     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
16327     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
16328     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
16329     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
16330     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
16331     if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
16332         if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
16333         FREE(cps->option[cps->nrOptions].name);
16334         cps->option[cps->nrOptions].name = q; q = NULL;
16335         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
16336           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
16337             SendToProgram(buf, cps);
16338             continue;
16339         }
16340         if(cps->nrOptions >= MAX_OPTIONS) {
16341             cps->nrOptions--;
16342             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
16343             DisplayError(buf, 0);
16344         }
16345         continue;
16346     }
16347     /* End of additions by HGM */
16348
16349     /* unknown feature: complain and skip */
16350     q = p;
16351     while (*q && *q != '=') q++;
16352     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
16353     SendToProgram(buf, cps);
16354     p = q;
16355     if (*p == '=') {
16356       p++;
16357       if (*p == '\"') {
16358         p++;
16359         while (*p && *p != '\"') p++;
16360         if (*p == '\"') p++;
16361       } else {
16362         while (*p && *p != ' ') p++;
16363       }
16364     }
16365   }
16366
16367 }
16368
16369 void
16370 PeriodicUpdatesEvent (int newState)
16371 {
16372     if (newState == appData.periodicUpdates)
16373       return;
16374
16375     appData.periodicUpdates=newState;
16376
16377     /* Display type changes, so update it now */
16378 //    DisplayAnalysis();
16379
16380     /* Get the ball rolling again... */
16381     if (newState) {
16382         AnalysisPeriodicEvent(1);
16383         StartAnalysisClock();
16384     }
16385 }
16386
16387 void
16388 PonderNextMoveEvent (int newState)
16389 {
16390     if (newState == appData.ponderNextMove) return;
16391     if (gameMode == EditPosition) EditPositionDone(TRUE);
16392     if (newState) {
16393         SendToProgram("hard\n", &first);
16394         if (gameMode == TwoMachinesPlay) {
16395             SendToProgram("hard\n", &second);
16396         }
16397     } else {
16398         SendToProgram("easy\n", &first);
16399         thinkOutput[0] = NULLCHAR;
16400         if (gameMode == TwoMachinesPlay) {
16401             SendToProgram("easy\n", &second);
16402         }
16403     }
16404     appData.ponderNextMove = newState;
16405 }
16406
16407 void
16408 NewSettingEvent (int option, int *feature, char *command, int value)
16409 {
16410     char buf[MSG_SIZ];
16411
16412     if (gameMode == EditPosition) EditPositionDone(TRUE);
16413     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
16414     if(feature == NULL || *feature) SendToProgram(buf, &first);
16415     if (gameMode == TwoMachinesPlay) {
16416         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
16417     }
16418 }
16419
16420 void
16421 ShowThinkingEvent ()
16422 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
16423 {
16424     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
16425     int newState = appData.showThinking
16426         // [HGM] thinking: other features now need thinking output as well
16427         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
16428
16429     if (oldState == newState) return;
16430     oldState = newState;
16431     if (gameMode == EditPosition) EditPositionDone(TRUE);
16432     if (oldState) {
16433         SendToProgram("post\n", &first);
16434         if (gameMode == TwoMachinesPlay) {
16435             SendToProgram("post\n", &second);
16436         }
16437     } else {
16438         SendToProgram("nopost\n", &first);
16439         thinkOutput[0] = NULLCHAR;
16440         if (gameMode == TwoMachinesPlay) {
16441             SendToProgram("nopost\n", &second);
16442         }
16443     }
16444 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
16445 }
16446
16447 void
16448 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
16449 {
16450   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
16451   if (pr == NoProc) return;
16452   AskQuestion(title, question, replyPrefix, pr);
16453 }
16454
16455 void
16456 TypeInEvent (char firstChar)
16457 {
16458     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
16459         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
16460         gameMode == AnalyzeMode || gameMode == EditGame ||
16461         gameMode == EditPosition || gameMode == IcsExamining ||
16462         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
16463         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
16464                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
16465                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
16466         gameMode == Training) PopUpMoveDialog(firstChar);
16467 }
16468
16469 void
16470 TypeInDoneEvent (char *move)
16471 {
16472         Board board;
16473         int n, fromX, fromY, toX, toY;
16474         char promoChar;
16475         ChessMove moveType;
16476
16477         // [HGM] FENedit
16478         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
16479                 EditPositionPasteFEN(move);
16480                 return;
16481         }
16482         // [HGM] movenum: allow move number to be typed in any mode
16483         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
16484           ToNrEvent(2*n-1);
16485           return;
16486         }
16487         // undocumented kludge: allow command-line option to be typed in!
16488         // (potentially fatal, and does not implement the effect of the option.)
16489         // should only be used for options that are values on which future decisions will be made,
16490         // and definitely not on options that would be used during initialization.
16491         if(strstr(move, "!!! -") == move) {
16492             ParseArgsFromString(move+4);
16493             return;
16494         }
16495
16496       if (gameMode != EditGame && currentMove != forwardMostMove &&
16497         gameMode != Training) {
16498         DisplayMoveError(_("Displayed move is not current"));
16499       } else {
16500         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16501           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
16502         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
16503         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16504           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
16505           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
16506         } else {
16507           DisplayMoveError(_("Could not parse move"));
16508         }
16509       }
16510 }
16511
16512 void
16513 DisplayMove (int moveNumber)
16514 {
16515     char message[MSG_SIZ];
16516     char res[MSG_SIZ];
16517     char cpThinkOutput[MSG_SIZ];
16518
16519     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
16520
16521     if (moveNumber == forwardMostMove - 1 ||
16522         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16523
16524         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
16525
16526         if (strchr(cpThinkOutput, '\n')) {
16527             *strchr(cpThinkOutput, '\n') = NULLCHAR;
16528         }
16529     } else {
16530         *cpThinkOutput = NULLCHAR;
16531     }
16532
16533     /* [AS] Hide thinking from human user */
16534     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
16535         *cpThinkOutput = NULLCHAR;
16536         if( thinkOutput[0] != NULLCHAR ) {
16537             int i;
16538
16539             for( i=0; i<=hiddenThinkOutputState; i++ ) {
16540                 cpThinkOutput[i] = '.';
16541             }
16542             cpThinkOutput[i] = NULLCHAR;
16543             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
16544         }
16545     }
16546
16547     if (moveNumber == forwardMostMove - 1 &&
16548         gameInfo.resultDetails != NULL) {
16549         if (gameInfo.resultDetails[0] == NULLCHAR) {
16550           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
16551         } else {
16552           snprintf(res, MSG_SIZ, " {%s} %s",
16553                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
16554         }
16555     } else {
16556         res[0] = NULLCHAR;
16557     }
16558
16559     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16560         DisplayMessage(res, cpThinkOutput);
16561     } else {
16562       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
16563                 WhiteOnMove(moveNumber) ? " " : ".. ",
16564                 parseList[moveNumber], res);
16565         DisplayMessage(message, cpThinkOutput);
16566     }
16567 }
16568
16569 void
16570 DisplayComment (int moveNumber, char *text)
16571 {
16572     char title[MSG_SIZ];
16573
16574     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16575       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
16576     } else {
16577       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
16578               WhiteOnMove(moveNumber) ? " " : ".. ",
16579               parseList[moveNumber]);
16580     }
16581     if (text != NULL && (appData.autoDisplayComment || commentUp))
16582         CommentPopUp(title, text);
16583 }
16584
16585 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
16586  * might be busy thinking or pondering.  It can be omitted if your
16587  * gnuchess is configured to stop thinking immediately on any user
16588  * input.  However, that gnuchess feature depends on the FIONREAD
16589  * ioctl, which does not work properly on some flavors of Unix.
16590  */
16591 void
16592 Attention (ChessProgramState *cps)
16593 {
16594 #if ATTENTION
16595     if (!cps->useSigint) return;
16596     if (appData.noChessProgram || (cps->pr == NoProc)) return;
16597     switch (gameMode) {
16598       case MachinePlaysWhite:
16599       case MachinePlaysBlack:
16600       case TwoMachinesPlay:
16601       case IcsPlayingWhite:
16602       case IcsPlayingBlack:
16603       case AnalyzeMode:
16604       case AnalyzeFile:
16605         /* Skip if we know it isn't thinking */
16606         if (!cps->maybeThinking) return;
16607         if (appData.debugMode)
16608           fprintf(debugFP, "Interrupting %s\n", cps->which);
16609         InterruptChildProcess(cps->pr);
16610         cps->maybeThinking = FALSE;
16611         break;
16612       default:
16613         break;
16614     }
16615 #endif /*ATTENTION*/
16616 }
16617
16618 int
16619 CheckFlags ()
16620 {
16621     if (whiteTimeRemaining <= 0) {
16622         if (!whiteFlag) {
16623             whiteFlag = TRUE;
16624             if (appData.icsActive) {
16625                 if (appData.autoCallFlag &&
16626                     gameMode == IcsPlayingBlack && !blackFlag) {
16627                   SendToICS(ics_prefix);
16628                   SendToICS("flag\n");
16629                 }
16630             } else {
16631                 if (blackFlag) {
16632                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16633                 } else {
16634                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
16635                     if (appData.autoCallFlag) {
16636                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
16637                         return TRUE;
16638                     }
16639                 }
16640             }
16641         }
16642     }
16643     if (blackTimeRemaining <= 0) {
16644         if (!blackFlag) {
16645             blackFlag = TRUE;
16646             if (appData.icsActive) {
16647                 if (appData.autoCallFlag &&
16648                     gameMode == IcsPlayingWhite && !whiteFlag) {
16649                   SendToICS(ics_prefix);
16650                   SendToICS("flag\n");
16651                 }
16652             } else {
16653                 if (whiteFlag) {
16654                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16655                 } else {
16656                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
16657                     if (appData.autoCallFlag) {
16658                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
16659                         return TRUE;
16660                     }
16661                 }
16662             }
16663         }
16664     }
16665     return FALSE;
16666 }
16667
16668 void
16669 CheckTimeControl ()
16670 {
16671     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
16672         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
16673
16674     /*
16675      * add time to clocks when time control is achieved ([HGM] now also used for increment)
16676      */
16677     if ( !WhiteOnMove(forwardMostMove) ) {
16678         /* White made time control */
16679         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
16680         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
16681         /* [HGM] time odds: correct new time quota for time odds! */
16682                                             / WhitePlayer()->timeOdds;
16683         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
16684     } else {
16685         lastBlack -= blackTimeRemaining;
16686         /* Black made time control */
16687         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
16688                                             / WhitePlayer()->other->timeOdds;
16689         lastWhite = whiteTimeRemaining;
16690     }
16691 }
16692
16693 void
16694 DisplayBothClocks ()
16695 {
16696     int wom = gameMode == EditPosition ?
16697       !blackPlaysFirst : WhiteOnMove(currentMove);
16698     DisplayWhiteClock(whiteTimeRemaining, wom);
16699     DisplayBlackClock(blackTimeRemaining, !wom);
16700 }
16701
16702
16703 /* Timekeeping seems to be a portability nightmare.  I think everyone
16704    has ftime(), but I'm really not sure, so I'm including some ifdefs
16705    to use other calls if you don't.  Clocks will be less accurate if
16706    you have neither ftime nor gettimeofday.
16707 */
16708
16709 /* VS 2008 requires the #include outside of the function */
16710 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
16711 #include <sys/timeb.h>
16712 #endif
16713
16714 /* Get the current time as a TimeMark */
16715 void
16716 GetTimeMark (TimeMark *tm)
16717 {
16718 #if HAVE_GETTIMEOFDAY
16719
16720     struct timeval timeVal;
16721     struct timezone timeZone;
16722
16723     gettimeofday(&timeVal, &timeZone);
16724     tm->sec = (long) timeVal.tv_sec;
16725     tm->ms = (int) (timeVal.tv_usec / 1000L);
16726
16727 #else /*!HAVE_GETTIMEOFDAY*/
16728 #if HAVE_FTIME
16729
16730 // include <sys/timeb.h> / moved to just above start of function
16731     struct timeb timeB;
16732
16733     ftime(&timeB);
16734     tm->sec = (long) timeB.time;
16735     tm->ms = (int) timeB.millitm;
16736
16737 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
16738     tm->sec = (long) time(NULL);
16739     tm->ms = 0;
16740 #endif
16741 #endif
16742 }
16743
16744 /* Return the difference in milliseconds between two
16745    time marks.  We assume the difference will fit in a long!
16746 */
16747 long
16748 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
16749 {
16750     return 1000L*(tm2->sec - tm1->sec) +
16751            (long) (tm2->ms - tm1->ms);
16752 }
16753
16754
16755 /*
16756  * Code to manage the game clocks.
16757  *
16758  * In tournament play, black starts the clock and then white makes a move.
16759  * We give the human user a slight advantage if he is playing white---the
16760  * clocks don't run until he makes his first move, so it takes zero time.
16761  * Also, we don't account for network lag, so we could get out of sync
16762  * with GNU Chess's clock -- but then, referees are always right.
16763  */
16764
16765 static TimeMark tickStartTM;
16766 static long intendedTickLength;
16767
16768 long
16769 NextTickLength (long timeRemaining)
16770 {
16771     long nominalTickLength, nextTickLength;
16772
16773     if (timeRemaining > 0L && timeRemaining <= 10000L)
16774       nominalTickLength = 100L;
16775     else
16776       nominalTickLength = 1000L;
16777     nextTickLength = timeRemaining % nominalTickLength;
16778     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
16779
16780     return nextTickLength;
16781 }
16782
16783 /* Adjust clock one minute up or down */
16784 void
16785 AdjustClock (Boolean which, int dir)
16786 {
16787     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
16788     if(which) blackTimeRemaining += 60000*dir;
16789     else      whiteTimeRemaining += 60000*dir;
16790     DisplayBothClocks();
16791     adjustedClock = TRUE;
16792 }
16793
16794 /* Stop clocks and reset to a fresh time control */
16795 void
16796 ResetClocks ()
16797 {
16798     (void) StopClockTimer();
16799     if (appData.icsActive) {
16800         whiteTimeRemaining = blackTimeRemaining = 0;
16801     } else if (searchTime) {
16802         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16803         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16804     } else { /* [HGM] correct new time quote for time odds */
16805         whiteTC = blackTC = fullTimeControlString;
16806         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
16807         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
16808     }
16809     if (whiteFlag || blackFlag) {
16810         DisplayTitle("");
16811         whiteFlag = blackFlag = FALSE;
16812     }
16813     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
16814     DisplayBothClocks();
16815     adjustedClock = FALSE;
16816 }
16817
16818 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
16819
16820 /* Decrement running clock by amount of time that has passed */
16821 void
16822 DecrementClocks ()
16823 {
16824     long timeRemaining;
16825     long lastTickLength, fudge;
16826     TimeMark now;
16827
16828     if (!appData.clockMode) return;
16829     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
16830
16831     GetTimeMark(&now);
16832
16833     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16834
16835     /* Fudge if we woke up a little too soon */
16836     fudge = intendedTickLength - lastTickLength;
16837     if (fudge < 0 || fudge > FUDGE) fudge = 0;
16838
16839     if (WhiteOnMove(forwardMostMove)) {
16840         if(whiteNPS >= 0) lastTickLength = 0;
16841         timeRemaining = whiteTimeRemaining -= lastTickLength;
16842         if(timeRemaining < 0 && !appData.icsActive) {
16843             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
16844             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
16845                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
16846                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
16847             }
16848         }
16849         DisplayWhiteClock(whiteTimeRemaining - fudge,
16850                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16851     } else {
16852         if(blackNPS >= 0) lastTickLength = 0;
16853         timeRemaining = blackTimeRemaining -= lastTickLength;
16854         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
16855             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
16856             if(suddenDeath) {
16857                 blackStartMove = forwardMostMove;
16858                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
16859             }
16860         }
16861         DisplayBlackClock(blackTimeRemaining - fudge,
16862                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16863     }
16864     if (CheckFlags()) return;
16865
16866     if(twoBoards) { // count down secondary board's clocks as well
16867         activePartnerTime -= lastTickLength;
16868         partnerUp = 1;
16869         if(activePartner == 'W')
16870             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
16871         else
16872             DisplayBlackClock(activePartnerTime, TRUE);
16873         partnerUp = 0;
16874     }
16875
16876     tickStartTM = now;
16877     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
16878     StartClockTimer(intendedTickLength);
16879
16880     /* if the time remaining has fallen below the alarm threshold, sound the
16881      * alarm. if the alarm has sounded and (due to a takeback or time control
16882      * with increment) the time remaining has increased to a level above the
16883      * threshold, reset the alarm so it can sound again.
16884      */
16885
16886     if (appData.icsActive && appData.icsAlarm) {
16887
16888         /* make sure we are dealing with the user's clock */
16889         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
16890                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
16891            )) return;
16892
16893         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
16894             alarmSounded = FALSE;
16895         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
16896             PlayAlarmSound();
16897             alarmSounded = TRUE;
16898         }
16899     }
16900 }
16901
16902
16903 /* A player has just moved, so stop the previously running
16904    clock and (if in clock mode) start the other one.
16905    We redisplay both clocks in case we're in ICS mode, because
16906    ICS gives us an update to both clocks after every move.
16907    Note that this routine is called *after* forwardMostMove
16908    is updated, so the last fractional tick must be subtracted
16909    from the color that is *not* on move now.
16910 */
16911 void
16912 SwitchClocks (int newMoveNr)
16913 {
16914     long lastTickLength;
16915     TimeMark now;
16916     int flagged = FALSE;
16917
16918     GetTimeMark(&now);
16919
16920     if (StopClockTimer() && appData.clockMode) {
16921         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16922         if (!WhiteOnMove(forwardMostMove)) {
16923             if(blackNPS >= 0) lastTickLength = 0;
16924             blackTimeRemaining -= lastTickLength;
16925            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16926 //         if(pvInfoList[forwardMostMove].time == -1)
16927                  pvInfoList[forwardMostMove].time =               // use GUI time
16928                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
16929         } else {
16930            if(whiteNPS >= 0) lastTickLength = 0;
16931            whiteTimeRemaining -= lastTickLength;
16932            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16933 //         if(pvInfoList[forwardMostMove].time == -1)
16934                  pvInfoList[forwardMostMove].time =
16935                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
16936         }
16937         flagged = CheckFlags();
16938     }
16939     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
16940     CheckTimeControl();
16941
16942     if (flagged || !appData.clockMode) return;
16943
16944     switch (gameMode) {
16945       case MachinePlaysBlack:
16946       case MachinePlaysWhite:
16947       case BeginningOfGame:
16948         if (pausing) return;
16949         break;
16950
16951       case EditGame:
16952       case PlayFromGameFile:
16953       case IcsExamining:
16954         return;
16955
16956       default:
16957         break;
16958     }
16959
16960     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
16961         if(WhiteOnMove(forwardMostMove))
16962              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16963         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16964     }
16965
16966     tickStartTM = now;
16967     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16968       whiteTimeRemaining : blackTimeRemaining);
16969     StartClockTimer(intendedTickLength);
16970 }
16971
16972
16973 /* Stop both clocks */
16974 void
16975 StopClocks ()
16976 {
16977     long lastTickLength;
16978     TimeMark now;
16979
16980     if (!StopClockTimer()) return;
16981     if (!appData.clockMode) return;
16982
16983     GetTimeMark(&now);
16984
16985     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16986     if (WhiteOnMove(forwardMostMove)) {
16987         if(whiteNPS >= 0) lastTickLength = 0;
16988         whiteTimeRemaining -= lastTickLength;
16989         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
16990     } else {
16991         if(blackNPS >= 0) lastTickLength = 0;
16992         blackTimeRemaining -= lastTickLength;
16993         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
16994     }
16995     CheckFlags();
16996 }
16997
16998 /* Start clock of player on move.  Time may have been reset, so
16999    if clock is already running, stop and restart it. */
17000 void
17001 StartClocks ()
17002 {
17003     (void) StopClockTimer(); /* in case it was running already */
17004     DisplayBothClocks();
17005     if (CheckFlags()) return;
17006
17007     if (!appData.clockMode) return;
17008     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
17009
17010     GetTimeMark(&tickStartTM);
17011     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17012       whiteTimeRemaining : blackTimeRemaining);
17013
17014    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
17015     whiteNPS = blackNPS = -1;
17016     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
17017        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
17018         whiteNPS = first.nps;
17019     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
17020        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
17021         blackNPS = first.nps;
17022     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
17023         whiteNPS = second.nps;
17024     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
17025         blackNPS = second.nps;
17026     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
17027
17028     StartClockTimer(intendedTickLength);
17029 }
17030
17031 char *
17032 TimeString (long ms)
17033 {
17034     long second, minute, hour, day;
17035     char *sign = "";
17036     static char buf[32];
17037
17038     if (ms > 0 && ms <= 9900) {
17039       /* convert milliseconds to tenths, rounding up */
17040       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
17041
17042       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
17043       return buf;
17044     }
17045
17046     /* convert milliseconds to seconds, rounding up */
17047     /* use floating point to avoid strangeness of integer division
17048        with negative dividends on many machines */
17049     second = (long) floor(((double) (ms + 999L)) / 1000.0);
17050
17051     if (second < 0) {
17052         sign = "-";
17053         second = -second;
17054     }
17055
17056     day = second / (60 * 60 * 24);
17057     second = second % (60 * 60 * 24);
17058     hour = second / (60 * 60);
17059     second = second % (60 * 60);
17060     minute = second / 60;
17061     second = second % 60;
17062
17063     if (day > 0)
17064       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
17065               sign, day, hour, minute, second);
17066     else if (hour > 0)
17067       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
17068     else
17069       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
17070
17071     return buf;
17072 }
17073
17074
17075 /*
17076  * This is necessary because some C libraries aren't ANSI C compliant yet.
17077  */
17078 char *
17079 StrStr (char *string, char *match)
17080 {
17081     int i, length;
17082
17083     length = strlen(match);
17084
17085     for (i = strlen(string) - length; i >= 0; i--, string++)
17086       if (!strncmp(match, string, length))
17087         return string;
17088
17089     return NULL;
17090 }
17091
17092 char *
17093 StrCaseStr (char *string, char *match)
17094 {
17095     int i, j, length;
17096
17097     length = strlen(match);
17098
17099     for (i = strlen(string) - length; i >= 0; i--, string++) {
17100         for (j = 0; j < length; j++) {
17101             if (ToLower(match[j]) != ToLower(string[j]))
17102               break;
17103         }
17104         if (j == length) return string;
17105     }
17106
17107     return NULL;
17108 }
17109
17110 #ifndef _amigados
17111 int
17112 StrCaseCmp (char *s1, char *s2)
17113 {
17114     char c1, c2;
17115
17116     for (;;) {
17117         c1 = ToLower(*s1++);
17118         c2 = ToLower(*s2++);
17119         if (c1 > c2) return 1;
17120         if (c1 < c2) return -1;
17121         if (c1 == NULLCHAR) return 0;
17122     }
17123 }
17124
17125
17126 int
17127 ToLower (int c)
17128 {
17129     return isupper(c) ? tolower(c) : c;
17130 }
17131
17132
17133 int
17134 ToUpper (int c)
17135 {
17136     return islower(c) ? toupper(c) : c;
17137 }
17138 #endif /* !_amigados    */
17139
17140 char *
17141 StrSave (char *s)
17142 {
17143   char *ret;
17144
17145   if ((ret = (char *) malloc(strlen(s) + 1)))
17146     {
17147       safeStrCpy(ret, s, strlen(s)+1);
17148     }
17149   return ret;
17150 }
17151
17152 char *
17153 StrSavePtr (char *s, char **savePtr)
17154 {
17155     if (*savePtr) {
17156         free(*savePtr);
17157     }
17158     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
17159       safeStrCpy(*savePtr, s, strlen(s)+1);
17160     }
17161     return(*savePtr);
17162 }
17163
17164 char *
17165 PGNDate ()
17166 {
17167     time_t clock;
17168     struct tm *tm;
17169     char buf[MSG_SIZ];
17170
17171     clock = time((time_t *)NULL);
17172     tm = localtime(&clock);
17173     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
17174             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
17175     return StrSave(buf);
17176 }
17177
17178
17179 char *
17180 PositionToFEN (int move, char *overrideCastling, int moveCounts)
17181 {
17182     int i, j, fromX, fromY, toX, toY;
17183     int whiteToPlay;
17184     char buf[MSG_SIZ];
17185     char *p, *q;
17186     int emptycount;
17187     ChessSquare piece;
17188
17189     whiteToPlay = (gameMode == EditPosition) ?
17190       !blackPlaysFirst : (move % 2 == 0);
17191     p = buf;
17192
17193     /* Piece placement data */
17194     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17195         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
17196         emptycount = 0;
17197         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
17198             if (boards[move][i][j] == EmptySquare) {
17199                 emptycount++;
17200             } else { ChessSquare piece = boards[move][i][j];
17201                 if (emptycount > 0) {
17202                     if(emptycount<10) /* [HGM] can be >= 10 */
17203                         *p++ = '0' + emptycount;
17204                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17205                     emptycount = 0;
17206                 }
17207                 if(PieceToChar(piece) == '+') {
17208                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
17209                     *p++ = '+';
17210                     piece = (ChessSquare)(DEMOTED piece);
17211                 }
17212                 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
17213                 if(p[-1] == '~') {
17214                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
17215                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
17216                     *p++ = '~';
17217                 }
17218             }
17219         }
17220         if (emptycount > 0) {
17221             if(emptycount<10) /* [HGM] can be >= 10 */
17222                 *p++ = '0' + emptycount;
17223             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17224             emptycount = 0;
17225         }
17226         *p++ = '/';
17227     }
17228     *(p - 1) = ' ';
17229
17230     /* [HGM] print Crazyhouse or Shogi holdings */
17231     if( gameInfo.holdingsWidth ) {
17232         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
17233         q = p;
17234         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
17235             piece = boards[move][i][BOARD_WIDTH-1];
17236             if( piece != EmptySquare )
17237               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
17238                   *p++ = PieceToChar(piece);
17239         }
17240         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
17241             piece = boards[move][BOARD_HEIGHT-i-1][0];
17242             if( piece != EmptySquare )
17243               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
17244                   *p++ = PieceToChar(piece);
17245         }
17246
17247         if( q == p ) *p++ = '-';
17248         *p++ = ']';
17249         *p++ = ' ';
17250     }
17251
17252     /* Active color */
17253     *p++ = whiteToPlay ? 'w' : 'b';
17254     *p++ = ' ';
17255
17256   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
17257     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
17258   } else {
17259   if(nrCastlingRights) {
17260      q = p;
17261      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
17262        /* [HGM] write directly from rights */
17263            if(boards[move][CASTLING][2] != NoRights &&
17264               boards[move][CASTLING][0] != NoRights   )
17265                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
17266            if(boards[move][CASTLING][2] != NoRights &&
17267               boards[move][CASTLING][1] != NoRights   )
17268                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
17269            if(boards[move][CASTLING][5] != NoRights &&
17270               boards[move][CASTLING][3] != NoRights   )
17271                 *p++ = boards[move][CASTLING][3] + AAA;
17272            if(boards[move][CASTLING][5] != NoRights &&
17273               boards[move][CASTLING][4] != NoRights   )
17274                 *p++ = boards[move][CASTLING][4] + AAA;
17275      } else {
17276
17277         /* [HGM] write true castling rights */
17278         if( nrCastlingRights == 6 ) {
17279             int q, k=0;
17280             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
17281                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
17282             q = (boards[move][CASTLING][1] == BOARD_LEFT &&
17283                  boards[move][CASTLING][2] != NoRights  );
17284             if(gameInfo.variant == VariantSChess) { // for S-Chess, indicate all vrgin backrank pieces
17285                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_RGHT]; // count white held pieces
17286                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17287                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
17288                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
17289             }
17290             if(q) *p++ = 'Q';
17291             k = 0;
17292             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
17293                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
17294             q = (boards[move][CASTLING][4] == BOARD_LEFT &&
17295                  boards[move][CASTLING][5] != NoRights  );
17296             if(gameInfo.variant == VariantSChess) {
17297                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_LEFT-1]; // count black held pieces
17298                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17299                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
17300                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17301             }
17302             if(q) *p++ = 'q';
17303         }
17304      }
17305      if (q == p) *p++ = '-'; /* No castling rights */
17306      *p++ = ' ';
17307   }
17308
17309   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17310      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17311      gameInfo.variant != VariantMakruk   && gameInfo.variant != VariantASEAN ) {
17312     /* En passant target square */
17313     if (move > backwardMostMove) {
17314         fromX = moveList[move - 1][0] - AAA;
17315         fromY = moveList[move - 1][1] - ONE;
17316         toX = moveList[move - 1][2] - AAA;
17317         toY = moveList[move - 1][3] - ONE;
17318         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
17319             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
17320             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
17321             fromX == toX) {
17322             /* 2-square pawn move just happened */
17323             *p++ = toX + AAA;
17324             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17325         } else {
17326             *p++ = '-';
17327         }
17328     } else if(move == backwardMostMove) {
17329         // [HGM] perhaps we should always do it like this, and forget the above?
17330         if((signed char)boards[move][EP_STATUS] >= 0) {
17331             *p++ = boards[move][EP_STATUS] + AAA;
17332             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17333         } else {
17334             *p++ = '-';
17335         }
17336     } else {
17337         *p++ = '-';
17338     }
17339     *p++ = ' ';
17340   }
17341   }
17342
17343     if(moveCounts)
17344     {   int i = 0, j=move;
17345
17346         /* [HGM] find reversible plies */
17347         if (appData.debugMode) { int k;
17348             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
17349             for(k=backwardMostMove; k<=forwardMostMove; k++)
17350                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
17351
17352         }
17353
17354         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
17355         if( j == backwardMostMove ) i += initialRulePlies;
17356         sprintf(p, "%d ", i);
17357         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
17358
17359         /* Fullmove number */
17360         sprintf(p, "%d", (move / 2) + 1);
17361     } else *--p = NULLCHAR;
17362
17363     return StrSave(buf);
17364 }
17365
17366 Boolean
17367 ParseFEN (Board board, int *blackPlaysFirst, char *fen)
17368 {
17369     int i, j;
17370     char *p, c;
17371     int emptycount, virgin[BOARD_FILES];
17372     ChessSquare piece;
17373
17374     p = fen;
17375
17376     /* [HGM] by default clear Crazyhouse holdings, if present */
17377     if(gameInfo.holdingsWidth) {
17378        for(i=0; i<BOARD_HEIGHT; i++) {
17379            board[i][0]             = EmptySquare; /* black holdings */
17380            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
17381            board[i][1]             = (ChessSquare) 0; /* black counts */
17382            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
17383        }
17384     }
17385
17386     /* Piece placement data */
17387     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17388         j = 0;
17389         for (;;) {
17390             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
17391                 if (*p == '/') p++;
17392                 emptycount = gameInfo.boardWidth - j;
17393                 while (emptycount--)
17394                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17395                 break;
17396 #if(BOARD_FILES >= 10)
17397             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
17398                 p++; emptycount=10;
17399                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17400                 while (emptycount--)
17401                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17402 #endif
17403             } else if (*p == '*') {
17404                 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
17405             } else if (isdigit(*p)) {
17406                 emptycount = *p++ - '0';
17407                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
17408                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17409                 while (emptycount--)
17410                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17411             } else if (*p == '+' || isalpha(*p)) {
17412                 if (j >= gameInfo.boardWidth) return FALSE;
17413                 if(*p=='+') {
17414                     piece = CharToPiece(*++p);
17415                     if(piece == EmptySquare) return FALSE; /* unknown piece */
17416                     piece = (ChessSquare) (PROMOTED piece ); p++;
17417                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
17418                 } else piece = CharToPiece(*p++);
17419
17420                 if(piece==EmptySquare) return FALSE; /* unknown piece */
17421                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
17422                     piece = (ChessSquare) (PROMOTED piece);
17423                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
17424                     p++;
17425                 }
17426                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
17427             } else {
17428                 return FALSE;
17429             }
17430         }
17431     }
17432     while (*p == '/' || *p == ' ') p++;
17433
17434     /* [HGM] look for Crazyhouse holdings here */
17435     while(*p==' ') p++;
17436     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
17437         if(*p == '[') p++;
17438         if(*p == '-' ) p++; /* empty holdings */ else {
17439             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
17440             /* if we would allow FEN reading to set board size, we would   */
17441             /* have to add holdings and shift the board read so far here   */
17442             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
17443                 p++;
17444                 if((int) piece >= (int) BlackPawn ) {
17445                     i = (int)piece - (int)BlackPawn;
17446                     i = PieceToNumber((ChessSquare)i);
17447                     if( i >= gameInfo.holdingsSize ) return FALSE;
17448                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
17449                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
17450                 } else {
17451                     i = (int)piece - (int)WhitePawn;
17452                     i = PieceToNumber((ChessSquare)i);
17453                     if( i >= gameInfo.holdingsSize ) return FALSE;
17454                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
17455                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
17456                 }
17457             }
17458         }
17459         if(*p == ']') p++;
17460     }
17461
17462     while(*p == ' ') p++;
17463
17464     /* Active color */
17465     c = *p++;
17466     if(appData.colorNickNames) {
17467       if( c == appData.colorNickNames[0] ) c = 'w'; else
17468       if( c == appData.colorNickNames[1] ) c = 'b';
17469     }
17470     switch (c) {
17471       case 'w':
17472         *blackPlaysFirst = FALSE;
17473         break;
17474       case 'b':
17475         *blackPlaysFirst = TRUE;
17476         break;
17477       default:
17478         return FALSE;
17479     }
17480
17481     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
17482     /* return the extra info in global variiables             */
17483
17484     /* set defaults in case FEN is incomplete */
17485     board[EP_STATUS] = EP_UNKNOWN;
17486     for(i=0; i<nrCastlingRights; i++ ) {
17487         board[CASTLING][i] =
17488             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
17489     }   /* assume possible unless obviously impossible */
17490     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
17491     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
17492     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
17493                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
17494     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
17495     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
17496     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
17497                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
17498     FENrulePlies = 0;
17499
17500     while(*p==' ') p++;
17501     if(nrCastlingRights) {
17502       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
17503       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
17504           /* castling indicator present, so default becomes no castlings */
17505           for(i=0; i<nrCastlingRights; i++ ) {
17506                  board[CASTLING][i] = NoRights;
17507           }
17508       }
17509       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
17510              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess) &&
17511              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
17512              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
17513         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
17514
17515         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
17516             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
17517             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
17518         }
17519         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
17520             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
17521         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
17522                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
17523         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
17524                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
17525         switch(c) {
17526           case'K':
17527               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
17528               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
17529               board[CASTLING][2] = whiteKingFile;
17530               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
17531               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17532               break;
17533           case'Q':
17534               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
17535               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
17536               board[CASTLING][2] = whiteKingFile;
17537               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
17538               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17539               break;
17540           case'k':
17541               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
17542               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
17543               board[CASTLING][5] = blackKingFile;
17544               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
17545               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17546               break;
17547           case'q':
17548               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
17549               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
17550               board[CASTLING][5] = blackKingFile;
17551               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
17552               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17553           case '-':
17554               break;
17555           default: /* FRC castlings */
17556               if(c >= 'a') { /* black rights */
17557                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
17558                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17559                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
17560                   if(i == BOARD_RGHT) break;
17561                   board[CASTLING][5] = i;
17562                   c -= AAA;
17563                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
17564                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
17565                   if(c > i)
17566                       board[CASTLING][3] = c;
17567                   else
17568                       board[CASTLING][4] = c;
17569               } else { /* white rights */
17570                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
17571                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17572                     if(board[0][i] == WhiteKing) break;
17573                   if(i == BOARD_RGHT) break;
17574                   board[CASTLING][2] = i;
17575                   c -= AAA - 'a' + 'A';
17576                   if(board[0][c] >= WhiteKing) break;
17577                   if(c > i)
17578                       board[CASTLING][0] = c;
17579                   else
17580                       board[CASTLING][1] = c;
17581               }
17582         }
17583       }
17584       for(i=0; i<nrCastlingRights; i++)
17585         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
17586       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = virgin[i];
17587     if (appData.debugMode) {
17588         fprintf(debugFP, "FEN castling rights:");
17589         for(i=0; i<nrCastlingRights; i++)
17590         fprintf(debugFP, " %d", board[CASTLING][i]);
17591         fprintf(debugFP, "\n");
17592     }
17593
17594       while(*p==' ') p++;
17595     }
17596
17597     /* read e.p. field in games that know e.p. capture */
17598     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17599        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17600        gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
17601       if(*p=='-') {
17602         p++; board[EP_STATUS] = EP_NONE;
17603       } else {
17604          char c = *p++ - AAA;
17605
17606          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
17607          if(*p >= '0' && *p <='9') p++;
17608          board[EP_STATUS] = c;
17609       }
17610     }
17611
17612
17613     if(sscanf(p, "%d", &i) == 1) {
17614         FENrulePlies = i; /* 50-move ply counter */
17615         /* (The move number is still ignored)    */
17616     }
17617
17618     return TRUE;
17619 }
17620
17621 void
17622 EditPositionPasteFEN (char *fen)
17623 {
17624   if (fen != NULL) {
17625     Board initial_position;
17626
17627     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
17628       DisplayError(_("Bad FEN position in clipboard"), 0);
17629       return ;
17630     } else {
17631       int savedBlackPlaysFirst = blackPlaysFirst;
17632       EditPositionEvent();
17633       blackPlaysFirst = savedBlackPlaysFirst;
17634       CopyBoard(boards[0], initial_position);
17635       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
17636       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
17637       DisplayBothClocks();
17638       DrawPosition(FALSE, boards[currentMove]);
17639     }
17640   }
17641 }
17642
17643 static char cseq[12] = "\\   ";
17644
17645 Boolean
17646 set_cont_sequence (char *new_seq)
17647 {
17648     int len;
17649     Boolean ret;
17650
17651     // handle bad attempts to set the sequence
17652         if (!new_seq)
17653                 return 0; // acceptable error - no debug
17654
17655     len = strlen(new_seq);
17656     ret = (len > 0) && (len < sizeof(cseq));
17657     if (ret)
17658       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
17659     else if (appData.debugMode)
17660       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
17661     return ret;
17662 }
17663
17664 /*
17665     reformat a source message so words don't cross the width boundary.  internal
17666     newlines are not removed.  returns the wrapped size (no null character unless
17667     included in source message).  If dest is NULL, only calculate the size required
17668     for the dest buffer.  lp argument indicats line position upon entry, and it's
17669     passed back upon exit.
17670 */
17671 int
17672 wrap (char *dest, char *src, int count, int width, int *lp)
17673 {
17674     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
17675
17676     cseq_len = strlen(cseq);
17677     old_line = line = *lp;
17678     ansi = len = clen = 0;
17679
17680     for (i=0; i < count; i++)
17681     {
17682         if (src[i] == '\033')
17683             ansi = 1;
17684
17685         // if we hit the width, back up
17686         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
17687         {
17688             // store i & len in case the word is too long
17689             old_i = i, old_len = len;
17690
17691             // find the end of the last word
17692             while (i && src[i] != ' ' && src[i] != '\n')
17693             {
17694                 i--;
17695                 len--;
17696             }
17697
17698             // word too long?  restore i & len before splitting it
17699             if ((old_i-i+clen) >= width)
17700             {
17701                 i = old_i;
17702                 len = old_len;
17703             }
17704
17705             // extra space?
17706             if (i && src[i-1] == ' ')
17707                 len--;
17708
17709             if (src[i] != ' ' && src[i] != '\n')
17710             {
17711                 i--;
17712                 if (len)
17713                     len--;
17714             }
17715
17716             // now append the newline and continuation sequence
17717             if (dest)
17718                 dest[len] = '\n';
17719             len++;
17720             if (dest)
17721                 strncpy(dest+len, cseq, cseq_len);
17722             len += cseq_len;
17723             line = cseq_len;
17724             clen = cseq_len;
17725             continue;
17726         }
17727
17728         if (dest)
17729             dest[len] = src[i];
17730         len++;
17731         if (!ansi)
17732             line++;
17733         if (src[i] == '\n')
17734             line = 0;
17735         if (src[i] == 'm')
17736             ansi = 0;
17737     }
17738     if (dest && appData.debugMode)
17739     {
17740         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
17741             count, width, line, len, *lp);
17742         show_bytes(debugFP, src, count);
17743         fprintf(debugFP, "\ndest: ");
17744         show_bytes(debugFP, dest, len);
17745         fprintf(debugFP, "\n");
17746     }
17747     *lp = dest ? line : old_line;
17748
17749     return len;
17750 }
17751
17752 // [HGM] vari: routines for shelving variations
17753 Boolean modeRestore = FALSE;
17754
17755 void
17756 PushInner (int firstMove, int lastMove)
17757 {
17758         int i, j, nrMoves = lastMove - firstMove;
17759
17760         // push current tail of game on stack
17761         savedResult[storedGames] = gameInfo.result;
17762         savedDetails[storedGames] = gameInfo.resultDetails;
17763         gameInfo.resultDetails = NULL;
17764         savedFirst[storedGames] = firstMove;
17765         savedLast [storedGames] = lastMove;
17766         savedFramePtr[storedGames] = framePtr;
17767         framePtr -= nrMoves; // reserve space for the boards
17768         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
17769             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
17770             for(j=0; j<MOVE_LEN; j++)
17771                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
17772             for(j=0; j<2*MOVE_LEN; j++)
17773                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
17774             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
17775             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
17776             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
17777             pvInfoList[firstMove+i-1].depth = 0;
17778             commentList[framePtr+i] = commentList[firstMove+i];
17779             commentList[firstMove+i] = NULL;
17780         }
17781
17782         storedGames++;
17783         forwardMostMove = firstMove; // truncate game so we can start variation
17784 }
17785
17786 void
17787 PushTail (int firstMove, int lastMove)
17788 {
17789         if(appData.icsActive) { // only in local mode
17790                 forwardMostMove = currentMove; // mimic old ICS behavior
17791                 return;
17792         }
17793         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
17794
17795         PushInner(firstMove, lastMove);
17796         if(storedGames == 1) GreyRevert(FALSE);
17797         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
17798 }
17799
17800 void
17801 PopInner (Boolean annotate)
17802 {
17803         int i, j, nrMoves;
17804         char buf[8000], moveBuf[20];
17805
17806         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
17807         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
17808         nrMoves = savedLast[storedGames] - currentMove;
17809         if(annotate) {
17810                 int cnt = 10;
17811                 if(!WhiteOnMove(currentMove))
17812                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
17813                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
17814                 for(i=currentMove; i<forwardMostMove; i++) {
17815                         if(WhiteOnMove(i))
17816                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
17817                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
17818                         strcat(buf, moveBuf);
17819                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
17820                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
17821                 }
17822                 strcat(buf, ")");
17823         }
17824         for(i=1; i<=nrMoves; i++) { // copy last variation back
17825             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
17826             for(j=0; j<MOVE_LEN; j++)
17827                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
17828             for(j=0; j<2*MOVE_LEN; j++)
17829                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
17830             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
17831             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
17832             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
17833             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
17834             commentList[currentMove+i] = commentList[framePtr+i];
17835             commentList[framePtr+i] = NULL;
17836         }
17837         if(annotate) AppendComment(currentMove+1, buf, FALSE);
17838         framePtr = savedFramePtr[storedGames];
17839         gameInfo.result = savedResult[storedGames];
17840         if(gameInfo.resultDetails != NULL) {
17841             free(gameInfo.resultDetails);
17842       }
17843         gameInfo.resultDetails = savedDetails[storedGames];
17844         forwardMostMove = currentMove + nrMoves;
17845 }
17846
17847 Boolean
17848 PopTail (Boolean annotate)
17849 {
17850         if(appData.icsActive) return FALSE; // only in local mode
17851         if(!storedGames) return FALSE; // sanity
17852         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
17853
17854         PopInner(annotate);
17855         if(currentMove < forwardMostMove) ForwardEvent(); else
17856         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
17857
17858         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
17859         return TRUE;
17860 }
17861
17862 void
17863 CleanupTail ()
17864 {       // remove all shelved variations
17865         int i;
17866         for(i=0; i<storedGames; i++) {
17867             if(savedDetails[i])
17868                 free(savedDetails[i]);
17869             savedDetails[i] = NULL;
17870         }
17871         for(i=framePtr; i<MAX_MOVES; i++) {
17872                 if(commentList[i]) free(commentList[i]);
17873                 commentList[i] = NULL;
17874         }
17875         framePtr = MAX_MOVES-1;
17876         storedGames = 0;
17877 }
17878
17879 void
17880 LoadVariation (int index, char *text)
17881 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
17882         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
17883         int level = 0, move;
17884
17885         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
17886         // first find outermost bracketing variation
17887         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
17888             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
17889                 if(*p == '{') wait = '}'; else
17890                 if(*p == '[') wait = ']'; else
17891                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
17892                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
17893             }
17894             if(*p == wait) wait = NULLCHAR; // closing ]} found
17895             p++;
17896         }
17897         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
17898         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
17899         end[1] = NULLCHAR; // clip off comment beyond variation
17900         ToNrEvent(currentMove-1);
17901         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
17902         // kludge: use ParsePV() to append variation to game
17903         move = currentMove;
17904         ParsePV(start, TRUE, TRUE);
17905         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
17906         ClearPremoveHighlights();
17907         CommentPopDown();
17908         ToNrEvent(currentMove+1);
17909 }
17910
17911 void
17912 LoadTheme ()
17913 {
17914     char *p, *q, buf[MSG_SIZ];
17915     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
17916         snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
17917         ParseArgsFromString(buf);
17918         ActivateTheme(TRUE); // also redo colors
17919         return;
17920     }
17921     p = nickName;
17922     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
17923     {
17924         int len;
17925         q = appData.themeNames;
17926         snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
17927       if(appData.useBitmaps) {
17928         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
17929                 appData.liteBackTextureFile, appData.darkBackTextureFile,
17930                 appData.liteBackTextureMode,
17931                 appData.darkBackTextureMode );
17932       } else {
17933         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
17934                 Col2Text(2),   // lightSquareColor
17935                 Col2Text(3) ); // darkSquareColor
17936       }
17937       if(appData.useBorder) {
17938         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
17939                 appData.border);
17940       } else {
17941         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
17942       }
17943       if(appData.useFont) {
17944         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
17945                 appData.renderPiecesWithFont,
17946                 appData.fontToPieceTable,
17947                 Col2Text(9),    // appData.fontBackColorWhite
17948                 Col2Text(10) ); // appData.fontForeColorBlack
17949       } else {
17950         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
17951                 appData.pieceDirectory);
17952         if(!appData.pieceDirectory[0])
17953           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
17954                 Col2Text(0),   // whitePieceColor
17955                 Col2Text(1) ); // blackPieceColor
17956       }
17957       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
17958                 Col2Text(4),   // highlightSquareColor
17959                 Col2Text(5) ); // premoveHighlightColor
17960         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
17961         if(insert != q) insert[-1] = NULLCHAR;
17962         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
17963         if(q)   free(q);
17964     }
17965     ActivateTheme(FALSE);
17966 }