Translation: fixed some inconsistencies reported by Benno Schulenberg
[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 agree"));
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                         } else DisplayError(_("failed writing PV"), 0);
9122                 }
9123
9124                 tempStats.depth = plylev;
9125                 tempStats.nodes = nodes;
9126                 tempStats.time = time;
9127                 tempStats.score = curscore;
9128                 tempStats.got_only_move = 0;
9129
9130                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9131                         int ticklen;
9132
9133                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
9134                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9135                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9136                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9137                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9138                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9139                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9140                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9141                 }
9142
9143                 /* Buffer overflow protection */
9144                 if (pv[0] != NULLCHAR) {
9145                     if (strlen(pv) >= sizeof(tempStats.movelist)
9146                         && appData.debugMode) {
9147                         fprintf(debugFP,
9148                                 "PV is too long; using the first %u bytes.\n",
9149                                 (unsigned) sizeof(tempStats.movelist) - 1);
9150                     }
9151
9152                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9153                 } else {
9154                     sprintf(tempStats.movelist, " no PV\n");
9155                 }
9156
9157                 if (tempStats.seen_stat) {
9158                     tempStats.ok_to_send = 1;
9159                 }
9160
9161                 if (strchr(tempStats.movelist, '(') != NULL) {
9162                     tempStats.line_is_book = 1;
9163                     tempStats.nr_moves = 0;
9164                     tempStats.moves_left = 0;
9165                 } else {
9166                     tempStats.line_is_book = 0;
9167                 }
9168
9169                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9170                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9171
9172                 SendProgramStatsToFrontend( cps, &tempStats );
9173
9174                 /*
9175                     [AS] Protect the thinkOutput buffer from overflow... this
9176                     is only useful if buf1 hasn't overflowed first!
9177                 */
9178                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
9179                          plylev,
9180                          (gameMode == TwoMachinesPlay ?
9181                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9182                          ((double) curscore) / 100.0,
9183                          prefixHint ? lastHint : "",
9184                          prefixHint ? " " : "" );
9185
9186                 if( buf1[0] != NULLCHAR ) {
9187                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9188
9189                     if( strlen(pv) > max_len ) {
9190                         if( appData.debugMode) {
9191                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9192                         }
9193                         pv[max_len+1] = '\0';
9194                     }
9195
9196                     strcat( thinkOutput, pv);
9197                 }
9198
9199                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9200                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9201                     DisplayMove(currentMove - 1);
9202                 }
9203                 return;
9204
9205             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9206                 /* crafty (9.25+) says "(only move) <move>"
9207                  * if there is only 1 legal move
9208                  */
9209                 sscanf(p, "(only move) %s", buf1);
9210                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9211                 sprintf(programStats.movelist, "%s (only move)", buf1);
9212                 programStats.depth = 1;
9213                 programStats.nr_moves = 1;
9214                 programStats.moves_left = 1;
9215                 programStats.nodes = 1;
9216                 programStats.time = 1;
9217                 programStats.got_only_move = 1;
9218
9219                 /* Not really, but we also use this member to
9220                    mean "line isn't going to change" (Crafty
9221                    isn't searching, so stats won't change) */
9222                 programStats.line_is_book = 1;
9223
9224                 SendProgramStatsToFrontend( cps, &programStats );
9225
9226                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9227                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9228                     DisplayMove(currentMove - 1);
9229                 }
9230                 return;
9231             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9232                               &time, &nodes, &plylev, &mvleft,
9233                               &mvtot, mvname) >= 5) {
9234                 /* The stat01: line is from Crafty (9.29+) in response
9235                    to the "." command */
9236                 programStats.seen_stat = 1;
9237                 cps->maybeThinking = TRUE;
9238
9239                 if (programStats.got_only_move || !appData.periodicUpdates)
9240                   return;
9241
9242                 programStats.depth = plylev;
9243                 programStats.time = time;
9244                 programStats.nodes = nodes;
9245                 programStats.moves_left = mvleft;
9246                 programStats.nr_moves = mvtot;
9247                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9248                 programStats.ok_to_send = 1;
9249                 programStats.movelist[0] = '\0';
9250
9251                 SendProgramStatsToFrontend( cps, &programStats );
9252
9253                 return;
9254
9255             } else if (strncmp(message,"++",2) == 0) {
9256                 /* Crafty 9.29+ outputs this */
9257                 programStats.got_fail = 2;
9258                 return;
9259
9260             } else if (strncmp(message,"--",2) == 0) {
9261                 /* Crafty 9.29+ outputs this */
9262                 programStats.got_fail = 1;
9263                 return;
9264
9265             } else if (thinkOutput[0] != NULLCHAR &&
9266                        strncmp(message, "    ", 4) == 0) {
9267                 unsigned message_len;
9268
9269                 p = message;
9270                 while (*p && *p == ' ') p++;
9271
9272                 message_len = strlen( p );
9273
9274                 /* [AS] Avoid buffer overflow */
9275                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9276                     strcat(thinkOutput, " ");
9277                     strcat(thinkOutput, p);
9278                 }
9279
9280                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9281                     strcat(programStats.movelist, " ");
9282                     strcat(programStats.movelist, p);
9283                 }
9284
9285                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9286                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9287                     DisplayMove(currentMove - 1);
9288                 }
9289                 return;
9290             }
9291         }
9292         else {
9293             buf1[0] = NULLCHAR;
9294
9295             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9296                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9297             {
9298                 ChessProgramStats cpstats;
9299
9300                 if (plyext != ' ' && plyext != '\t') {
9301                     time *= 100;
9302                 }
9303
9304                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9305                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9306                     curscore = -curscore;
9307                 }
9308
9309                 cpstats.depth = plylev;
9310                 cpstats.nodes = nodes;
9311                 cpstats.time = time;
9312                 cpstats.score = curscore;
9313                 cpstats.got_only_move = 0;
9314                 cpstats.movelist[0] = '\0';
9315
9316                 if (buf1[0] != NULLCHAR) {
9317                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9318                 }
9319
9320                 cpstats.ok_to_send = 0;
9321                 cpstats.line_is_book = 0;
9322                 cpstats.nr_moves = 0;
9323                 cpstats.moves_left = 0;
9324
9325                 SendProgramStatsToFrontend( cps, &cpstats );
9326             }
9327         }
9328     }
9329 }
9330
9331
9332 /* Parse a game score from the character string "game", and
9333    record it as the history of the current game.  The game
9334    score is NOT assumed to start from the standard position.
9335    The display is not updated in any way.
9336    */
9337 void
9338 ParseGameHistory (char *game)
9339 {
9340     ChessMove moveType;
9341     int fromX, fromY, toX, toY, boardIndex;
9342     char promoChar;
9343     char *p, *q;
9344     char buf[MSG_SIZ];
9345
9346     if (appData.debugMode)
9347       fprintf(debugFP, "Parsing game history: %s\n", game);
9348
9349     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9350     gameInfo.site = StrSave(appData.icsHost);
9351     gameInfo.date = PGNDate();
9352     gameInfo.round = StrSave("-");
9353
9354     /* Parse out names of players */
9355     while (*game == ' ') game++;
9356     p = buf;
9357     while (*game != ' ') *p++ = *game++;
9358     *p = NULLCHAR;
9359     gameInfo.white = StrSave(buf);
9360     while (*game == ' ') game++;
9361     p = buf;
9362     while (*game != ' ' && *game != '\n') *p++ = *game++;
9363     *p = NULLCHAR;
9364     gameInfo.black = StrSave(buf);
9365
9366     /* Parse moves */
9367     boardIndex = blackPlaysFirst ? 1 : 0;
9368     yynewstr(game);
9369     for (;;) {
9370         yyboardindex = boardIndex;
9371         moveType = (ChessMove) Myylex();
9372         switch (moveType) {
9373           case IllegalMove:             /* maybe suicide chess, etc. */
9374   if (appData.debugMode) {
9375     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9376     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9377     setbuf(debugFP, NULL);
9378   }
9379           case WhitePromotion:
9380           case BlackPromotion:
9381           case WhiteNonPromotion:
9382           case BlackNonPromotion:
9383           case NormalMove:
9384           case WhiteCapturesEnPassant:
9385           case BlackCapturesEnPassant:
9386           case WhiteKingSideCastle:
9387           case WhiteQueenSideCastle:
9388           case BlackKingSideCastle:
9389           case BlackQueenSideCastle:
9390           case WhiteKingSideCastleWild:
9391           case WhiteQueenSideCastleWild:
9392           case BlackKingSideCastleWild:
9393           case BlackQueenSideCastleWild:
9394           /* PUSH Fabien */
9395           case WhiteHSideCastleFR:
9396           case WhiteASideCastleFR:
9397           case BlackHSideCastleFR:
9398           case BlackASideCastleFR:
9399           /* POP Fabien */
9400             fromX = currentMoveString[0] - AAA;
9401             fromY = currentMoveString[1] - ONE;
9402             toX = currentMoveString[2] - AAA;
9403             toY = currentMoveString[3] - ONE;
9404             promoChar = currentMoveString[4];
9405             break;
9406           case WhiteDrop:
9407           case BlackDrop:
9408             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9409             fromX = moveType == WhiteDrop ?
9410               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9411             (int) CharToPiece(ToLower(currentMoveString[0]));
9412             fromY = DROP_RANK;
9413             toX = currentMoveString[2] - AAA;
9414             toY = currentMoveString[3] - ONE;
9415             promoChar = NULLCHAR;
9416             break;
9417           case AmbiguousMove:
9418             /* bug? */
9419             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9420   if (appData.debugMode) {
9421     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9422     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9423     setbuf(debugFP, NULL);
9424   }
9425             DisplayError(buf, 0);
9426             return;
9427           case ImpossibleMove:
9428             /* bug? */
9429             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9430   if (appData.debugMode) {
9431     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9432     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9433     setbuf(debugFP, NULL);
9434   }
9435             DisplayError(buf, 0);
9436             return;
9437           case EndOfFile:
9438             if (boardIndex < backwardMostMove) {
9439                 /* Oops, gap.  How did that happen? */
9440                 DisplayError(_("Gap in move list"), 0);
9441                 return;
9442             }
9443             backwardMostMove =  blackPlaysFirst ? 1 : 0;
9444             if (boardIndex > forwardMostMove) {
9445                 forwardMostMove = boardIndex;
9446             }
9447             return;
9448           case ElapsedTime:
9449             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9450                 strcat(parseList[boardIndex-1], " ");
9451                 strcat(parseList[boardIndex-1], yy_text);
9452             }
9453             continue;
9454           case Comment:
9455           case PGNTag:
9456           case NAG:
9457           default:
9458             /* ignore */
9459             continue;
9460           case WhiteWins:
9461           case BlackWins:
9462           case GameIsDrawn:
9463           case GameUnfinished:
9464             if (gameMode == IcsExamining) {
9465                 if (boardIndex < backwardMostMove) {
9466                     /* Oops, gap.  How did that happen? */
9467                     return;
9468                 }
9469                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9470                 return;
9471             }
9472             gameInfo.result = moveType;
9473             p = strchr(yy_text, '{');
9474             if (p == NULL) p = strchr(yy_text, '(');
9475             if (p == NULL) {
9476                 p = yy_text;
9477                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9478             } else {
9479                 q = strchr(p, *p == '{' ? '}' : ')');
9480                 if (q != NULL) *q = NULLCHAR;
9481                 p++;
9482             }
9483             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9484             gameInfo.resultDetails = StrSave(p);
9485             continue;
9486         }
9487         if (boardIndex >= forwardMostMove &&
9488             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9489             backwardMostMove = blackPlaysFirst ? 1 : 0;
9490             return;
9491         }
9492         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9493                                  fromY, fromX, toY, toX, promoChar,
9494                                  parseList[boardIndex]);
9495         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9496         /* currentMoveString is set as a side-effect of yylex */
9497         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9498         strcat(moveList[boardIndex], "\n");
9499         boardIndex++;
9500         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9501         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9502           case MT_NONE:
9503           case MT_STALEMATE:
9504           default:
9505             break;
9506           case MT_CHECK:
9507             if(gameInfo.variant != VariantShogi)
9508                 strcat(parseList[boardIndex - 1], "+");
9509             break;
9510           case MT_CHECKMATE:
9511           case MT_STAINMATE:
9512             strcat(parseList[boardIndex - 1], "#");
9513             break;
9514         }
9515     }
9516 }
9517
9518
9519 /* Apply a move to the given board  */
9520 void
9521 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9522 {
9523   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9524   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9525
9526     /* [HGM] compute & store e.p. status and castling rights for new position */
9527     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9528
9529       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9530       oldEP = (signed char)board[EP_STATUS];
9531       board[EP_STATUS] = EP_NONE;
9532
9533   if (fromY == DROP_RANK) {
9534         /* must be first */
9535         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9536             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9537             return;
9538         }
9539         piece = board[toY][toX] = (ChessSquare) fromX;
9540   } else {
9541       int i;
9542
9543       if( board[toY][toX] != EmptySquare )
9544            board[EP_STATUS] = EP_CAPTURE;
9545
9546       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9547            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9548                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9549       } else
9550       if( board[fromY][fromX] == WhitePawn ) {
9551            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9552                board[EP_STATUS] = EP_PAWN_MOVE;
9553            if( toY-fromY==2) {
9554                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9555                         gameInfo.variant != VariantBerolina || toX < fromX)
9556                       board[EP_STATUS] = toX | berolina;
9557                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9558                         gameInfo.variant != VariantBerolina || toX > fromX)
9559                       board[EP_STATUS] = toX;
9560            }
9561       } else
9562       if( board[fromY][fromX] == BlackPawn ) {
9563            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9564                board[EP_STATUS] = EP_PAWN_MOVE;
9565            if( toY-fromY== -2) {
9566                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9567                         gameInfo.variant != VariantBerolina || toX < fromX)
9568                       board[EP_STATUS] = toX | berolina;
9569                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9570                         gameInfo.variant != VariantBerolina || toX > fromX)
9571                       board[EP_STATUS] = toX;
9572            }
9573        }
9574
9575        for(i=0; i<nrCastlingRights; i++) {
9576            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9577               board[CASTLING][i] == toX   && castlingRank[i] == toY
9578              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9579        }
9580
9581        if(gameInfo.variant == VariantSChess) { // update virginity
9582            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
9583            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
9584            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
9585            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
9586        }
9587
9588      if (fromX == toX && fromY == toY) return;
9589
9590      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9591      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9592      if(gameInfo.variant == VariantKnightmate)
9593          king += (int) WhiteUnicorn - (int) WhiteKing;
9594
9595     /* Code added by Tord: */
9596     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9597     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9598         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9599       board[fromY][fromX] = EmptySquare;
9600       board[toY][toX] = EmptySquare;
9601       if((toX > fromX) != (piece == WhiteRook)) {
9602         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9603       } else {
9604         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9605       }
9606     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9607                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9608       board[fromY][fromX] = EmptySquare;
9609       board[toY][toX] = EmptySquare;
9610       if((toX > fromX) != (piece == BlackRook)) {
9611         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9612       } else {
9613         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9614       }
9615     /* End of code added by Tord */
9616
9617     } else if (board[fromY][fromX] == king
9618         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9619         && toY == fromY && toX > fromX+1) {
9620         board[fromY][fromX] = EmptySquare;
9621         board[toY][toX] = king;
9622         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9623         board[fromY][BOARD_RGHT-1] = EmptySquare;
9624     } else if (board[fromY][fromX] == king
9625         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9626                && toY == fromY && toX < fromX-1) {
9627         board[fromY][fromX] = EmptySquare;
9628         board[toY][toX] = king;
9629         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9630         board[fromY][BOARD_LEFT] = EmptySquare;
9631     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9632                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9633                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9634                ) {
9635         /* white pawn promotion */
9636         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9637         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9638             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9639         board[fromY][fromX] = EmptySquare;
9640     } else if ((fromY >= BOARD_HEIGHT>>1)
9641                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9642                && (toX != fromX)
9643                && gameInfo.variant != VariantXiangqi
9644                && gameInfo.variant != VariantBerolina
9645                && (board[fromY][fromX] == WhitePawn)
9646                && (board[toY][toX] == EmptySquare)) {
9647         board[fromY][fromX] = EmptySquare;
9648         board[toY][toX] = WhitePawn;
9649         captured = board[toY - 1][toX];
9650         board[toY - 1][toX] = EmptySquare;
9651     } else if ((fromY == BOARD_HEIGHT-4)
9652                && (toX == fromX)
9653                && gameInfo.variant == VariantBerolina
9654                && (board[fromY][fromX] == WhitePawn)
9655                && (board[toY][toX] == EmptySquare)) {
9656         board[fromY][fromX] = EmptySquare;
9657         board[toY][toX] = WhitePawn;
9658         if(oldEP & EP_BEROLIN_A) {
9659                 captured = board[fromY][fromX-1];
9660                 board[fromY][fromX-1] = EmptySquare;
9661         }else{  captured = board[fromY][fromX+1];
9662                 board[fromY][fromX+1] = EmptySquare;
9663         }
9664     } else if (board[fromY][fromX] == king
9665         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9666                && toY == fromY && toX > fromX+1) {
9667         board[fromY][fromX] = EmptySquare;
9668         board[toY][toX] = king;
9669         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9670         board[fromY][BOARD_RGHT-1] = EmptySquare;
9671     } else if (board[fromY][fromX] == king
9672         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9673                && toY == fromY && toX < fromX-1) {
9674         board[fromY][fromX] = EmptySquare;
9675         board[toY][toX] = king;
9676         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9677         board[fromY][BOARD_LEFT] = EmptySquare;
9678     } else if (fromY == 7 && fromX == 3
9679                && board[fromY][fromX] == BlackKing
9680                && toY == 7 && toX == 5) {
9681         board[fromY][fromX] = EmptySquare;
9682         board[toY][toX] = BlackKing;
9683         board[fromY][7] = EmptySquare;
9684         board[toY][4] = BlackRook;
9685     } else if (fromY == 7 && fromX == 3
9686                && board[fromY][fromX] == BlackKing
9687                && toY == 7 && toX == 1) {
9688         board[fromY][fromX] = EmptySquare;
9689         board[toY][toX] = BlackKing;
9690         board[fromY][0] = EmptySquare;
9691         board[toY][2] = BlackRook;
9692     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9693                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9694                && toY < promoRank && promoChar
9695                ) {
9696         /* black pawn promotion */
9697         board[toY][toX] = CharToPiece(ToLower(promoChar));
9698         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9699             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9700         board[fromY][fromX] = EmptySquare;
9701     } else if ((fromY < BOARD_HEIGHT>>1)
9702                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9703                && (toX != fromX)
9704                && gameInfo.variant != VariantXiangqi
9705                && gameInfo.variant != VariantBerolina
9706                && (board[fromY][fromX] == BlackPawn)
9707                && (board[toY][toX] == EmptySquare)) {
9708         board[fromY][fromX] = EmptySquare;
9709         board[toY][toX] = BlackPawn;
9710         captured = board[toY + 1][toX];
9711         board[toY + 1][toX] = EmptySquare;
9712     } else if ((fromY == 3)
9713                && (toX == fromX)
9714                && gameInfo.variant == VariantBerolina
9715                && (board[fromY][fromX] == BlackPawn)
9716                && (board[toY][toX] == EmptySquare)) {
9717         board[fromY][fromX] = EmptySquare;
9718         board[toY][toX] = BlackPawn;
9719         if(oldEP & EP_BEROLIN_A) {
9720                 captured = board[fromY][fromX-1];
9721                 board[fromY][fromX-1] = EmptySquare;
9722         }else{  captured = board[fromY][fromX+1];
9723                 board[fromY][fromX+1] = EmptySquare;
9724         }
9725     } else {
9726         board[toY][toX] = board[fromY][fromX];
9727         board[fromY][fromX] = EmptySquare;
9728     }
9729   }
9730
9731     if (gameInfo.holdingsWidth != 0) {
9732
9733       /* !!A lot more code needs to be written to support holdings  */
9734       /* [HGM] OK, so I have written it. Holdings are stored in the */
9735       /* penultimate board files, so they are automaticlly stored   */
9736       /* in the game history.                                       */
9737       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9738                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9739         /* Delete from holdings, by decreasing count */
9740         /* and erasing image if necessary            */
9741         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9742         if(p < (int) BlackPawn) { /* white drop */
9743              p -= (int)WhitePawn;
9744                  p = PieceToNumber((ChessSquare)p);
9745              if(p >= gameInfo.holdingsSize) p = 0;
9746              if(--board[p][BOARD_WIDTH-2] <= 0)
9747                   board[p][BOARD_WIDTH-1] = EmptySquare;
9748              if((int)board[p][BOARD_WIDTH-2] < 0)
9749                         board[p][BOARD_WIDTH-2] = 0;
9750         } else {                  /* black drop */
9751              p -= (int)BlackPawn;
9752                  p = PieceToNumber((ChessSquare)p);
9753              if(p >= gameInfo.holdingsSize) p = 0;
9754              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9755                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9756              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9757                         board[BOARD_HEIGHT-1-p][1] = 0;
9758         }
9759       }
9760       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9761           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9762         /* [HGM] holdings: Add to holdings, if holdings exist */
9763         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9764                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9765                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9766         }
9767         p = (int) captured;
9768         if (p >= (int) BlackPawn) {
9769           p -= (int)BlackPawn;
9770           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9771                   /* in Shogi restore piece to its original  first */
9772                   captured = (ChessSquare) (DEMOTED captured);
9773                   p = DEMOTED p;
9774           }
9775           p = PieceToNumber((ChessSquare)p);
9776           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9777           board[p][BOARD_WIDTH-2]++;
9778           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9779         } else {
9780           p -= (int)WhitePawn;
9781           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9782                   captured = (ChessSquare) (DEMOTED captured);
9783                   p = DEMOTED p;
9784           }
9785           p = PieceToNumber((ChessSquare)p);
9786           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9787           board[BOARD_HEIGHT-1-p][1]++;
9788           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9789         }
9790       }
9791     } else if (gameInfo.variant == VariantAtomic) {
9792       if (captured != EmptySquare) {
9793         int y, x;
9794         for (y = toY-1; y <= toY+1; y++) {
9795           for (x = toX-1; x <= toX+1; x++) {
9796             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9797                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9798               board[y][x] = EmptySquare;
9799             }
9800           }
9801         }
9802         board[toY][toX] = EmptySquare;
9803       }
9804     }
9805     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9806         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9807     } else
9808     if(promoChar == '+') {
9809         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
9810         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9811     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9812         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9813         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
9814            && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
9815         board[toY][toX] = newPiece;
9816     }
9817     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9818                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9819         // [HGM] superchess: take promotion piece out of holdings
9820         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9821         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9822             if(!--board[k][BOARD_WIDTH-2])
9823                 board[k][BOARD_WIDTH-1] = EmptySquare;
9824         } else {
9825             if(!--board[BOARD_HEIGHT-1-k][1])
9826                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9827         }
9828     }
9829
9830 }
9831
9832 /* Updates forwardMostMove */
9833 void
9834 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
9835 {
9836 //    forwardMostMove++; // [HGM] bare: moved downstream
9837
9838     (void) CoordsToAlgebraic(boards[forwardMostMove],
9839                              PosFlags(forwardMostMove),
9840                              fromY, fromX, toY, toX, promoChar,
9841                              parseList[forwardMostMove]);
9842
9843     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9844         int timeLeft; static int lastLoadFlag=0; int king, piece;
9845         piece = boards[forwardMostMove][fromY][fromX];
9846         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9847         if(gameInfo.variant == VariantKnightmate)
9848             king += (int) WhiteUnicorn - (int) WhiteKing;
9849         if(forwardMostMove == 0) {
9850             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9851                 fprintf(serverMoves, "%s;", UserName());
9852             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9853                 fprintf(serverMoves, "%s;", second.tidy);
9854             fprintf(serverMoves, "%s;", first.tidy);
9855             if(gameMode == MachinePlaysWhite)
9856                 fprintf(serverMoves, "%s;", UserName());
9857             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9858                 fprintf(serverMoves, "%s;", second.tidy);
9859         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9860         lastLoadFlag = loadFlag;
9861         // print base move
9862         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9863         // print castling suffix
9864         if( toY == fromY && piece == king ) {
9865             if(toX-fromX > 1)
9866                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9867             if(fromX-toX >1)
9868                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9869         }
9870         // e.p. suffix
9871         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9872              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9873              boards[forwardMostMove][toY][toX] == EmptySquare
9874              && fromX != toX && fromY != toY)
9875                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9876         // promotion suffix
9877         if(promoChar != NULLCHAR) {
9878             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
9879                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
9880                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
9881             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
9882         }
9883         if(!loadFlag) {
9884                 char buf[MOVE_LEN*2], *p; int len;
9885             fprintf(serverMoves, "/%d/%d",
9886                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9887             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9888             else                      timeLeft = blackTimeRemaining/1000;
9889             fprintf(serverMoves, "/%d", timeLeft);
9890                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
9891                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
9892                 if(p = strchr(buf, '=')) *p = NULLCHAR;
9893                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
9894             fprintf(serverMoves, "/%s", buf);
9895         }
9896         fflush(serverMoves);
9897     }
9898
9899     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
9900         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
9901       return;
9902     }
9903     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9904     if (commentList[forwardMostMove+1] != NULL) {
9905         free(commentList[forwardMostMove+1]);
9906         commentList[forwardMostMove+1] = NULL;
9907     }
9908     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9909     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9910     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9911     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9912     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9913     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9914     adjustedClock = FALSE;
9915     gameInfo.result = GameUnfinished;
9916     if (gameInfo.resultDetails != NULL) {
9917         free(gameInfo.resultDetails);
9918         gameInfo.resultDetails = NULL;
9919     }
9920     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9921                               moveList[forwardMostMove - 1]);
9922     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9923       case MT_NONE:
9924       case MT_STALEMATE:
9925       default:
9926         break;
9927       case MT_CHECK:
9928         if(gameInfo.variant != VariantShogi)
9929             strcat(parseList[forwardMostMove - 1], "+");
9930         break;
9931       case MT_CHECKMATE:
9932       case MT_STAINMATE:
9933         strcat(parseList[forwardMostMove - 1], "#");
9934         break;
9935     }
9936
9937 }
9938
9939 /* Updates currentMove if not pausing */
9940 void
9941 ShowMove (int fromX, int fromY, int toX, int toY)
9942 {
9943     int instant = (gameMode == PlayFromGameFile) ?
9944         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9945     if(appData.noGUI) return;
9946     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9947         if (!instant) {
9948             if (forwardMostMove == currentMove + 1) {
9949                 AnimateMove(boards[forwardMostMove - 1],
9950                             fromX, fromY, toX, toY);
9951             }
9952         }
9953         currentMove = forwardMostMove;
9954     }
9955
9956     if (instant) return;
9957
9958     DisplayMove(currentMove - 1);
9959     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9960             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
9961                 SetHighlights(fromX, fromY, toX, toY);
9962             }
9963     }
9964     DrawPosition(FALSE, boards[currentMove]);
9965     DisplayBothClocks();
9966     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9967 }
9968
9969 void
9970 SendEgtPath (ChessProgramState *cps)
9971 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9972         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9973
9974         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9975
9976         while(*p) {
9977             char c, *q = name+1, *r, *s;
9978
9979             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9980             while(*p && *p != ',') *q++ = *p++;
9981             *q++ = ':'; *q = 0;
9982             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9983                 strcmp(name, ",nalimov:") == 0 ) {
9984                 // take nalimov path from the menu-changeable option first, if it is defined
9985               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9986                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9987             } else
9988             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9989                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9990                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9991                 s = r = StrStr(s, ":") + 1; // beginning of path info
9992                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9993                 c = *r; *r = 0;             // temporarily null-terminate path info
9994                     *--q = 0;               // strip of trailig ':' from name
9995                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9996                 *r = c;
9997                 SendToProgram(buf,cps);     // send egtbpath command for this format
9998             }
9999             if(*p == ',') p++; // read away comma to position for next format name
10000         }
10001 }
10002
10003 static int
10004 NonStandardBoardSize ()
10005 {
10006       /* [HGM] Awkward testing. Should really be a table */
10007       int overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
10008       if( gameInfo.variant == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10009       if( gameInfo.variant == VariantXiangqi )
10010            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
10011       if( gameInfo.variant == VariantShogi )
10012            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
10013       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
10014            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
10015       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
10016           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
10017            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
10018       if( gameInfo.variant == VariantCourier )
10019            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
10020       if( gameInfo.variant == VariantSuper )
10021            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
10022       if( gameInfo.variant == VariantGreat )
10023            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
10024       if( gameInfo.variant == VariantSChess )
10025            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
10026       if( gameInfo.variant == VariantGrand )
10027            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
10028       return overruled;
10029 }
10030
10031 void
10032 InitChessProgram (ChessProgramState *cps, int setup)
10033 /* setup needed to setup FRC opening position */
10034 {
10035     char buf[MSG_SIZ], b[MSG_SIZ];
10036     if (appData.noChessProgram) return;
10037     hintRequested = FALSE;
10038     bookRequested = FALSE;
10039
10040     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10041     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10042     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10043     if(cps->memSize) { /* [HGM] memory */
10044       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10045         SendToProgram(buf, cps);
10046     }
10047     SendEgtPath(cps); /* [HGM] EGT */
10048     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10049       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10050         SendToProgram(buf, cps);
10051     }
10052
10053     SendToProgram(cps->initString, cps);
10054     if (gameInfo.variant != VariantNormal &&
10055         gameInfo.variant != VariantLoadable
10056         /* [HGM] also send variant if board size non-standard */
10057         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
10058                                             ) {
10059       char *v = VariantName(gameInfo.variant);
10060       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
10061         /* [HGM] in protocol 1 we have to assume all variants valid */
10062         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
10063         DisplayFatalError(buf, 0, 1);
10064         return;
10065       }
10066
10067       if(NonStandardBoardSize()) { /* [HGM] make prefix for non-standard board size. */
10068         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
10069                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
10070            /* [HGM] varsize: try first if this defiant size variant is specifically known */
10071            if(StrStr(cps->variants, b) == NULL) {
10072                // specific sized variant not known, check if general sizing allowed
10073                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
10074                    if(StrStr(cps->variants, "boardsize") == NULL) {
10075                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10076                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
10077                        DisplayFatalError(buf, 0, 1);
10078                        return;
10079                    }
10080                    /* [HGM] here we really should compare with the maximum supported board size */
10081                }
10082            }
10083       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
10084       snprintf(buf, MSG_SIZ, "variant %s\n", b);
10085       SendToProgram(buf, cps);
10086     }
10087     currentlyInitializedVariant = gameInfo.variant;
10088
10089     /* [HGM] send opening position in FRC to first engine */
10090     if(setup) {
10091           SendToProgram("force\n", cps);
10092           SendBoard(cps, 0);
10093           /* engine is now in force mode! Set flag to wake it up after first move. */
10094           setboardSpoiledMachineBlack = 1;
10095     }
10096
10097     if (cps->sendICS) {
10098       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10099       SendToProgram(buf, cps);
10100     }
10101     cps->maybeThinking = FALSE;
10102     cps->offeredDraw = 0;
10103     if (!appData.icsActive) {
10104         SendTimeControl(cps, movesPerSession, timeControl,
10105                         timeIncrement, appData.searchDepth,
10106                         searchTime);
10107     }
10108     if (appData.showThinking
10109         // [HGM] thinking: four options require thinking output to be sent
10110         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10111                                 ) {
10112         SendToProgram("post\n", cps);
10113     }
10114     SendToProgram("hard\n", cps);
10115     if (!appData.ponderNextMove) {
10116         /* Warning: "easy" is a toggle in GNU Chess, so don't send
10117            it without being sure what state we are in first.  "hard"
10118            is not a toggle, so that one is OK.
10119          */
10120         SendToProgram("easy\n", cps);
10121     }
10122     if (cps->usePing) {
10123       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
10124       SendToProgram(buf, cps);
10125     }
10126     cps->initDone = TRUE;
10127     ClearEngineOutputPane(cps == &second);
10128 }
10129
10130
10131 void
10132 ResendOptions (ChessProgramState *cps)
10133 { // send the stored value of the options
10134   int i;
10135   char buf[MSG_SIZ];
10136   Option *opt = cps->option;
10137   for(i=0; i<cps->nrOptions; i++, opt++) {
10138       switch(opt->type) {
10139         case Spin:
10140         case Slider:
10141         case CheckBox:
10142             snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10143           break;
10144         case ComboBox:
10145           snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10146           break;
10147         default:
10148             snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10149           break;
10150         case Button:
10151         case SaveButton:
10152           continue;
10153       }
10154       SendToProgram(buf, cps);
10155   }
10156 }
10157
10158 void
10159 StartChessProgram (ChessProgramState *cps)
10160 {
10161     char buf[MSG_SIZ];
10162     int err;
10163
10164     if (appData.noChessProgram) return;
10165     cps->initDone = FALSE;
10166
10167     if (strcmp(cps->host, "localhost") == 0) {
10168         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10169     } else if (*appData.remoteShell == NULLCHAR) {
10170         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10171     } else {
10172         if (*appData.remoteUser == NULLCHAR) {
10173           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10174                     cps->program);
10175         } else {
10176           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10177                     cps->host, appData.remoteUser, cps->program);
10178         }
10179         err = StartChildProcess(buf, "", &cps->pr);
10180     }
10181
10182     if (err != 0) {
10183       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10184         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10185         if(cps != &first) return;
10186         appData.noChessProgram = TRUE;
10187         ThawUI();
10188         SetNCPMode();
10189 //      DisplayFatalError(buf, err, 1);
10190 //      cps->pr = NoProc;
10191 //      cps->isr = NULL;
10192         return;
10193     }
10194
10195     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10196     if (cps->protocolVersion > 1) {
10197       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10198       if(!cps->reload) { // do not clear options when reloading because of -xreuse
10199         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10200         cps->comboCnt = 0;  //                and values of combo boxes
10201       }
10202       SendToProgram(buf, cps);
10203       if(cps->reload) ResendOptions(cps);
10204     } else {
10205       SendToProgram("xboard\n", cps);
10206     }
10207 }
10208
10209 void
10210 TwoMachinesEventIfReady P((void))
10211 {
10212   static int curMess = 0;
10213   if (first.lastPing != first.lastPong) {
10214     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10215     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10216     return;
10217   }
10218   if (second.lastPing != second.lastPong) {
10219     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10220     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10221     return;
10222   }
10223   DisplayMessage("", ""); curMess = 0;
10224   TwoMachinesEvent();
10225 }
10226
10227 char *
10228 MakeName (char *template)
10229 {
10230     time_t clock;
10231     struct tm *tm;
10232     static char buf[MSG_SIZ];
10233     char *p = buf;
10234     int i;
10235
10236     clock = time((time_t *)NULL);
10237     tm = localtime(&clock);
10238
10239     while(*p++ = *template++) if(p[-1] == '%') {
10240         switch(*template++) {
10241           case 0:   *p = 0; return buf;
10242           case 'Y': i = tm->tm_year+1900; break;
10243           case 'y': i = tm->tm_year-100; break;
10244           case 'M': i = tm->tm_mon+1; break;
10245           case 'd': i = tm->tm_mday; break;
10246           case 'h': i = tm->tm_hour; break;
10247           case 'm': i = tm->tm_min; break;
10248           case 's': i = tm->tm_sec; break;
10249           default:  i = 0;
10250         }
10251         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10252     }
10253     return buf;
10254 }
10255
10256 int
10257 CountPlayers (char *p)
10258 {
10259     int n = 0;
10260     while(p = strchr(p, '\n')) p++, n++; // count participants
10261     return n;
10262 }
10263
10264 FILE *
10265 WriteTourneyFile (char *results, FILE *f)
10266 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10267     if(f == NULL) f = fopen(appData.tourneyFile, "w");
10268     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10269         // create a file with tournament description
10270         fprintf(f, "-participants {%s}\n", appData.participants);
10271         fprintf(f, "-seedBase %d\n", appData.seedBase);
10272         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10273         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10274         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10275         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10276         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10277         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10278         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10279         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10280         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10281         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10282         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10283         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10284         fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
10285         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10286         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10287         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10288         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10289         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10290         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10291         fprintf(f, "-smpCores %d\n", appData.smpCores);
10292         if(searchTime > 0)
10293                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10294         else {
10295                 fprintf(f, "-mps %d\n", appData.movesPerSession);
10296                 fprintf(f, "-tc %s\n", appData.timeControl);
10297                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10298         }
10299         fprintf(f, "-results \"%s\"\n", results);
10300     }
10301     return f;
10302 }
10303
10304 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10305
10306 void
10307 Substitute (char *participants, int expunge)
10308 {
10309     int i, changed, changes=0, nPlayers=0;
10310     char *p, *q, *r, buf[MSG_SIZ];
10311     if(participants == NULL) return;
10312     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10313     r = p = participants; q = appData.participants;
10314     while(*p && *p == *q) {
10315         if(*p == '\n') r = p+1, nPlayers++;
10316         p++; q++;
10317     }
10318     if(*p) { // difference
10319         while(*p && *p++ != '\n');
10320         while(*q && *q++ != '\n');
10321       changed = nPlayers;
10322         changes = 1 + (strcmp(p, q) != 0);
10323     }
10324     if(changes == 1) { // a single engine mnemonic was changed
10325         q = r; while(*q) nPlayers += (*q++ == '\n');
10326         p = buf; while(*r && (*p = *r++) != '\n') p++;
10327         *p = NULLCHAR;
10328         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10329         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10330         if(mnemonic[i]) { // The substitute is valid
10331             FILE *f;
10332             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10333                 flock(fileno(f), LOCK_EX);
10334                 ParseArgsFromFile(f);
10335                 fseek(f, 0, SEEK_SET);
10336                 FREE(appData.participants); appData.participants = participants;
10337                 if(expunge) { // erase results of replaced engine
10338                     int len = strlen(appData.results), w, b, dummy;
10339                     for(i=0; i<len; i++) {
10340                         Pairing(i, nPlayers, &w, &b, &dummy);
10341                         if((w == changed || b == changed) && appData.results[i] == '*') {
10342                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10343                             fclose(f);
10344                             return;
10345                         }
10346                     }
10347                     for(i=0; i<len; i++) {
10348                         Pairing(i, nPlayers, &w, &b, &dummy);
10349                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10350                     }
10351                 }
10352                 WriteTourneyFile(appData.results, f);
10353                 fclose(f); // release lock
10354                 return;
10355             }
10356         } else DisplayError(_("No engine with the name you gave is installed"), 0);
10357     }
10358     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10359     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
10360     free(participants);
10361     return;
10362 }
10363
10364 int
10365 CheckPlayers (char *participants)
10366 {
10367         int i;
10368         char buf[MSG_SIZ], *p;
10369         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10370         while(p = strchr(participants, '\n')) {
10371             *p = NULLCHAR;
10372             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10373             if(!mnemonic[i]) {
10374                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10375                 *p = '\n';
10376                 DisplayError(buf, 0);
10377                 return 1;
10378             }
10379             *p = '\n';
10380             participants = p + 1;
10381         }
10382         return 0;
10383 }
10384
10385 int
10386 CreateTourney (char *name)
10387 {
10388         FILE *f;
10389         if(matchMode && strcmp(name, appData.tourneyFile)) {
10390              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10391         }
10392         if(name[0] == NULLCHAR) {
10393             if(appData.participants[0])
10394                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10395             return 0;
10396         }
10397         f = fopen(name, "r");
10398         if(f) { // file exists
10399             ASSIGN(appData.tourneyFile, name);
10400             ParseArgsFromFile(f); // parse it
10401         } else {
10402             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10403             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10404                 DisplayError(_("Not enough participants"), 0);
10405                 return 0;
10406             }
10407             if(CheckPlayers(appData.participants)) return 0;
10408             ASSIGN(appData.tourneyFile, name);
10409             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10410             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10411         }
10412         fclose(f);
10413         appData.noChessProgram = FALSE;
10414         appData.clockMode = TRUE;
10415         SetGNUMode();
10416         return 1;
10417 }
10418
10419 int
10420 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10421 {
10422     char buf[MSG_SIZ], *p, *q;
10423     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10424     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10425     skip = !all && group[0]; // if group requested, we start in skip mode
10426     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10427         p = names; q = buf; header = 0;
10428         while(*p && *p != '\n') *q++ = *p++;
10429         *q = 0;
10430         if(*p == '\n') p++;
10431         if(buf[0] == '#') {
10432             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10433             depth++; // we must be entering a new group
10434             if(all) continue; // suppress printing group headers when complete list requested
10435             header = 1;
10436             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10437         }
10438         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10439         if(engineList[i]) free(engineList[i]);
10440         engineList[i] = strdup(buf);
10441         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10442         if(engineMnemonic[i]) free(engineMnemonic[i]);
10443         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10444             strcat(buf, " (");
10445             sscanf(q + 8, "%s", buf + strlen(buf));
10446             strcat(buf, ")");
10447         }
10448         engineMnemonic[i] = strdup(buf);
10449         i++;
10450     }
10451     engineList[i] = engineMnemonic[i] = NULL;
10452     return i;
10453 }
10454
10455 // following implemented as macro to avoid type limitations
10456 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10457
10458 void
10459 SwapEngines (int n)
10460 {   // swap settings for first engine and other engine (so far only some selected options)
10461     int h;
10462     char *p;
10463     if(n == 0) return;
10464     SWAP(directory, p)
10465     SWAP(chessProgram, p)
10466     SWAP(isUCI, h)
10467     SWAP(hasOwnBookUCI, h)
10468     SWAP(protocolVersion, h)
10469     SWAP(reuse, h)
10470     SWAP(scoreIsAbsolute, h)
10471     SWAP(timeOdds, h)
10472     SWAP(logo, p)
10473     SWAP(pgnName, p)
10474     SWAP(pvSAN, h)
10475     SWAP(engOptions, p)
10476     SWAP(engInitString, p)
10477     SWAP(computerString, p)
10478     SWAP(features, p)
10479     SWAP(fenOverride, p)
10480     SWAP(NPS, h)
10481     SWAP(accumulateTC, h)
10482     SWAP(host, p)
10483 }
10484
10485 int
10486 GetEngineLine (char *s, int n)
10487 {
10488     int i;
10489     char buf[MSG_SIZ];
10490     extern char *icsNames;
10491     if(!s || !*s) return 0;
10492     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
10493     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
10494     if(!mnemonic[i]) return 0;
10495     if(n == 11) return 1; // just testing if there was a match
10496     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
10497     if(n == 1) SwapEngines(n);
10498     ParseArgsFromString(buf);
10499     if(n == 1) SwapEngines(n);
10500     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
10501         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
10502         ParseArgsFromString(buf);
10503     }
10504     return 1;
10505 }
10506
10507 int
10508 SetPlayer (int player, char *p)
10509 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
10510     int i;
10511     char buf[MSG_SIZ], *engineName;
10512     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10513     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10514     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10515     if(mnemonic[i]) {
10516         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10517         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10518         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10519         ParseArgsFromString(buf);
10520     } else { // no engine with this nickname is installed!
10521         snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
10522         ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
10523         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10524         ModeHighlight();
10525         DisplayError(buf, 0);
10526         return 0;
10527     }
10528     free(engineName);
10529     return i;
10530 }
10531
10532 char *recentEngines;
10533
10534 void
10535 RecentEngineEvent (int nr)
10536 {
10537     int n;
10538 //    SwapEngines(1); // bump first to second
10539 //    ReplaceEngine(&second, 1); // and load it there
10540     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10541     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10542     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10543         ReplaceEngine(&first, 0);
10544         FloatToFront(&appData.recentEngineList, command[n]);
10545     }
10546 }
10547
10548 int
10549 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10550 {   // determine players from game number
10551     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10552
10553     if(appData.tourneyType == 0) {
10554         roundsPerCycle = (nPlayers - 1) | 1;
10555         pairingsPerRound = nPlayers / 2;
10556     } else if(appData.tourneyType > 0) {
10557         roundsPerCycle = nPlayers - appData.tourneyType;
10558         pairingsPerRound = appData.tourneyType;
10559     }
10560     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10561     gamesPerCycle = gamesPerRound * roundsPerCycle;
10562     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10563     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10564     curRound = nr / gamesPerRound; nr %= gamesPerRound;
10565     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10566     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10567     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10568
10569     if(appData.cycleSync) *syncInterval = gamesPerCycle;
10570     if(appData.roundSync) *syncInterval = gamesPerRound;
10571
10572     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10573
10574     if(appData.tourneyType == 0) {
10575         if(curPairing == (nPlayers-1)/2 ) {
10576             *whitePlayer = curRound;
10577             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10578         } else {
10579             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10580             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10581             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10582             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10583         }
10584     } else if(appData.tourneyType > 1) {
10585         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10586         *whitePlayer = curRound + appData.tourneyType;
10587     } else if(appData.tourneyType > 0) {
10588         *whitePlayer = curPairing;
10589         *blackPlayer = curRound + appData.tourneyType;
10590     }
10591
10592     // take care of white/black alternation per round.
10593     // For cycles and games this is already taken care of by default, derived from matchGame!
10594     return curRound & 1;
10595 }
10596
10597 int
10598 NextTourneyGame (int nr, int *swapColors)
10599 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10600     char *p, *q;
10601     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
10602     FILE *tf;
10603     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10604     tf = fopen(appData.tourneyFile, "r");
10605     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10606     ParseArgsFromFile(tf); fclose(tf);
10607     InitTimeControls(); // TC might be altered from tourney file
10608
10609     nPlayers = CountPlayers(appData.participants); // count participants
10610     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10611     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10612
10613     if(syncInterval) {
10614         p = q = appData.results;
10615         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10616         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10617             DisplayMessage(_("Waiting for other game(s)"),"");
10618             waitingForGame = TRUE;
10619             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10620             return 0;
10621         }
10622         waitingForGame = FALSE;
10623     }
10624
10625     if(appData.tourneyType < 0) {
10626         if(nr>=0 && !pairingReceived) {
10627             char buf[1<<16];
10628             if(pairing.pr == NoProc) {
10629                 if(!appData.pairingEngine[0]) {
10630                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10631                     return 0;
10632                 }
10633                 StartChessProgram(&pairing); // starts the pairing engine
10634             }
10635             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10636             SendToProgram(buf, &pairing);
10637             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10638             SendToProgram(buf, &pairing);
10639             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10640         }
10641         pairingReceived = 0;                              // ... so we continue here
10642         *swapColors = 0;
10643         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10644         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10645         matchGame = 1; roundNr = nr / syncInterval + 1;
10646     }
10647
10648     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10649
10650     // redefine engines, engine dir, etc.
10651     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10652     if(first.pr == NoProc) {
10653       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
10654       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10655     }
10656     if(second.pr == NoProc) {
10657       SwapEngines(1);
10658       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
10659       SwapEngines(1);         // and make that valid for second engine by swapping
10660       InitEngine(&second, 1);
10661     }
10662     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10663     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10664     return OK;
10665 }
10666
10667 void
10668 NextMatchGame ()
10669 {   // performs game initialization that does not invoke engines, and then tries to start the game
10670     int res, firstWhite, swapColors = 0;
10671     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10672     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
10673         char buf[MSG_SIZ];
10674         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
10675         if(strcmp(buf, currentDebugFile)) { // name has changed
10676             FILE *f = fopen(buf, "w");
10677             if(f) { // if opening the new file failed, just keep using the old one
10678                 ASSIGN(currentDebugFile, buf);
10679                 fclose(debugFP);
10680                 debugFP = f;
10681             }
10682             if(appData.serverFileName) {
10683                 if(serverFP) fclose(serverFP);
10684                 serverFP = fopen(appData.serverFileName, "w");
10685                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
10686                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
10687             }
10688         }
10689     }
10690     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10691     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10692     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
10693     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10694     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10695     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10696     Reset(FALSE, first.pr != NoProc);
10697     res = LoadGameOrPosition(matchGame); // setup game
10698     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10699     if(!res) return; // abort when bad game/pos file
10700     TwoMachinesEvent();
10701 }
10702
10703 void
10704 UserAdjudicationEvent (int result)
10705 {
10706     ChessMove gameResult = GameIsDrawn;
10707
10708     if( result > 0 ) {
10709         gameResult = WhiteWins;
10710     }
10711     else if( result < 0 ) {
10712         gameResult = BlackWins;
10713     }
10714
10715     if( gameMode == TwoMachinesPlay ) {
10716         GameEnds( gameResult, "User adjudication", GE_XBOARD );
10717     }
10718 }
10719
10720
10721 // [HGM] save: calculate checksum of game to make games easily identifiable
10722 int
10723 StringCheckSum (char *s)
10724 {
10725         int i = 0;
10726         if(s==NULL) return 0;
10727         while(*s) i = i*259 + *s++;
10728         return i;
10729 }
10730
10731 int
10732 GameCheckSum ()
10733 {
10734         int i, sum=0;
10735         for(i=backwardMostMove; i<forwardMostMove; i++) {
10736                 sum += pvInfoList[i].depth;
10737                 sum += StringCheckSum(parseList[i]);
10738                 sum += StringCheckSum(commentList[i]);
10739                 sum *= 261;
10740         }
10741         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10742         return sum + StringCheckSum(commentList[i]);
10743 } // end of save patch
10744
10745 void
10746 GameEnds (ChessMove result, char *resultDetails, int whosays)
10747 {
10748     GameMode nextGameMode;
10749     int isIcsGame;
10750     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10751
10752     if(endingGame) return; /* [HGM] crash: forbid recursion */
10753     endingGame = 1;
10754     if(twoBoards) { // [HGM] dual: switch back to one board
10755         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10756         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10757     }
10758     if (appData.debugMode) {
10759       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10760               result, resultDetails ? resultDetails : "(null)", whosays);
10761     }
10762
10763     fromX = fromY = -1; // [HGM] abort any move the user is entering.
10764
10765     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
10766
10767     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10768         /* If we are playing on ICS, the server decides when the
10769            game is over, but the engine can offer to draw, claim
10770            a draw, or resign.
10771          */
10772 #if ZIPPY
10773         if (appData.zippyPlay && first.initDone) {
10774             if (result == GameIsDrawn) {
10775                 /* In case draw still needs to be claimed */
10776                 SendToICS(ics_prefix);
10777                 SendToICS("draw\n");
10778             } else if (StrCaseStr(resultDetails, "resign")) {
10779                 SendToICS(ics_prefix);
10780                 SendToICS("resign\n");
10781             }
10782         }
10783 #endif
10784         endingGame = 0; /* [HGM] crash */
10785         return;
10786     }
10787
10788     /* If we're loading the game from a file, stop */
10789     if (whosays == GE_FILE) {
10790       (void) StopLoadGameTimer();
10791       gameFileFP = NULL;
10792     }
10793
10794     /* Cancel draw offers */
10795     first.offeredDraw = second.offeredDraw = 0;
10796
10797     /* If this is an ICS game, only ICS can really say it's done;
10798        if not, anyone can. */
10799     isIcsGame = (gameMode == IcsPlayingWhite ||
10800                  gameMode == IcsPlayingBlack ||
10801                  gameMode == IcsObserving    ||
10802                  gameMode == IcsExamining);
10803
10804     if (!isIcsGame || whosays == GE_ICS) {
10805         /* OK -- not an ICS game, or ICS said it was done */
10806         StopClocks();
10807         if (!isIcsGame && !appData.noChessProgram)
10808           SetUserThinkingEnables();
10809
10810         /* [HGM] if a machine claims the game end we verify this claim */
10811         if(gameMode == TwoMachinesPlay && appData.testClaims) {
10812             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10813                 char claimer;
10814                 ChessMove trueResult = (ChessMove) -1;
10815
10816                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
10817                                             first.twoMachinesColor[0] :
10818                                             second.twoMachinesColor[0] ;
10819
10820                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10821                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10822                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10823                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10824                 } else
10825                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10826                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10827                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10828                 } else
10829                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10830                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10831                 }
10832
10833                 // now verify win claims, but not in drop games, as we don't understand those yet
10834                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10835                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10836                     (result == WhiteWins && claimer == 'w' ||
10837                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
10838                       if (appData.debugMode) {
10839                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
10840                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10841                       }
10842                       if(result != trueResult) {
10843                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10844                               result = claimer == 'w' ? BlackWins : WhiteWins;
10845                               resultDetails = buf;
10846                       }
10847                 } else
10848                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10849                     && (forwardMostMove <= backwardMostMove ||
10850                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10851                         (claimer=='b')==(forwardMostMove&1))
10852                                                                                   ) {
10853                       /* [HGM] verify: draws that were not flagged are false claims */
10854                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10855                       result = claimer == 'w' ? BlackWins : WhiteWins;
10856                       resultDetails = buf;
10857                 }
10858                 /* (Claiming a loss is accepted no questions asked!) */
10859             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
10860                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
10861                 result = GameUnfinished;
10862                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
10863             }
10864             /* [HGM] bare: don't allow bare King to win */
10865             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10866                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10867                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10868                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10869                && result != GameIsDrawn)
10870             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10871                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10872                         int p = (signed char)boards[forwardMostMove][i][j] - color;
10873                         if(p >= 0 && p <= (int)WhiteKing) k++;
10874                 }
10875                 if (appData.debugMode) {
10876                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10877                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10878                 }
10879                 if(k <= 1) {
10880                         result = GameIsDrawn;
10881                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10882                         resultDetails = buf;
10883                 }
10884             }
10885         }
10886
10887
10888         if(serverMoves != NULL && !loadFlag) { char c = '=';
10889             if(result==WhiteWins) c = '+';
10890             if(result==BlackWins) c = '-';
10891             if(resultDetails != NULL)
10892                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
10893         }
10894         if (resultDetails != NULL) {
10895             gameInfo.result = result;
10896             gameInfo.resultDetails = StrSave(resultDetails);
10897
10898             /* display last move only if game was not loaded from file */
10899             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10900                 DisplayMove(currentMove - 1);
10901
10902             if (forwardMostMove != 0) {
10903                 if (gameMode != PlayFromGameFile && gameMode != EditGame
10904                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10905                                                                 ) {
10906                     if (*appData.saveGameFile != NULLCHAR) {
10907                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
10908                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
10909                         else
10910                         SaveGameToFile(appData.saveGameFile, TRUE);
10911                     } else if (appData.autoSaveGames) {
10912                         if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
10913                     }
10914                     if (*appData.savePositionFile != NULLCHAR) {
10915                         SavePositionToFile(appData.savePositionFile);
10916                     }
10917                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
10918                 }
10919             }
10920
10921             /* Tell program how game ended in case it is learning */
10922             /* [HGM] Moved this to after saving the PGN, just in case */
10923             /* engine died and we got here through time loss. In that */
10924             /* case we will get a fatal error writing the pipe, which */
10925             /* would otherwise lose us the PGN.                       */
10926             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
10927             /* output during GameEnds should never be fatal anymore   */
10928             if (gameMode == MachinePlaysWhite ||
10929                 gameMode == MachinePlaysBlack ||
10930                 gameMode == TwoMachinesPlay ||
10931                 gameMode == IcsPlayingWhite ||
10932                 gameMode == IcsPlayingBlack ||
10933                 gameMode == BeginningOfGame) {
10934                 char buf[MSG_SIZ];
10935                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10936                         resultDetails);
10937                 if (first.pr != NoProc) {
10938                     SendToProgram(buf, &first);
10939                 }
10940                 if (second.pr != NoProc &&
10941                     gameMode == TwoMachinesPlay) {
10942                     SendToProgram(buf, &second);
10943                 }
10944             }
10945         }
10946
10947         if (appData.icsActive) {
10948             if (appData.quietPlay &&
10949                 (gameMode == IcsPlayingWhite ||
10950                  gameMode == IcsPlayingBlack)) {
10951                 SendToICS(ics_prefix);
10952                 SendToICS("set shout 1\n");
10953             }
10954             nextGameMode = IcsIdle;
10955             ics_user_moved = FALSE;
10956             /* clean up premove.  It's ugly when the game has ended and the
10957              * premove highlights are still on the board.
10958              */
10959             if (gotPremove) {
10960               gotPremove = FALSE;
10961               ClearPremoveHighlights();
10962               DrawPosition(FALSE, boards[currentMove]);
10963             }
10964             if (whosays == GE_ICS) {
10965                 switch (result) {
10966                 case WhiteWins:
10967                     if (gameMode == IcsPlayingWhite)
10968                         PlayIcsWinSound();
10969                     else if(gameMode == IcsPlayingBlack)
10970                         PlayIcsLossSound();
10971                     break;
10972                 case BlackWins:
10973                     if (gameMode == IcsPlayingBlack)
10974                         PlayIcsWinSound();
10975                     else if(gameMode == IcsPlayingWhite)
10976                         PlayIcsLossSound();
10977                     break;
10978                 case GameIsDrawn:
10979                     PlayIcsDrawSound();
10980                     break;
10981                 default:
10982                     PlayIcsUnfinishedSound();
10983                 }
10984             }
10985             if(appData.quitNext) { ExitEvent(0); return; }
10986         } else if (gameMode == EditGame ||
10987                    gameMode == PlayFromGameFile ||
10988                    gameMode == AnalyzeMode ||
10989                    gameMode == AnalyzeFile) {
10990             nextGameMode = gameMode;
10991         } else {
10992             nextGameMode = EndOfGame;
10993         }
10994         pausing = FALSE;
10995         ModeHighlight();
10996     } else {
10997         nextGameMode = gameMode;
10998     }
10999
11000     if (appData.noChessProgram) {
11001         gameMode = nextGameMode;
11002         ModeHighlight();
11003         endingGame = 0; /* [HGM] crash */
11004         return;
11005     }
11006
11007     if (first.reuse) {
11008         /* Put first chess program into idle state */
11009         if (first.pr != NoProc &&
11010             (gameMode == MachinePlaysWhite ||
11011              gameMode == MachinePlaysBlack ||
11012              gameMode == TwoMachinesPlay ||
11013              gameMode == IcsPlayingWhite ||
11014              gameMode == IcsPlayingBlack ||
11015              gameMode == BeginningOfGame)) {
11016             SendToProgram("force\n", &first);
11017             if (first.usePing) {
11018               char buf[MSG_SIZ];
11019               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11020               SendToProgram(buf, &first);
11021             }
11022         }
11023     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11024         /* Kill off first chess program */
11025         if (first.isr != NULL)
11026           RemoveInputSource(first.isr);
11027         first.isr = NULL;
11028
11029         if (first.pr != NoProc) {
11030             ExitAnalyzeMode();
11031             DoSleep( appData.delayBeforeQuit );
11032             SendToProgram("quit\n", &first);
11033             DoSleep( appData.delayAfterQuit );
11034             DestroyChildProcess(first.pr, first.useSigterm);
11035             first.reload = TRUE;
11036         }
11037         first.pr = NoProc;
11038     }
11039     if (second.reuse) {
11040         /* Put second chess program into idle state */
11041         if (second.pr != NoProc &&
11042             gameMode == TwoMachinesPlay) {
11043             SendToProgram("force\n", &second);
11044             if (second.usePing) {
11045               char buf[MSG_SIZ];
11046               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11047               SendToProgram(buf, &second);
11048             }
11049         }
11050     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11051         /* Kill off second chess program */
11052         if (second.isr != NULL)
11053           RemoveInputSource(second.isr);
11054         second.isr = NULL;
11055
11056         if (second.pr != NoProc) {
11057             DoSleep( appData.delayBeforeQuit );
11058             SendToProgram("quit\n", &second);
11059             DoSleep( appData.delayAfterQuit );
11060             DestroyChildProcess(second.pr, second.useSigterm);
11061             second.reload = TRUE;
11062         }
11063         second.pr = NoProc;
11064     }
11065
11066     if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11067         char resChar = '=';
11068         switch (result) {
11069         case WhiteWins:
11070           resChar = '+';
11071           if (first.twoMachinesColor[0] == 'w') {
11072             first.matchWins++;
11073           } else {
11074             second.matchWins++;
11075           }
11076           break;
11077         case BlackWins:
11078           resChar = '-';
11079           if (first.twoMachinesColor[0] == 'b') {
11080             first.matchWins++;
11081           } else {
11082             second.matchWins++;
11083           }
11084           break;
11085         case GameUnfinished:
11086           resChar = ' ';
11087         default:
11088           break;
11089         }
11090
11091         if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11092         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11093             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11094             ReserveGame(nextGame, resChar); // sets nextGame
11095             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11096             else ranking = strdup("busy"); //suppress popup when aborted but not finished
11097         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11098
11099         if (nextGame <= appData.matchGames && !abortMatch) {
11100             gameMode = nextGameMode;
11101             matchGame = nextGame; // this will be overruled in tourney mode!
11102             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11103             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11104             endingGame = 0; /* [HGM] crash */
11105             return;
11106         } else {
11107             gameMode = nextGameMode;
11108             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11109                      first.tidy, second.tidy,
11110                      first.matchWins, second.matchWins,
11111                      appData.matchGames - (first.matchWins + second.matchWins));
11112             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11113             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11114             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11115             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11116                 first.twoMachinesColor = "black\n";
11117                 second.twoMachinesColor = "white\n";
11118             } else {
11119                 first.twoMachinesColor = "white\n";
11120                 second.twoMachinesColor = "black\n";
11121             }
11122         }
11123     }
11124     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11125         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11126       ExitAnalyzeMode();
11127     gameMode = nextGameMode;
11128     ModeHighlight();
11129     endingGame = 0;  /* [HGM] crash */
11130     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11131         if(matchMode == TRUE) { // match through command line: exit with or without popup
11132             if(ranking) {
11133                 ToNrEvent(forwardMostMove);
11134                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11135                 else ExitEvent(0);
11136             } else DisplayFatalError(buf, 0, 0);
11137         } else { // match through menu; just stop, with or without popup
11138             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11139             ModeHighlight();
11140             if(ranking){
11141                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11142             } else DisplayNote(buf);
11143       }
11144       if(ranking) free(ranking);
11145     }
11146 }
11147
11148 /* Assumes program was just initialized (initString sent).
11149    Leaves program in force mode. */
11150 void
11151 FeedMovesToProgram (ChessProgramState *cps, int upto)
11152 {
11153     int i;
11154
11155     if (appData.debugMode)
11156       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11157               startedFromSetupPosition ? "position and " : "",
11158               backwardMostMove, upto, cps->which);
11159     if(currentlyInitializedVariant != gameInfo.variant) {
11160       char buf[MSG_SIZ];
11161         // [HGM] variantswitch: make engine aware of new variant
11162         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
11163                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11164         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11165         SendToProgram(buf, cps);
11166         currentlyInitializedVariant = gameInfo.variant;
11167     }
11168     SendToProgram("force\n", cps);
11169     if (startedFromSetupPosition) {
11170         SendBoard(cps, backwardMostMove);
11171     if (appData.debugMode) {
11172         fprintf(debugFP, "feedMoves\n");
11173     }
11174     }
11175     for (i = backwardMostMove; i < upto; i++) {
11176         SendMoveToProgram(i, cps);
11177     }
11178 }
11179
11180
11181 int
11182 ResurrectChessProgram ()
11183 {
11184      /* The chess program may have exited.
11185         If so, restart it and feed it all the moves made so far. */
11186     static int doInit = 0;
11187
11188     if (appData.noChessProgram) return 1;
11189
11190     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11191         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11192         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11193         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11194     } else {
11195         if (first.pr != NoProc) return 1;
11196         StartChessProgram(&first);
11197     }
11198     InitChessProgram(&first, FALSE);
11199     FeedMovesToProgram(&first, currentMove);
11200
11201     if (!first.sendTime) {
11202         /* can't tell gnuchess what its clock should read,
11203            so we bow to its notion. */
11204         ResetClocks();
11205         timeRemaining[0][currentMove] = whiteTimeRemaining;
11206         timeRemaining[1][currentMove] = blackTimeRemaining;
11207     }
11208
11209     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11210                 appData.icsEngineAnalyze) && first.analysisSupport) {
11211       SendToProgram("analyze\n", &first);
11212       first.analyzing = TRUE;
11213     }
11214     return 1;
11215 }
11216
11217 /*
11218  * Button procedures
11219  */
11220 void
11221 Reset (int redraw, int init)
11222 {
11223     int i;
11224
11225     if (appData.debugMode) {
11226         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11227                 redraw, init, gameMode);
11228     }
11229     CleanupTail(); // [HGM] vari: delete any stored variations
11230     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11231     pausing = pauseExamInvalid = FALSE;
11232     startedFromSetupPosition = blackPlaysFirst = FALSE;
11233     firstMove = TRUE;
11234     whiteFlag = blackFlag = FALSE;
11235     userOfferedDraw = FALSE;
11236     hintRequested = bookRequested = FALSE;
11237     first.maybeThinking = FALSE;
11238     second.maybeThinking = FALSE;
11239     first.bookSuspend = FALSE; // [HGM] book
11240     second.bookSuspend = FALSE;
11241     thinkOutput[0] = NULLCHAR;
11242     lastHint[0] = NULLCHAR;
11243     ClearGameInfo(&gameInfo);
11244     gameInfo.variant = StringToVariant(appData.variant);
11245     if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) gameInfo.variant = VariantUnknown;
11246     ics_user_moved = ics_clock_paused = FALSE;
11247     ics_getting_history = H_FALSE;
11248     ics_gamenum = -1;
11249     white_holding[0] = black_holding[0] = NULLCHAR;
11250     ClearProgramStats();
11251     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11252
11253     ResetFrontEnd();
11254     ClearHighlights();
11255     flipView = appData.flipView;
11256     ClearPremoveHighlights();
11257     gotPremove = FALSE;
11258     alarmSounded = FALSE;
11259
11260     GameEnds(EndOfFile, NULL, GE_PLAYER);
11261     if(appData.serverMovesName != NULL) {
11262         /* [HGM] prepare to make moves file for broadcasting */
11263         clock_t t = clock();
11264         if(serverMoves != NULL) fclose(serverMoves);
11265         serverMoves = fopen(appData.serverMovesName, "r");
11266         if(serverMoves != NULL) {
11267             fclose(serverMoves);
11268             /* delay 15 sec before overwriting, so all clients can see end */
11269             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11270         }
11271         serverMoves = fopen(appData.serverMovesName, "w");
11272     }
11273
11274     ExitAnalyzeMode();
11275     gameMode = BeginningOfGame;
11276     ModeHighlight();
11277     if(appData.icsActive) gameInfo.variant = VariantNormal;
11278     currentMove = forwardMostMove = backwardMostMove = 0;
11279     MarkTargetSquares(1);
11280     InitPosition(redraw);
11281     for (i = 0; i < MAX_MOVES; i++) {
11282         if (commentList[i] != NULL) {
11283             free(commentList[i]);
11284             commentList[i] = NULL;
11285         }
11286     }
11287     ResetClocks();
11288     timeRemaining[0][0] = whiteTimeRemaining;
11289     timeRemaining[1][0] = blackTimeRemaining;
11290
11291     if (first.pr == NoProc) {
11292         StartChessProgram(&first);
11293     }
11294     if (init) {
11295             InitChessProgram(&first, startedFromSetupPosition);
11296     }
11297     DisplayTitle("");
11298     DisplayMessage("", "");
11299     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11300     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11301     ClearMap();        // [HGM] exclude: invalidate map
11302 }
11303
11304 void
11305 AutoPlayGameLoop ()
11306 {
11307     for (;;) {
11308         if (!AutoPlayOneMove())
11309           return;
11310         if (matchMode || appData.timeDelay == 0)
11311           continue;
11312         if (appData.timeDelay < 0)
11313           return;
11314         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11315         break;
11316     }
11317 }
11318
11319 void
11320 AnalyzeNextGame()
11321 {
11322     ReloadGame(1); // next game
11323 }
11324
11325 int
11326 AutoPlayOneMove ()
11327 {
11328     int fromX, fromY, toX, toY;
11329
11330     if (appData.debugMode) {
11331       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11332     }
11333
11334     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11335       return FALSE;
11336
11337     if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
11338       pvInfoList[currentMove].depth = programStats.depth;
11339       pvInfoList[currentMove].score = programStats.score;
11340       pvInfoList[currentMove].time  = 0;
11341       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11342       else { // append analysis of final position as comment
11343         char buf[MSG_SIZ];
11344         snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
11345         AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
11346       }
11347       programStats.depth = 0;
11348     }
11349
11350     if (currentMove >= forwardMostMove) {
11351       if(gameMode == AnalyzeFile) {
11352           if(appData.loadGameIndex == -1) {
11353             GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
11354           ScheduleDelayedEvent(AnalyzeNextGame, 10);
11355           } else {
11356           ExitAnalyzeMode(); SendToProgram("force\n", &first);
11357         }
11358       }
11359 //      gameMode = EndOfGame;
11360 //      ModeHighlight();
11361
11362       /* [AS] Clear current move marker at the end of a game */
11363       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11364
11365       return FALSE;
11366     }
11367
11368     toX = moveList[currentMove][2] - AAA;
11369     toY = moveList[currentMove][3] - ONE;
11370
11371     if (moveList[currentMove][1] == '@') {
11372         if (appData.highlightLastMove) {
11373             SetHighlights(-1, -1, toX, toY);
11374         }
11375     } else {
11376         fromX = moveList[currentMove][0] - AAA;
11377         fromY = moveList[currentMove][1] - ONE;
11378
11379         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11380
11381         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11382
11383         if (appData.highlightLastMove) {
11384             SetHighlights(fromX, fromY, toX, toY);
11385         }
11386     }
11387     DisplayMove(currentMove);
11388     SendMoveToProgram(currentMove++, &first);
11389     DisplayBothClocks();
11390     DrawPosition(FALSE, boards[currentMove]);
11391     // [HGM] PV info: always display, routine tests if empty
11392     DisplayComment(currentMove - 1, commentList[currentMove]);
11393     return TRUE;
11394 }
11395
11396
11397 int
11398 LoadGameOneMove (ChessMove readAhead)
11399 {
11400     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11401     char promoChar = NULLCHAR;
11402     ChessMove moveType;
11403     char move[MSG_SIZ];
11404     char *p, *q;
11405
11406     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11407         gameMode != AnalyzeMode && gameMode != Training) {
11408         gameFileFP = NULL;
11409         return FALSE;
11410     }
11411
11412     yyboardindex = forwardMostMove;
11413     if (readAhead != EndOfFile) {
11414       moveType = readAhead;
11415     } else {
11416       if (gameFileFP == NULL)
11417           return FALSE;
11418       moveType = (ChessMove) Myylex();
11419     }
11420
11421     done = FALSE;
11422     switch (moveType) {
11423       case Comment:
11424         if (appData.debugMode)
11425           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11426         p = yy_text;
11427
11428         /* append the comment but don't display it */
11429         AppendComment(currentMove, p, FALSE);
11430         return TRUE;
11431
11432       case WhiteCapturesEnPassant:
11433       case BlackCapturesEnPassant:
11434       case WhitePromotion:
11435       case BlackPromotion:
11436       case WhiteNonPromotion:
11437       case BlackNonPromotion:
11438       case NormalMove:
11439       case WhiteKingSideCastle:
11440       case WhiteQueenSideCastle:
11441       case BlackKingSideCastle:
11442       case BlackQueenSideCastle:
11443       case WhiteKingSideCastleWild:
11444       case WhiteQueenSideCastleWild:
11445       case BlackKingSideCastleWild:
11446       case BlackQueenSideCastleWild:
11447       /* PUSH Fabien */
11448       case WhiteHSideCastleFR:
11449       case WhiteASideCastleFR:
11450       case BlackHSideCastleFR:
11451       case BlackASideCastleFR:
11452       /* POP Fabien */
11453         if (appData.debugMode)
11454           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11455         fromX = currentMoveString[0] - AAA;
11456         fromY = currentMoveString[1] - ONE;
11457         toX = currentMoveString[2] - AAA;
11458         toY = currentMoveString[3] - ONE;
11459         promoChar = currentMoveString[4];
11460         break;
11461
11462       case WhiteDrop:
11463       case BlackDrop:
11464         if (appData.debugMode)
11465           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11466         fromX = moveType == WhiteDrop ?
11467           (int) CharToPiece(ToUpper(currentMoveString[0])) :
11468         (int) CharToPiece(ToLower(currentMoveString[0]));
11469         fromY = DROP_RANK;
11470         toX = currentMoveString[2] - AAA;
11471         toY = currentMoveString[3] - ONE;
11472         break;
11473
11474       case WhiteWins:
11475       case BlackWins:
11476       case GameIsDrawn:
11477       case GameUnfinished:
11478         if (appData.debugMode)
11479           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11480         p = strchr(yy_text, '{');
11481         if (p == NULL) p = strchr(yy_text, '(');
11482         if (p == NULL) {
11483             p = yy_text;
11484             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11485         } else {
11486             q = strchr(p, *p == '{' ? '}' : ')');
11487             if (q != NULL) *q = NULLCHAR;
11488             p++;
11489         }
11490         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11491         GameEnds(moveType, p, GE_FILE);
11492         done = TRUE;
11493         if (cmailMsgLoaded) {
11494             ClearHighlights();
11495             flipView = WhiteOnMove(currentMove);
11496             if (moveType == GameUnfinished) flipView = !flipView;
11497             if (appData.debugMode)
11498               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11499         }
11500         break;
11501
11502       case EndOfFile:
11503         if (appData.debugMode)
11504           fprintf(debugFP, "Parser hit end of file\n");
11505         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11506           case MT_NONE:
11507           case MT_CHECK:
11508             break;
11509           case MT_CHECKMATE:
11510           case MT_STAINMATE:
11511             if (WhiteOnMove(currentMove)) {
11512                 GameEnds(BlackWins, "Black mates", GE_FILE);
11513             } else {
11514                 GameEnds(WhiteWins, "White mates", GE_FILE);
11515             }
11516             break;
11517           case MT_STALEMATE:
11518             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11519             break;
11520         }
11521         done = TRUE;
11522         break;
11523
11524       case MoveNumberOne:
11525         if (lastLoadGameStart == GNUChessGame) {
11526             /* GNUChessGames have numbers, but they aren't move numbers */
11527             if (appData.debugMode)
11528               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11529                       yy_text, (int) moveType);
11530             return LoadGameOneMove(EndOfFile); /* tail recursion */
11531         }
11532         /* else fall thru */
11533
11534       case XBoardGame:
11535       case GNUChessGame:
11536       case PGNTag:
11537         /* Reached start of next game in file */
11538         if (appData.debugMode)
11539           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11540         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11541           case MT_NONE:
11542           case MT_CHECK:
11543             break;
11544           case MT_CHECKMATE:
11545           case MT_STAINMATE:
11546             if (WhiteOnMove(currentMove)) {
11547                 GameEnds(BlackWins, "Black mates", GE_FILE);
11548             } else {
11549                 GameEnds(WhiteWins, "White mates", GE_FILE);
11550             }
11551             break;
11552           case MT_STALEMATE:
11553             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11554             break;
11555         }
11556         done = TRUE;
11557         break;
11558
11559       case PositionDiagram:     /* should not happen; ignore */
11560       case ElapsedTime:         /* ignore */
11561       case NAG:                 /* ignore */
11562         if (appData.debugMode)
11563           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11564                   yy_text, (int) moveType);
11565         return LoadGameOneMove(EndOfFile); /* tail recursion */
11566
11567       case IllegalMove:
11568         if (appData.testLegality) {
11569             if (appData.debugMode)
11570               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11571             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11572                     (forwardMostMove / 2) + 1,
11573                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11574             DisplayError(move, 0);
11575             done = TRUE;
11576         } else {
11577             if (appData.debugMode)
11578               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11579                       yy_text, currentMoveString);
11580             fromX = currentMoveString[0] - AAA;
11581             fromY = currentMoveString[1] - ONE;
11582             toX = currentMoveString[2] - AAA;
11583             toY = currentMoveString[3] - ONE;
11584             promoChar = currentMoveString[4];
11585         }
11586         break;
11587
11588       case AmbiguousMove:
11589         if (appData.debugMode)
11590           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11591         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11592                 (forwardMostMove / 2) + 1,
11593                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11594         DisplayError(move, 0);
11595         done = TRUE;
11596         break;
11597
11598       default:
11599       case ImpossibleMove:
11600         if (appData.debugMode)
11601           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11602         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11603                 (forwardMostMove / 2) + 1,
11604                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11605         DisplayError(move, 0);
11606         done = TRUE;
11607         break;
11608     }
11609
11610     if (done) {
11611         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11612             DrawPosition(FALSE, boards[currentMove]);
11613             DisplayBothClocks();
11614             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11615               DisplayComment(currentMove - 1, commentList[currentMove]);
11616         }
11617         (void) StopLoadGameTimer();
11618         gameFileFP = NULL;
11619         cmailOldMove = forwardMostMove;
11620         return FALSE;
11621     } else {
11622         /* currentMoveString is set as a side-effect of yylex */
11623
11624         thinkOutput[0] = NULLCHAR;
11625         MakeMove(fromX, fromY, toX, toY, promoChar);
11626         currentMove = forwardMostMove;
11627         return TRUE;
11628     }
11629 }
11630
11631 /* Load the nth game from the given file */
11632 int
11633 LoadGameFromFile (char *filename, int n, char *title, int useList)
11634 {
11635     FILE *f;
11636     char buf[MSG_SIZ];
11637
11638     if (strcmp(filename, "-") == 0) {
11639         f = stdin;
11640         title = "stdin";
11641     } else {
11642         f = fopen(filename, "rb");
11643         if (f == NULL) {
11644           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
11645             DisplayError(buf, errno);
11646             return FALSE;
11647         }
11648     }
11649     if (fseek(f, 0, 0) == -1) {
11650         /* f is not seekable; probably a pipe */
11651         useList = FALSE;
11652     }
11653     if (useList && n == 0) {
11654         int error = GameListBuild(f);
11655         if (error) {
11656             DisplayError(_("Cannot build game list"), error);
11657         } else if (!ListEmpty(&gameList) &&
11658                    ((ListGame *) gameList.tailPred)->number > 1) {
11659             GameListPopUp(f, title);
11660             return TRUE;
11661         }
11662         GameListDestroy();
11663         n = 1;
11664     }
11665     if (n == 0) n = 1;
11666     return LoadGame(f, n, title, FALSE);
11667 }
11668
11669
11670 void
11671 MakeRegisteredMove ()
11672 {
11673     int fromX, fromY, toX, toY;
11674     char promoChar;
11675     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11676         switch (cmailMoveType[lastLoadGameNumber - 1]) {
11677           case CMAIL_MOVE:
11678           case CMAIL_DRAW:
11679             if (appData.debugMode)
11680               fprintf(debugFP, "Restoring %s for game %d\n",
11681                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11682
11683             thinkOutput[0] = NULLCHAR;
11684             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11685             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11686             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11687             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11688             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11689             promoChar = cmailMove[lastLoadGameNumber - 1][4];
11690             MakeMove(fromX, fromY, toX, toY, promoChar);
11691             ShowMove(fromX, fromY, toX, toY);
11692
11693             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11694               case MT_NONE:
11695               case MT_CHECK:
11696                 break;
11697
11698               case MT_CHECKMATE:
11699               case MT_STAINMATE:
11700                 if (WhiteOnMove(currentMove)) {
11701                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
11702                 } else {
11703                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
11704                 }
11705                 break;
11706
11707               case MT_STALEMATE:
11708                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11709                 break;
11710             }
11711
11712             break;
11713
11714           case CMAIL_RESIGN:
11715             if (WhiteOnMove(currentMove)) {
11716                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11717             } else {
11718                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11719             }
11720             break;
11721
11722           case CMAIL_ACCEPT:
11723             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11724             break;
11725
11726           default:
11727             break;
11728         }
11729     }
11730
11731     return;
11732 }
11733
11734 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11735 int
11736 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11737 {
11738     int retVal;
11739
11740     if (gameNumber > nCmailGames) {
11741         DisplayError(_("No more games in this message"), 0);
11742         return FALSE;
11743     }
11744     if (f == lastLoadGameFP) {
11745         int offset = gameNumber - lastLoadGameNumber;
11746         if (offset == 0) {
11747             cmailMsg[0] = NULLCHAR;
11748             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11749                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11750                 nCmailMovesRegistered--;
11751             }
11752             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11753             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11754                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11755             }
11756         } else {
11757             if (! RegisterMove()) return FALSE;
11758         }
11759     }
11760
11761     retVal = LoadGame(f, gameNumber, title, useList);
11762
11763     /* Make move registered during previous look at this game, if any */
11764     MakeRegisteredMove();
11765
11766     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11767         commentList[currentMove]
11768           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11769         DisplayComment(currentMove - 1, commentList[currentMove]);
11770     }
11771
11772     return retVal;
11773 }
11774
11775 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11776 int
11777 ReloadGame (int offset)
11778 {
11779     int gameNumber = lastLoadGameNumber + offset;
11780     if (lastLoadGameFP == NULL) {
11781         DisplayError(_("No game has been loaded yet"), 0);
11782         return FALSE;
11783     }
11784     if (gameNumber <= 0) {
11785         DisplayError(_("Can't back up any further"), 0);
11786         return FALSE;
11787     }
11788     if (cmailMsgLoaded) {
11789         return CmailLoadGame(lastLoadGameFP, gameNumber,
11790                              lastLoadGameTitle, lastLoadGameUseList);
11791     } else {
11792         return LoadGame(lastLoadGameFP, gameNumber,
11793                         lastLoadGameTitle, lastLoadGameUseList);
11794     }
11795 }
11796
11797 int keys[EmptySquare+1];
11798
11799 int
11800 PositionMatches (Board b1, Board b2)
11801 {
11802     int r, f, sum=0;
11803     switch(appData.searchMode) {
11804         case 1: return CompareWithRights(b1, b2);
11805         case 2:
11806             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11807                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11808             }
11809             return TRUE;
11810         case 3:
11811             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11812               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11813                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11814             }
11815             return sum==0;
11816         case 4:
11817             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11818                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11819             }
11820             return sum==0;
11821     }
11822     return TRUE;
11823 }
11824
11825 #define Q_PROMO  4
11826 #define Q_EP     3
11827 #define Q_BCASTL 2
11828 #define Q_WCASTL 1
11829
11830 int pieceList[256], quickBoard[256];
11831 ChessSquare pieceType[256] = { EmptySquare };
11832 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11833 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11834 int soughtTotal, turn;
11835 Boolean epOK, flipSearch;
11836
11837 typedef struct {
11838     unsigned char piece, to;
11839 } Move;
11840
11841 #define DSIZE (250000)
11842
11843 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11844 Move *moveDatabase = initialSpace;
11845 unsigned int movePtr, dataSize = DSIZE;
11846
11847 int
11848 MakePieceList (Board board, int *counts)
11849 {
11850     int r, f, n=Q_PROMO, total=0;
11851     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11852     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11853         int sq = f + (r<<4);
11854         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
11855             quickBoard[sq] = ++n;
11856             pieceList[n] = sq;
11857             pieceType[n] = board[r][f];
11858             counts[board[r][f]]++;
11859             if(board[r][f] == WhiteKing) pieceList[1] = n; else
11860             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
11861             total++;
11862         }
11863     }
11864     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
11865     return total;
11866 }
11867
11868 void
11869 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
11870 {
11871     int sq = fromX + (fromY<<4);
11872     int piece = quickBoard[sq];
11873     quickBoard[sq] = 0;
11874     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
11875     if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11876         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
11877         moveDatabase[movePtr++].piece = Q_WCASTL;
11878         quickBoard[sq] = piece;
11879         piece = quickBoard[from]; quickBoard[from] = 0;
11880         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11881     } else
11882     if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11883         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
11884         moveDatabase[movePtr++].piece = Q_BCASTL;
11885         quickBoard[sq] = piece;
11886         piece = quickBoard[from]; quickBoard[from] = 0;
11887         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11888     } else
11889     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
11890         quickBoard[(fromY<<4)+toX] = 0;
11891         moveDatabase[movePtr].piece = Q_EP;
11892         moveDatabase[movePtr++].to = (fromY<<4)+toX;
11893         moveDatabase[movePtr].to = sq;
11894     } else
11895     if(promoPiece != pieceType[piece]) {
11896         moveDatabase[movePtr++].piece = Q_PROMO;
11897         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
11898     }
11899     moveDatabase[movePtr].piece = piece;
11900     quickBoard[sq] = piece;
11901     movePtr++;
11902 }
11903
11904 int
11905 PackGame (Board board)
11906 {
11907     Move *newSpace = NULL;
11908     moveDatabase[movePtr].piece = 0; // terminate previous game
11909     if(movePtr > dataSize) {
11910         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
11911         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
11912         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
11913         if(newSpace) {
11914             int i;
11915             Move *p = moveDatabase, *q = newSpace;
11916             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
11917             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
11918             moveDatabase = newSpace;
11919         } else { // calloc failed, we must be out of memory. Too bad...
11920             dataSize = 0; // prevent calloc events for all subsequent games
11921             return 0;     // and signal this one isn't cached
11922         }
11923     }
11924     movePtr++;
11925     MakePieceList(board, counts);
11926     return movePtr;
11927 }
11928
11929 int
11930 QuickCompare (Board board, int *minCounts, int *maxCounts)
11931 {   // compare according to search mode
11932     int r, f;
11933     switch(appData.searchMode)
11934     {
11935       case 1: // exact position match
11936         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
11937         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11938             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11939         }
11940         break;
11941       case 2: // can have extra material on empty squares
11942         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11943             if(board[r][f] == EmptySquare) continue;
11944             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11945         }
11946         break;
11947       case 3: // material with exact Pawn structure
11948         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11949             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
11950             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11951         } // fall through to material comparison
11952       case 4: // exact material
11953         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
11954         break;
11955       case 6: // material range with given imbalance
11956         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
11957         // fall through to range comparison
11958       case 5: // material range
11959         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
11960     }
11961     return TRUE;
11962 }
11963
11964 int
11965 QuickScan (Board board, Move *move)
11966 {   // reconstruct game,and compare all positions in it
11967     int cnt=0, stretch=0, total = MakePieceList(board, counts);
11968     do {
11969         int piece = move->piece;
11970         int to = move->to, from = pieceList[piece];
11971         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
11972           if(!piece) return -1;
11973           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
11974             piece = (++move)->piece;
11975             from = pieceList[piece];
11976             counts[pieceType[piece]]--;
11977             pieceType[piece] = (ChessSquare) move->to;
11978             counts[move->to]++;
11979           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
11980             counts[pieceType[quickBoard[to]]]--;
11981             quickBoard[to] = 0; total--;
11982             move++;
11983             continue;
11984           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
11985             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
11986             from  = pieceList[piece]; // so this must be King
11987             quickBoard[from] = 0;
11988             pieceList[piece] = to;
11989             from = pieceList[(++move)->piece]; // for FRC this has to be done here
11990             quickBoard[from] = 0; // rook
11991             quickBoard[to] = piece;
11992             to = move->to; piece = move->piece;
11993             goto aftercastle;
11994           }
11995         }
11996         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
11997         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
11998         quickBoard[from] = 0;
11999       aftercastle:
12000         quickBoard[to] = piece;
12001         pieceList[piece] = to;
12002         cnt++; turn ^= 3;
12003         if(QuickCompare(soughtBoard, minSought, maxSought) ||
12004            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12005            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12006                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12007           ) {
12008             static int lastCounts[EmptySquare+1];
12009             int i;
12010             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12011             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12012         } else stretch = 0;
12013         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
12014         move++;
12015     } while(1);
12016 }
12017
12018 void
12019 InitSearch ()
12020 {
12021     int r, f;
12022     flipSearch = FALSE;
12023     CopyBoard(soughtBoard, boards[currentMove]);
12024     soughtTotal = MakePieceList(soughtBoard, maxSought);
12025     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12026     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12027     CopyBoard(reverseBoard, boards[currentMove]);
12028     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12029         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12030         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12031         reverseBoard[r][f] = piece;
12032     }
12033     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12034     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12035     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12036                  || (boards[currentMove][CASTLING][2] == NoRights ||
12037                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12038                  && (boards[currentMove][CASTLING][5] == NoRights ||
12039                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12040       ) {
12041         flipSearch = TRUE;
12042         CopyBoard(flipBoard, soughtBoard);
12043         CopyBoard(rotateBoard, reverseBoard);
12044         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12045             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
12046             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12047         }
12048     }
12049     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12050     if(appData.searchMode >= 5) {
12051         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12052         MakePieceList(soughtBoard, minSought);
12053         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12054     }
12055     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12056         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12057 }
12058
12059 GameInfo dummyInfo;
12060 static int creatingBook;
12061
12062 int
12063 GameContainsPosition (FILE *f, ListGame *lg)
12064 {
12065     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12066     int fromX, fromY, toX, toY;
12067     char promoChar;
12068     static int initDone=FALSE;
12069
12070     // weed out games based on numerical tag comparison
12071     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12072     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12073     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12074     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12075     if(!initDone) {
12076         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12077         initDone = TRUE;
12078     }
12079     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen);
12080     else CopyBoard(boards[scratch], initialPosition); // default start position
12081     if(lg->moves) {
12082         turn = btm + 1;
12083         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12084         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12085     }
12086     if(btm) plyNr++;
12087     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12088     fseek(f, lg->offset, 0);
12089     yynewfile(f);
12090     while(1) {
12091         yyboardindex = scratch;
12092         quickFlag = plyNr+1;
12093         next = Myylex();
12094         quickFlag = 0;
12095         switch(next) {
12096             case PGNTag:
12097                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12098             default:
12099                 continue;
12100
12101             case XBoardGame:
12102             case GNUChessGame:
12103                 if(plyNr) return -1; // after we have seen moves, this is for new game
12104               continue;
12105
12106             case AmbiguousMove: // we cannot reconstruct the game beyond these two
12107             case ImpossibleMove:
12108             case WhiteWins: // game ends here with these four
12109             case BlackWins:
12110             case GameIsDrawn:
12111             case GameUnfinished:
12112                 return -1;
12113
12114             case IllegalMove:
12115                 if(appData.testLegality) return -1;
12116             case WhiteCapturesEnPassant:
12117             case BlackCapturesEnPassant:
12118             case WhitePromotion:
12119             case BlackPromotion:
12120             case WhiteNonPromotion:
12121             case BlackNonPromotion:
12122             case NormalMove:
12123             case WhiteKingSideCastle:
12124             case WhiteQueenSideCastle:
12125             case BlackKingSideCastle:
12126             case BlackQueenSideCastle:
12127             case WhiteKingSideCastleWild:
12128             case WhiteQueenSideCastleWild:
12129             case BlackKingSideCastleWild:
12130             case BlackQueenSideCastleWild:
12131             case WhiteHSideCastleFR:
12132             case WhiteASideCastleFR:
12133             case BlackHSideCastleFR:
12134             case BlackASideCastleFR:
12135                 fromX = currentMoveString[0] - AAA;
12136                 fromY = currentMoveString[1] - ONE;
12137                 toX = currentMoveString[2] - AAA;
12138                 toY = currentMoveString[3] - ONE;
12139                 promoChar = currentMoveString[4];
12140                 break;
12141             case WhiteDrop:
12142             case BlackDrop:
12143                 fromX = next == WhiteDrop ?
12144                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
12145                   (int) CharToPiece(ToLower(currentMoveString[0]));
12146                 fromY = DROP_RANK;
12147                 toX = currentMoveString[2] - AAA;
12148                 toY = currentMoveString[3] - ONE;
12149                 promoChar = 0;
12150                 break;
12151         }
12152         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12153         plyNr++;
12154         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12155         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12156         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12157         if(appData.findMirror) {
12158             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12159             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12160         }
12161     }
12162 }
12163
12164 /* Load the nth game from open file f */
12165 int
12166 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12167 {
12168     ChessMove cm;
12169     char buf[MSG_SIZ];
12170     int gn = gameNumber;
12171     ListGame *lg = NULL;
12172     int numPGNTags = 0;
12173     int err, pos = -1;
12174     GameMode oldGameMode;
12175     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12176
12177     if (appData.debugMode)
12178         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12179
12180     if (gameMode == Training )
12181         SetTrainingModeOff();
12182
12183     oldGameMode = gameMode;
12184     if (gameMode != BeginningOfGame) {
12185       Reset(FALSE, TRUE);
12186     }
12187
12188     gameFileFP = f;
12189     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12190         fclose(lastLoadGameFP);
12191     }
12192
12193     if (useList) {
12194         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12195
12196         if (lg) {
12197             fseek(f, lg->offset, 0);
12198             GameListHighlight(gameNumber);
12199             pos = lg->position;
12200             gn = 1;
12201         }
12202         else {
12203             if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
12204               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12205             else
12206             DisplayError(_("Game number out of range"), 0);
12207             return FALSE;
12208         }
12209     } else {
12210         GameListDestroy();
12211         if (fseek(f, 0, 0) == -1) {
12212             if (f == lastLoadGameFP ?
12213                 gameNumber == lastLoadGameNumber + 1 :
12214                 gameNumber == 1) {
12215                 gn = 1;
12216             } else {
12217                 DisplayError(_("Can't seek on game file"), 0);
12218                 return FALSE;
12219             }
12220         }
12221     }
12222     lastLoadGameFP = f;
12223     lastLoadGameNumber = gameNumber;
12224     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12225     lastLoadGameUseList = useList;
12226
12227     yynewfile(f);
12228
12229     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12230       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12231                 lg->gameInfo.black);
12232             DisplayTitle(buf);
12233     } else if (*title != NULLCHAR) {
12234         if (gameNumber > 1) {
12235           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12236             DisplayTitle(buf);
12237         } else {
12238             DisplayTitle(title);
12239         }
12240     }
12241
12242     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12243         gameMode = PlayFromGameFile;
12244         ModeHighlight();
12245     }
12246
12247     currentMove = forwardMostMove = backwardMostMove = 0;
12248     CopyBoard(boards[0], initialPosition);
12249     StopClocks();
12250
12251     /*
12252      * Skip the first gn-1 games in the file.
12253      * Also skip over anything that precedes an identifiable
12254      * start of game marker, to avoid being confused by
12255      * garbage at the start of the file.  Currently
12256      * recognized start of game markers are the move number "1",
12257      * the pattern "gnuchess .* game", the pattern
12258      * "^[#;%] [^ ]* game file", and a PGN tag block.
12259      * A game that starts with one of the latter two patterns
12260      * will also have a move number 1, possibly
12261      * following a position diagram.
12262      * 5-4-02: Let's try being more lenient and allowing a game to
12263      * start with an unnumbered move.  Does that break anything?
12264      */
12265     cm = lastLoadGameStart = EndOfFile;
12266     while (gn > 0) {
12267         yyboardindex = forwardMostMove;
12268         cm = (ChessMove) Myylex();
12269         switch (cm) {
12270           case EndOfFile:
12271             if (cmailMsgLoaded) {
12272                 nCmailGames = CMAIL_MAX_GAMES - gn;
12273             } else {
12274                 Reset(TRUE, TRUE);
12275                 DisplayError(_("Game not found in file"), 0);
12276             }
12277             return FALSE;
12278
12279           case GNUChessGame:
12280           case XBoardGame:
12281             gn--;
12282             lastLoadGameStart = cm;
12283             break;
12284
12285           case MoveNumberOne:
12286             switch (lastLoadGameStart) {
12287               case GNUChessGame:
12288               case XBoardGame:
12289               case PGNTag:
12290                 break;
12291               case MoveNumberOne:
12292               case EndOfFile:
12293                 gn--;           /* count this game */
12294                 lastLoadGameStart = cm;
12295                 break;
12296               default:
12297                 /* impossible */
12298                 break;
12299             }
12300             break;
12301
12302           case PGNTag:
12303             switch (lastLoadGameStart) {
12304               case GNUChessGame:
12305               case PGNTag:
12306               case MoveNumberOne:
12307               case EndOfFile:
12308                 gn--;           /* count this game */
12309                 lastLoadGameStart = cm;
12310                 break;
12311               case XBoardGame:
12312                 lastLoadGameStart = cm; /* game counted already */
12313                 break;
12314               default:
12315                 /* impossible */
12316                 break;
12317             }
12318             if (gn > 0) {
12319                 do {
12320                     yyboardindex = forwardMostMove;
12321                     cm = (ChessMove) Myylex();
12322                 } while (cm == PGNTag || cm == Comment);
12323             }
12324             break;
12325
12326           case WhiteWins:
12327           case BlackWins:
12328           case GameIsDrawn:
12329             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
12330                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
12331                     != CMAIL_OLD_RESULT) {
12332                     nCmailResults ++ ;
12333                     cmailResult[  CMAIL_MAX_GAMES
12334                                 - gn - 1] = CMAIL_OLD_RESULT;
12335                 }
12336             }
12337             break;
12338
12339           case NormalMove:
12340             /* Only a NormalMove can be at the start of a game
12341              * without a position diagram. */
12342             if (lastLoadGameStart == EndOfFile ) {
12343               gn--;
12344               lastLoadGameStart = MoveNumberOne;
12345             }
12346             break;
12347
12348           default:
12349             break;
12350         }
12351     }
12352
12353     if (appData.debugMode)
12354       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
12355
12356     if (cm == XBoardGame) {
12357         /* Skip any header junk before position diagram and/or move 1 */
12358         for (;;) {
12359             yyboardindex = forwardMostMove;
12360             cm = (ChessMove) Myylex();
12361
12362             if (cm == EndOfFile ||
12363                 cm == GNUChessGame || cm == XBoardGame) {
12364                 /* Empty game; pretend end-of-file and handle later */
12365                 cm = EndOfFile;
12366                 break;
12367             }
12368
12369             if (cm == MoveNumberOne || cm == PositionDiagram ||
12370                 cm == PGNTag || cm == Comment)
12371               break;
12372         }
12373     } else if (cm == GNUChessGame) {
12374         if (gameInfo.event != NULL) {
12375             free(gameInfo.event);
12376         }
12377         gameInfo.event = StrSave(yy_text);
12378     }
12379
12380     startedFromSetupPosition = FALSE;
12381     while (cm == PGNTag) {
12382         if (appData.debugMode)
12383           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
12384         err = ParsePGNTag(yy_text, &gameInfo);
12385         if (!err) numPGNTags++;
12386
12387         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
12388         if(gameInfo.variant != oldVariant) {
12389             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
12390             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
12391             InitPosition(TRUE);
12392             oldVariant = gameInfo.variant;
12393             if (appData.debugMode)
12394               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
12395         }
12396
12397
12398         if (gameInfo.fen != NULL) {
12399           Board initial_position;
12400           startedFromSetupPosition = TRUE;
12401           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
12402             Reset(TRUE, TRUE);
12403             DisplayError(_("Bad FEN position in file"), 0);
12404             return FALSE;
12405           }
12406           CopyBoard(boards[0], initial_position);
12407           if (blackPlaysFirst) {
12408             currentMove = forwardMostMove = backwardMostMove = 1;
12409             CopyBoard(boards[1], initial_position);
12410             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12411             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12412             timeRemaining[0][1] = whiteTimeRemaining;
12413             timeRemaining[1][1] = blackTimeRemaining;
12414             if (commentList[0] != NULL) {
12415               commentList[1] = commentList[0];
12416               commentList[0] = NULL;
12417             }
12418           } else {
12419             currentMove = forwardMostMove = backwardMostMove = 0;
12420           }
12421           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12422           {   int i;
12423               initialRulePlies = FENrulePlies;
12424               for( i=0; i< nrCastlingRights; i++ )
12425                   initialRights[i] = initial_position[CASTLING][i];
12426           }
12427           yyboardindex = forwardMostMove;
12428           free(gameInfo.fen);
12429           gameInfo.fen = NULL;
12430         }
12431
12432         yyboardindex = forwardMostMove;
12433         cm = (ChessMove) Myylex();
12434
12435         /* Handle comments interspersed among the tags */
12436         while (cm == Comment) {
12437             char *p;
12438             if (appData.debugMode)
12439               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12440             p = yy_text;
12441             AppendComment(currentMove, p, FALSE);
12442             yyboardindex = forwardMostMove;
12443             cm = (ChessMove) Myylex();
12444         }
12445     }
12446
12447     /* don't rely on existence of Event tag since if game was
12448      * pasted from clipboard the Event tag may not exist
12449      */
12450     if (numPGNTags > 0){
12451         char *tags;
12452         if (gameInfo.variant == VariantNormal) {
12453           VariantClass v = StringToVariant(gameInfo.event);
12454           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12455           if(v < VariantShogi) gameInfo.variant = v;
12456         }
12457         if (!matchMode) {
12458           if( appData.autoDisplayTags ) {
12459             tags = PGNTags(&gameInfo);
12460             TagsPopUp(tags, CmailMsg());
12461             free(tags);
12462           }
12463         }
12464     } else {
12465         /* Make something up, but don't display it now */
12466         SetGameInfo();
12467         TagsPopDown();
12468     }
12469
12470     if (cm == PositionDiagram) {
12471         int i, j;
12472         char *p;
12473         Board initial_position;
12474
12475         if (appData.debugMode)
12476           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12477
12478         if (!startedFromSetupPosition) {
12479             p = yy_text;
12480             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12481               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12482                 switch (*p) {
12483                   case '{':
12484                   case '[':
12485                   case '-':
12486                   case ' ':
12487                   case '\t':
12488                   case '\n':
12489                   case '\r':
12490                     break;
12491                   default:
12492                     initial_position[i][j++] = CharToPiece(*p);
12493                     break;
12494                 }
12495             while (*p == ' ' || *p == '\t' ||
12496                    *p == '\n' || *p == '\r') p++;
12497
12498             if (strncmp(p, "black", strlen("black"))==0)
12499               blackPlaysFirst = TRUE;
12500             else
12501               blackPlaysFirst = FALSE;
12502             startedFromSetupPosition = TRUE;
12503
12504             CopyBoard(boards[0], initial_position);
12505             if (blackPlaysFirst) {
12506                 currentMove = forwardMostMove = backwardMostMove = 1;
12507                 CopyBoard(boards[1], initial_position);
12508                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12509                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12510                 timeRemaining[0][1] = whiteTimeRemaining;
12511                 timeRemaining[1][1] = blackTimeRemaining;
12512                 if (commentList[0] != NULL) {
12513                     commentList[1] = commentList[0];
12514                     commentList[0] = NULL;
12515                 }
12516             } else {
12517                 currentMove = forwardMostMove = backwardMostMove = 0;
12518             }
12519         }
12520         yyboardindex = forwardMostMove;
12521         cm = (ChessMove) Myylex();
12522     }
12523
12524   if(!creatingBook) {
12525     if (first.pr == NoProc) {
12526         StartChessProgram(&first);
12527     }
12528     InitChessProgram(&first, FALSE);
12529     SendToProgram("force\n", &first);
12530     if (startedFromSetupPosition) {
12531         SendBoard(&first, forwardMostMove);
12532     if (appData.debugMode) {
12533         fprintf(debugFP, "Load Game\n");
12534     }
12535         DisplayBothClocks();
12536     }
12537   }
12538
12539     /* [HGM] server: flag to write setup moves in broadcast file as one */
12540     loadFlag = appData.suppressLoadMoves;
12541
12542     while (cm == Comment) {
12543         char *p;
12544         if (appData.debugMode)
12545           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12546         p = yy_text;
12547         AppendComment(currentMove, p, FALSE);
12548         yyboardindex = forwardMostMove;
12549         cm = (ChessMove) Myylex();
12550     }
12551
12552     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12553         cm == WhiteWins || cm == BlackWins ||
12554         cm == GameIsDrawn || cm == GameUnfinished) {
12555         DisplayMessage("", _("No moves in game"));
12556         if (cmailMsgLoaded) {
12557             if (appData.debugMode)
12558               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12559             ClearHighlights();
12560             flipView = FALSE;
12561         }
12562         DrawPosition(FALSE, boards[currentMove]);
12563         DisplayBothClocks();
12564         gameMode = EditGame;
12565         ModeHighlight();
12566         gameFileFP = NULL;
12567         cmailOldMove = 0;
12568         return TRUE;
12569     }
12570
12571     // [HGM] PV info: routine tests if comment empty
12572     if (!matchMode && (pausing || appData.timeDelay != 0)) {
12573         DisplayComment(currentMove - 1, commentList[currentMove]);
12574     }
12575     if (!matchMode && appData.timeDelay != 0)
12576       DrawPosition(FALSE, boards[currentMove]);
12577
12578     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12579       programStats.ok_to_send = 1;
12580     }
12581
12582     /* if the first token after the PGN tags is a move
12583      * and not move number 1, retrieve it from the parser
12584      */
12585     if (cm != MoveNumberOne)
12586         LoadGameOneMove(cm);
12587
12588     /* load the remaining moves from the file */
12589     while (LoadGameOneMove(EndOfFile)) {
12590       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12591       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12592     }
12593
12594     /* rewind to the start of the game */
12595     currentMove = backwardMostMove;
12596
12597     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12598
12599     if (oldGameMode == AnalyzeFile) {
12600       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
12601       AnalyzeFileEvent();
12602     } else
12603     if (oldGameMode == AnalyzeMode) {
12604       AnalyzeFileEvent();
12605     }
12606
12607     if(creatingBook) return TRUE;
12608     if (!matchMode && pos > 0) {
12609         ToNrEvent(pos); // [HGM] no autoplay if selected on position
12610     } else
12611     if (matchMode || appData.timeDelay == 0) {
12612       ToEndEvent();
12613     } else if (appData.timeDelay > 0) {
12614       AutoPlayGameLoop();
12615     }
12616
12617     if (appData.debugMode)
12618         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
12619
12620     loadFlag = 0; /* [HGM] true game starts */
12621     return TRUE;
12622 }
12623
12624 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
12625 int
12626 ReloadPosition (int offset)
12627 {
12628     int positionNumber = lastLoadPositionNumber + offset;
12629     if (lastLoadPositionFP == NULL) {
12630         DisplayError(_("No position has been loaded yet"), 0);
12631         return FALSE;
12632     }
12633     if (positionNumber <= 0) {
12634         DisplayError(_("Can't back up any further"), 0);
12635         return FALSE;
12636     }
12637     return LoadPosition(lastLoadPositionFP, positionNumber,
12638                         lastLoadPositionTitle);
12639 }
12640
12641 /* Load the nth position from the given file */
12642 int
12643 LoadPositionFromFile (char *filename, int n, char *title)
12644 {
12645     FILE *f;
12646     char buf[MSG_SIZ];
12647
12648     if (strcmp(filename, "-") == 0) {
12649         return LoadPosition(stdin, n, "stdin");
12650     } else {
12651         f = fopen(filename, "rb");
12652         if (f == NULL) {
12653             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12654             DisplayError(buf, errno);
12655             return FALSE;
12656         } else {
12657             return LoadPosition(f, n, title);
12658         }
12659     }
12660 }
12661
12662 /* Load the nth position from the given open file, and close it */
12663 int
12664 LoadPosition (FILE *f, int positionNumber, char *title)
12665 {
12666     char *p, line[MSG_SIZ];
12667     Board initial_position;
12668     int i, j, fenMode, pn;
12669
12670     if (gameMode == Training )
12671         SetTrainingModeOff();
12672
12673     if (gameMode != BeginningOfGame) {
12674         Reset(FALSE, TRUE);
12675     }
12676     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12677         fclose(lastLoadPositionFP);
12678     }
12679     if (positionNumber == 0) positionNumber = 1;
12680     lastLoadPositionFP = f;
12681     lastLoadPositionNumber = positionNumber;
12682     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12683     if (first.pr == NoProc && !appData.noChessProgram) {
12684       StartChessProgram(&first);
12685       InitChessProgram(&first, FALSE);
12686     }
12687     pn = positionNumber;
12688     if (positionNumber < 0) {
12689         /* Negative position number means to seek to that byte offset */
12690         if (fseek(f, -positionNumber, 0) == -1) {
12691             DisplayError(_("Can't seek on position file"), 0);
12692             return FALSE;
12693         };
12694         pn = 1;
12695     } else {
12696         if (fseek(f, 0, 0) == -1) {
12697             if (f == lastLoadPositionFP ?
12698                 positionNumber == lastLoadPositionNumber + 1 :
12699                 positionNumber == 1) {
12700                 pn = 1;
12701             } else {
12702                 DisplayError(_("Can't seek on position file"), 0);
12703                 return FALSE;
12704             }
12705         }
12706     }
12707     /* See if this file is FEN or old-style xboard */
12708     if (fgets(line, MSG_SIZ, f) == NULL) {
12709         DisplayError(_("Position not found in file"), 0);
12710         return FALSE;
12711     }
12712     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12713     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12714
12715     if (pn >= 2) {
12716         if (fenMode || line[0] == '#') pn--;
12717         while (pn > 0) {
12718             /* skip positions before number pn */
12719             if (fgets(line, MSG_SIZ, f) == NULL) {
12720                 Reset(TRUE, TRUE);
12721                 DisplayError(_("Position not found in file"), 0);
12722                 return FALSE;
12723             }
12724             if (fenMode || line[0] == '#') pn--;
12725         }
12726     }
12727
12728     if (fenMode) {
12729         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
12730             DisplayError(_("Bad FEN position in file"), 0);
12731             return FALSE;
12732         }
12733     } else {
12734         (void) fgets(line, MSG_SIZ, f);
12735         (void) fgets(line, MSG_SIZ, f);
12736
12737         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12738             (void) fgets(line, MSG_SIZ, f);
12739             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12740                 if (*p == ' ')
12741                   continue;
12742                 initial_position[i][j++] = CharToPiece(*p);
12743             }
12744         }
12745
12746         blackPlaysFirst = FALSE;
12747         if (!feof(f)) {
12748             (void) fgets(line, MSG_SIZ, f);
12749             if (strncmp(line, "black", strlen("black"))==0)
12750               blackPlaysFirst = TRUE;
12751         }
12752     }
12753     startedFromSetupPosition = TRUE;
12754
12755     CopyBoard(boards[0], initial_position);
12756     if (blackPlaysFirst) {
12757         currentMove = forwardMostMove = backwardMostMove = 1;
12758         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12759         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12760         CopyBoard(boards[1], initial_position);
12761         DisplayMessage("", _("Black to play"));
12762     } else {
12763         currentMove = forwardMostMove = backwardMostMove = 0;
12764         DisplayMessage("", _("White to play"));
12765     }
12766     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12767     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12768         SendToProgram("force\n", &first);
12769         SendBoard(&first, forwardMostMove);
12770     }
12771     if (appData.debugMode) {
12772 int i, j;
12773   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12774   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12775         fprintf(debugFP, "Load Position\n");
12776     }
12777
12778     if (positionNumber > 1) {
12779       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12780         DisplayTitle(line);
12781     } else {
12782         DisplayTitle(title);
12783     }
12784     gameMode = EditGame;
12785     ModeHighlight();
12786     ResetClocks();
12787     timeRemaining[0][1] = whiteTimeRemaining;
12788     timeRemaining[1][1] = blackTimeRemaining;
12789     DrawPosition(FALSE, boards[currentMove]);
12790
12791     return TRUE;
12792 }
12793
12794
12795 void
12796 CopyPlayerNameIntoFileName (char **dest, char *src)
12797 {
12798     while (*src != NULLCHAR && *src != ',') {
12799         if (*src == ' ') {
12800             *(*dest)++ = '_';
12801             src++;
12802         } else {
12803             *(*dest)++ = *src++;
12804         }
12805     }
12806 }
12807
12808 char *
12809 DefaultFileName (char *ext)
12810 {
12811     static char def[MSG_SIZ];
12812     char *p;
12813
12814     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12815         p = def;
12816         CopyPlayerNameIntoFileName(&p, gameInfo.white);
12817         *p++ = '-';
12818         CopyPlayerNameIntoFileName(&p, gameInfo.black);
12819         *p++ = '.';
12820         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12821     } else {
12822         def[0] = NULLCHAR;
12823     }
12824     return def;
12825 }
12826
12827 /* Save the current game to the given file */
12828 int
12829 SaveGameToFile (char *filename, int append)
12830 {
12831     FILE *f;
12832     char buf[MSG_SIZ];
12833     int result, i, t,tot=0;
12834
12835     if (strcmp(filename, "-") == 0) {
12836         return SaveGame(stdout, 0, NULL);
12837     } else {
12838         for(i=0; i<10; i++) { // upto 10 tries
12839              f = fopen(filename, append ? "a" : "w");
12840              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12841              if(f || errno != 13) break;
12842              DoSleep(t = 5 + random()%11); // wait 5-15 msec
12843              tot += t;
12844         }
12845         if (f == NULL) {
12846             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12847             DisplayError(buf, errno);
12848             return FALSE;
12849         } else {
12850             safeStrCpy(buf, lastMsg, MSG_SIZ);
12851             DisplayMessage(_("Waiting for access to save file"), "");
12852             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
12853             DisplayMessage(_("Saving game"), "");
12854             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
12855             result = SaveGame(f, 0, NULL);
12856             DisplayMessage(buf, "");
12857             return result;
12858         }
12859     }
12860 }
12861
12862 char *
12863 SavePart (char *str)
12864 {
12865     static char buf[MSG_SIZ];
12866     char *p;
12867
12868     p = strchr(str, ' ');
12869     if (p == NULL) return str;
12870     strncpy(buf, str, p - str);
12871     buf[p - str] = NULLCHAR;
12872     return buf;
12873 }
12874
12875 #define PGN_MAX_LINE 75
12876
12877 #define PGN_SIDE_WHITE  0
12878 #define PGN_SIDE_BLACK  1
12879
12880 static int
12881 FindFirstMoveOutOfBook (int side)
12882 {
12883     int result = -1;
12884
12885     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12886         int index = backwardMostMove;
12887         int has_book_hit = 0;
12888
12889         if( (index % 2) != side ) {
12890             index++;
12891         }
12892
12893         while( index < forwardMostMove ) {
12894             /* Check to see if engine is in book */
12895             int depth = pvInfoList[index].depth;
12896             int score = pvInfoList[index].score;
12897             int in_book = 0;
12898
12899             if( depth <= 2 ) {
12900                 in_book = 1;
12901             }
12902             else if( score == 0 && depth == 63 ) {
12903                 in_book = 1; /* Zappa */
12904             }
12905             else if( score == 2 && depth == 99 ) {
12906                 in_book = 1; /* Abrok */
12907             }
12908
12909             has_book_hit += in_book;
12910
12911             if( ! in_book ) {
12912                 result = index;
12913
12914                 break;
12915             }
12916
12917             index += 2;
12918         }
12919     }
12920
12921     return result;
12922 }
12923
12924 void
12925 GetOutOfBookInfo (char * buf)
12926 {
12927     int oob[2];
12928     int i;
12929     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12930
12931     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12932     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12933
12934     *buf = '\0';
12935
12936     if( oob[0] >= 0 || oob[1] >= 0 ) {
12937         for( i=0; i<2; i++ ) {
12938             int idx = oob[i];
12939
12940             if( idx >= 0 ) {
12941                 if( i > 0 && oob[0] >= 0 ) {
12942                     strcat( buf, "   " );
12943                 }
12944
12945                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12946                 sprintf( buf+strlen(buf), "%s%.2f",
12947                     pvInfoList[idx].score >= 0 ? "+" : "",
12948                     pvInfoList[idx].score / 100.0 );
12949             }
12950         }
12951     }
12952 }
12953
12954 /* Save game in PGN style and close the file */
12955 int
12956 SaveGamePGN (FILE *f)
12957 {
12958     int i, offset, linelen, newblock;
12959 //    char *movetext;
12960     char numtext[32];
12961     int movelen, numlen, blank;
12962     char move_buffer[100]; /* [AS] Buffer for move+PV info */
12963
12964     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12965
12966     PrintPGNTags(f, &gameInfo);
12967
12968     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
12969
12970     if (backwardMostMove > 0 || startedFromSetupPosition) {
12971         char *fen = PositionToFEN(backwardMostMove, NULL, 1);
12972         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12973         fprintf(f, "\n{--------------\n");
12974         PrintPosition(f, backwardMostMove);
12975         fprintf(f, "--------------}\n");
12976         free(fen);
12977     }
12978     else {
12979         /* [AS] Out of book annotation */
12980         if( appData.saveOutOfBookInfo ) {
12981             char buf[64];
12982
12983             GetOutOfBookInfo( buf );
12984
12985             if( buf[0] != '\0' ) {
12986                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12987             }
12988         }
12989
12990         fprintf(f, "\n");
12991     }
12992
12993     i = backwardMostMove;
12994     linelen = 0;
12995     newblock = TRUE;
12996
12997     while (i < forwardMostMove) {
12998         /* Print comments preceding this move */
12999         if (commentList[i] != NULL) {
13000             if (linelen > 0) fprintf(f, "\n");
13001             fprintf(f, "%s", commentList[i]);
13002             linelen = 0;
13003             newblock = TRUE;
13004         }
13005
13006         /* Format move number */
13007         if ((i % 2) == 0)
13008           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13009         else
13010           if (newblock)
13011             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13012           else
13013             numtext[0] = NULLCHAR;
13014
13015         numlen = strlen(numtext);
13016         newblock = FALSE;
13017
13018         /* Print move number */
13019         blank = linelen > 0 && numlen > 0;
13020         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13021             fprintf(f, "\n");
13022             linelen = 0;
13023             blank = 0;
13024         }
13025         if (blank) {
13026             fprintf(f, " ");
13027             linelen++;
13028         }
13029         fprintf(f, "%s", numtext);
13030         linelen += numlen;
13031
13032         /* Get move */
13033         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13034         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13035
13036         /* Print move */
13037         blank = linelen > 0 && movelen > 0;
13038         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13039             fprintf(f, "\n");
13040             linelen = 0;
13041             blank = 0;
13042         }
13043         if (blank) {
13044             fprintf(f, " ");
13045             linelen++;
13046         }
13047         fprintf(f, "%s", move_buffer);
13048         linelen += movelen;
13049
13050         /* [AS] Add PV info if present */
13051         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13052             /* [HGM] add time */
13053             char buf[MSG_SIZ]; int seconds;
13054
13055             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13056
13057             if( seconds <= 0)
13058               buf[0] = 0;
13059             else
13060               if( seconds < 30 )
13061                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13062               else
13063                 {
13064                   seconds = (seconds + 4)/10; // round to full seconds
13065                   if( seconds < 60 )
13066                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
13067                   else
13068                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
13069                 }
13070
13071             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
13072                       pvInfoList[i].score >= 0 ? "+" : "",
13073                       pvInfoList[i].score / 100.0,
13074                       pvInfoList[i].depth,
13075                       buf );
13076
13077             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
13078
13079             /* Print score/depth */
13080             blank = linelen > 0 && movelen > 0;
13081             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13082                 fprintf(f, "\n");
13083                 linelen = 0;
13084                 blank = 0;
13085             }
13086             if (blank) {
13087                 fprintf(f, " ");
13088                 linelen++;
13089             }
13090             fprintf(f, "%s", move_buffer);
13091             linelen += movelen;
13092         }
13093
13094         i++;
13095     }
13096
13097     /* Start a new line */
13098     if (linelen > 0) fprintf(f, "\n");
13099
13100     /* Print comments after last move */
13101     if (commentList[i] != NULL) {
13102         fprintf(f, "%s\n", commentList[i]);
13103     }
13104
13105     /* Print result */
13106     if (gameInfo.resultDetails != NULL &&
13107         gameInfo.resultDetails[0] != NULLCHAR) {
13108         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
13109                 PGNResult(gameInfo.result));
13110     } else {
13111         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13112     }
13113
13114     fclose(f);
13115     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13116     return TRUE;
13117 }
13118
13119 /* Save game in old style and close the file */
13120 int
13121 SaveGameOldStyle (FILE *f)
13122 {
13123     int i, offset;
13124     time_t tm;
13125
13126     tm = time((time_t *) NULL);
13127
13128     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
13129     PrintOpponents(f);
13130
13131     if (backwardMostMove > 0 || startedFromSetupPosition) {
13132         fprintf(f, "\n[--------------\n");
13133         PrintPosition(f, backwardMostMove);
13134         fprintf(f, "--------------]\n");
13135     } else {
13136         fprintf(f, "\n");
13137     }
13138
13139     i = backwardMostMove;
13140     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13141
13142     while (i < forwardMostMove) {
13143         if (commentList[i] != NULL) {
13144             fprintf(f, "[%s]\n", commentList[i]);
13145         }
13146
13147         if ((i % 2) == 1) {
13148             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
13149             i++;
13150         } else {
13151             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
13152             i++;
13153             if (commentList[i] != NULL) {
13154                 fprintf(f, "\n");
13155                 continue;
13156             }
13157             if (i >= forwardMostMove) {
13158                 fprintf(f, "\n");
13159                 break;
13160             }
13161             fprintf(f, "%s\n", parseList[i]);
13162             i++;
13163         }
13164     }
13165
13166     if (commentList[i] != NULL) {
13167         fprintf(f, "[%s]\n", commentList[i]);
13168     }
13169
13170     /* This isn't really the old style, but it's close enough */
13171     if (gameInfo.resultDetails != NULL &&
13172         gameInfo.resultDetails[0] != NULLCHAR) {
13173         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13174                 gameInfo.resultDetails);
13175     } else {
13176         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13177     }
13178
13179     fclose(f);
13180     return TRUE;
13181 }
13182
13183 /* Save the current game to open file f and close the file */
13184 int
13185 SaveGame (FILE *f, int dummy, char *dummy2)
13186 {
13187     if (gameMode == EditPosition) EditPositionDone(TRUE);
13188     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13189     if (appData.oldSaveStyle)
13190       return SaveGameOldStyle(f);
13191     else
13192       return SaveGamePGN(f);
13193 }
13194
13195 /* Save the current position to the given file */
13196 int
13197 SavePositionToFile (char *filename)
13198 {
13199     FILE *f;
13200     char buf[MSG_SIZ];
13201
13202     if (strcmp(filename, "-") == 0) {
13203         return SavePosition(stdout, 0, NULL);
13204     } else {
13205         f = fopen(filename, "a");
13206         if (f == NULL) {
13207             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13208             DisplayError(buf, errno);
13209             return FALSE;
13210         } else {
13211             safeStrCpy(buf, lastMsg, MSG_SIZ);
13212             DisplayMessage(_("Waiting for access to save file"), "");
13213             flock(fileno(f), LOCK_EX); // [HGM] lock
13214             DisplayMessage(_("Saving position"), "");
13215             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
13216             SavePosition(f, 0, NULL);
13217             DisplayMessage(buf, "");
13218             return TRUE;
13219         }
13220     }
13221 }
13222
13223 /* Save the current position to the given open file and close the file */
13224 int
13225 SavePosition (FILE *f, int dummy, char *dummy2)
13226 {
13227     time_t tm;
13228     char *fen;
13229
13230     if (gameMode == EditPosition) EditPositionDone(TRUE);
13231     if (appData.oldSaveStyle) {
13232         tm = time((time_t *) NULL);
13233
13234         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
13235         PrintOpponents(f);
13236         fprintf(f, "[--------------\n");
13237         PrintPosition(f, currentMove);
13238         fprintf(f, "--------------]\n");
13239     } else {
13240         fen = PositionToFEN(currentMove, NULL, 1);
13241         fprintf(f, "%s\n", fen);
13242         free(fen);
13243     }
13244     fclose(f);
13245     return TRUE;
13246 }
13247
13248 void
13249 ReloadCmailMsgEvent (int unregister)
13250 {
13251 #if !WIN32
13252     static char *inFilename = NULL;
13253     static char *outFilename;
13254     int i;
13255     struct stat inbuf, outbuf;
13256     int status;
13257
13258     /* Any registered moves are unregistered if unregister is set, */
13259     /* i.e. invoked by the signal handler */
13260     if (unregister) {
13261         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13262             cmailMoveRegistered[i] = FALSE;
13263             if (cmailCommentList[i] != NULL) {
13264                 free(cmailCommentList[i]);
13265                 cmailCommentList[i] = NULL;
13266             }
13267         }
13268         nCmailMovesRegistered = 0;
13269     }
13270
13271     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13272         cmailResult[i] = CMAIL_NOT_RESULT;
13273     }
13274     nCmailResults = 0;
13275
13276     if (inFilename == NULL) {
13277         /* Because the filenames are static they only get malloced once  */
13278         /* and they never get freed                                      */
13279         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
13280         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
13281
13282         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
13283         sprintf(outFilename, "%s.out", appData.cmailGameName);
13284     }
13285
13286     status = stat(outFilename, &outbuf);
13287     if (status < 0) {
13288         cmailMailedMove = FALSE;
13289     } else {
13290         status = stat(inFilename, &inbuf);
13291         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
13292     }
13293
13294     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
13295        counts the games, notes how each one terminated, etc.
13296
13297        It would be nice to remove this kludge and instead gather all
13298        the information while building the game list.  (And to keep it
13299        in the game list nodes instead of having a bunch of fixed-size
13300        parallel arrays.)  Note this will require getting each game's
13301        termination from the PGN tags, as the game list builder does
13302        not process the game moves.  --mann
13303        */
13304     cmailMsgLoaded = TRUE;
13305     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
13306
13307     /* Load first game in the file or popup game menu */
13308     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
13309
13310 #endif /* !WIN32 */
13311     return;
13312 }
13313
13314 int
13315 RegisterMove ()
13316 {
13317     FILE *f;
13318     char string[MSG_SIZ];
13319
13320     if (   cmailMailedMove
13321         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
13322         return TRUE;            /* Allow free viewing  */
13323     }
13324
13325     /* Unregister move to ensure that we don't leave RegisterMove        */
13326     /* with the move registered when the conditions for registering no   */
13327     /* longer hold                                                       */
13328     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
13329         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
13330         nCmailMovesRegistered --;
13331
13332         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
13333           {
13334               free(cmailCommentList[lastLoadGameNumber - 1]);
13335               cmailCommentList[lastLoadGameNumber - 1] = NULL;
13336           }
13337     }
13338
13339     if (cmailOldMove == -1) {
13340         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
13341         return FALSE;
13342     }
13343
13344     if (currentMove > cmailOldMove + 1) {
13345         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
13346         return FALSE;
13347     }
13348
13349     if (currentMove < cmailOldMove) {
13350         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
13351         return FALSE;
13352     }
13353
13354     if (forwardMostMove > currentMove) {
13355         /* Silently truncate extra moves */
13356         TruncateGame();
13357     }
13358
13359     if (   (currentMove == cmailOldMove + 1)
13360         || (   (currentMove == cmailOldMove)
13361             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
13362                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
13363         if (gameInfo.result != GameUnfinished) {
13364             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
13365         }
13366
13367         if (commentList[currentMove] != NULL) {
13368             cmailCommentList[lastLoadGameNumber - 1]
13369               = StrSave(commentList[currentMove]);
13370         }
13371         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
13372
13373         if (appData.debugMode)
13374           fprintf(debugFP, "Saving %s for game %d\n",
13375                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
13376
13377         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
13378
13379         f = fopen(string, "w");
13380         if (appData.oldSaveStyle) {
13381             SaveGameOldStyle(f); /* also closes the file */
13382
13383             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
13384             f = fopen(string, "w");
13385             SavePosition(f, 0, NULL); /* also closes the file */
13386         } else {
13387             fprintf(f, "{--------------\n");
13388             PrintPosition(f, currentMove);
13389             fprintf(f, "--------------}\n\n");
13390
13391             SaveGame(f, 0, NULL); /* also closes the file*/
13392         }
13393
13394         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
13395         nCmailMovesRegistered ++;
13396     } else if (nCmailGames == 1) {
13397         DisplayError(_("You have not made a move yet"), 0);
13398         return FALSE;
13399     }
13400
13401     return TRUE;
13402 }
13403
13404 void
13405 MailMoveEvent ()
13406 {
13407 #if !WIN32
13408     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
13409     FILE *commandOutput;
13410     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
13411     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
13412     int nBuffers;
13413     int i;
13414     int archived;
13415     char *arcDir;
13416
13417     if (! cmailMsgLoaded) {
13418         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
13419         return;
13420     }
13421
13422     if (nCmailGames == nCmailResults) {
13423         DisplayError(_("No unfinished games"), 0);
13424         return;
13425     }
13426
13427 #if CMAIL_PROHIBIT_REMAIL
13428     if (cmailMailedMove) {
13429       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);
13430         DisplayError(msg, 0);
13431         return;
13432     }
13433 #endif
13434
13435     if (! (cmailMailedMove || RegisterMove())) return;
13436
13437     if (   cmailMailedMove
13438         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13439       snprintf(string, MSG_SIZ, partCommandString,
13440                appData.debugMode ? " -v" : "", appData.cmailGameName);
13441         commandOutput = popen(string, "r");
13442
13443         if (commandOutput == NULL) {
13444             DisplayError(_("Failed to invoke cmail"), 0);
13445         } else {
13446             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13447                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13448             }
13449             if (nBuffers > 1) {
13450                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13451                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13452                 nBytes = MSG_SIZ - 1;
13453             } else {
13454                 (void) memcpy(msg, buffer, nBytes);
13455             }
13456             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13457
13458             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13459                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
13460
13461                 archived = TRUE;
13462                 for (i = 0; i < nCmailGames; i ++) {
13463                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
13464                         archived = FALSE;
13465                     }
13466                 }
13467                 if (   archived
13468                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13469                         != NULL)) {
13470                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13471                            arcDir,
13472                            appData.cmailGameName,
13473                            gameInfo.date);
13474                     LoadGameFromFile(buffer, 1, buffer, FALSE);
13475                     cmailMsgLoaded = FALSE;
13476                 }
13477             }
13478
13479             DisplayInformation(msg);
13480             pclose(commandOutput);
13481         }
13482     } else {
13483         if ((*cmailMsg) != '\0') {
13484             DisplayInformation(cmailMsg);
13485         }
13486     }
13487
13488     return;
13489 #endif /* !WIN32 */
13490 }
13491
13492 char *
13493 CmailMsg ()
13494 {
13495 #if WIN32
13496     return NULL;
13497 #else
13498     int  prependComma = 0;
13499     char number[5];
13500     char string[MSG_SIZ];       /* Space for game-list */
13501     int  i;
13502
13503     if (!cmailMsgLoaded) return "";
13504
13505     if (cmailMailedMove) {
13506       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13507     } else {
13508         /* Create a list of games left */
13509       snprintf(string, MSG_SIZ, "[");
13510         for (i = 0; i < nCmailGames; i ++) {
13511             if (! (   cmailMoveRegistered[i]
13512                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13513                 if (prependComma) {
13514                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13515                 } else {
13516                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13517                     prependComma = 1;
13518                 }
13519
13520                 strcat(string, number);
13521             }
13522         }
13523         strcat(string, "]");
13524
13525         if (nCmailMovesRegistered + nCmailResults == 0) {
13526             switch (nCmailGames) {
13527               case 1:
13528                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13529                 break;
13530
13531               case 2:
13532                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13533                 break;
13534
13535               default:
13536                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13537                          nCmailGames);
13538                 break;
13539             }
13540         } else {
13541             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13542               case 1:
13543                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13544                          string);
13545                 break;
13546
13547               case 0:
13548                 if (nCmailResults == nCmailGames) {
13549                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13550                 } else {
13551                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13552                 }
13553                 break;
13554
13555               default:
13556                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13557                          string);
13558             }
13559         }
13560     }
13561     return cmailMsg;
13562 #endif /* WIN32 */
13563 }
13564
13565 void
13566 ResetGameEvent ()
13567 {
13568     if (gameMode == Training)
13569       SetTrainingModeOff();
13570
13571     Reset(TRUE, TRUE);
13572     cmailMsgLoaded = FALSE;
13573     if (appData.icsActive) {
13574       SendToICS(ics_prefix);
13575       SendToICS("refresh\n");
13576     }
13577 }
13578
13579 void
13580 ExitEvent (int status)
13581 {
13582     exiting++;
13583     if (exiting > 2) {
13584       /* Give up on clean exit */
13585       exit(status);
13586     }
13587     if (exiting > 1) {
13588       /* Keep trying for clean exit */
13589       return;
13590     }
13591
13592     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
13593
13594     if (telnetISR != NULL) {
13595       RemoveInputSource(telnetISR);
13596     }
13597     if (icsPR != NoProc) {
13598       DestroyChildProcess(icsPR, TRUE);
13599     }
13600
13601     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
13602     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
13603
13604     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
13605     /* make sure this other one finishes before killing it!                  */
13606     if(endingGame) { int count = 0;
13607         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
13608         while(endingGame && count++ < 10) DoSleep(1);
13609         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
13610     }
13611
13612     /* Kill off chess programs */
13613     if (first.pr != NoProc) {
13614         ExitAnalyzeMode();
13615
13616         DoSleep( appData.delayBeforeQuit );
13617         SendToProgram("quit\n", &first);
13618         DoSleep( appData.delayAfterQuit );
13619         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
13620     }
13621     if (second.pr != NoProc) {
13622         DoSleep( appData.delayBeforeQuit );
13623         SendToProgram("quit\n", &second);
13624         DoSleep( appData.delayAfterQuit );
13625         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
13626     }
13627     if (first.isr != NULL) {
13628         RemoveInputSource(first.isr);
13629     }
13630     if (second.isr != NULL) {
13631         RemoveInputSource(second.isr);
13632     }
13633
13634     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13635     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13636
13637     ShutDownFrontEnd();
13638     exit(status);
13639 }
13640
13641 void
13642 PauseEngine (ChessProgramState *cps)
13643 {
13644     SendToProgram("pause\n", cps);
13645     cps->pause = 2;
13646 }
13647
13648 void
13649 UnPauseEngine (ChessProgramState *cps)
13650 {
13651     SendToProgram("resume\n", cps);
13652     cps->pause = 1;
13653 }
13654
13655 void
13656 PauseEvent ()
13657 {
13658     if (appData.debugMode)
13659         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13660     if (pausing) {
13661         pausing = FALSE;
13662         ModeHighlight();
13663         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
13664             StartClocks();
13665             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
13666                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
13667                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
13668             }
13669             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
13670             HandleMachineMove(stashedInputMove, stalledEngine);
13671             stalledEngine = NULL;
13672             return;
13673         }
13674         if (gameMode == MachinePlaysWhite ||
13675             gameMode == TwoMachinesPlay   ||
13676             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
13677             if(first.pause)  UnPauseEngine(&first);
13678             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
13679             if(second.pause) UnPauseEngine(&second);
13680             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
13681             StartClocks();
13682         } else {
13683             DisplayBothClocks();
13684         }
13685         if (gameMode == PlayFromGameFile) {
13686             if (appData.timeDelay >= 0)
13687                 AutoPlayGameLoop();
13688         } else if (gameMode == IcsExamining && pauseExamInvalid) {
13689             Reset(FALSE, TRUE);
13690             SendToICS(ics_prefix);
13691             SendToICS("refresh\n");
13692         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
13693             ForwardInner(forwardMostMove);
13694         }
13695         pauseExamInvalid = FALSE;
13696     } else {
13697         switch (gameMode) {
13698           default:
13699             return;
13700           case IcsExamining:
13701             pauseExamForwardMostMove = forwardMostMove;
13702             pauseExamInvalid = FALSE;
13703             /* fall through */
13704           case IcsObserving:
13705           case IcsPlayingWhite:
13706           case IcsPlayingBlack:
13707             pausing = TRUE;
13708             ModeHighlight();
13709             return;
13710           case PlayFromGameFile:
13711             (void) StopLoadGameTimer();
13712             pausing = TRUE;
13713             ModeHighlight();
13714             break;
13715           case BeginningOfGame:
13716             if (appData.icsActive) return;
13717             /* else fall through */
13718           case MachinePlaysWhite:
13719           case MachinePlaysBlack:
13720           case TwoMachinesPlay:
13721             if (forwardMostMove == 0)
13722               return;           /* don't pause if no one has moved */
13723             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
13724                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
13725                 if(onMove->pause) {           // thinking engine can be paused
13726                     PauseEngine(onMove);      // do it
13727                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
13728                         PauseEngine(onMove->other);
13729                     else
13730                         SendToProgram("easy\n", onMove->other);
13731                     StopClocks();
13732                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
13733             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
13734                 if(first.pause) {
13735                     PauseEngine(&first);
13736                     StopClocks();
13737                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
13738             } else { // human on move, pause pondering by either method
13739                 if(first.pause)
13740                     PauseEngine(&first);
13741                 else if(appData.ponderNextMove)
13742                     SendToProgram("easy\n", &first);
13743                 StopClocks();
13744             }
13745             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
13746           case AnalyzeMode:
13747             pausing = TRUE;
13748             ModeHighlight();
13749             break;
13750         }
13751     }
13752 }
13753
13754 void
13755 EditCommentEvent ()
13756 {
13757     char title[MSG_SIZ];
13758
13759     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13760       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13761     } else {
13762       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13763                WhiteOnMove(currentMove - 1) ? " " : ".. ",
13764                parseList[currentMove - 1]);
13765     }
13766
13767     EditCommentPopUp(currentMove, title, commentList[currentMove]);
13768 }
13769
13770
13771 void
13772 EditTagsEvent ()
13773 {
13774     char *tags = PGNTags(&gameInfo);
13775     bookUp = FALSE;
13776     EditTagsPopUp(tags, NULL);
13777     free(tags);
13778 }
13779
13780 void
13781 ToggleSecond ()
13782 {
13783   if(second.analyzing) {
13784     SendToProgram("exit\n", &second);
13785     second.analyzing = FALSE;
13786   } else {
13787     if (second.pr == NoProc) StartChessProgram(&second);
13788     InitChessProgram(&second, FALSE);
13789     FeedMovesToProgram(&second, currentMove);
13790
13791     SendToProgram("analyze\n", &second);
13792     second.analyzing = TRUE;
13793   }
13794 }
13795
13796 /* Toggle ShowThinking */
13797 void
13798 ToggleShowThinking()
13799 {
13800   appData.showThinking = !appData.showThinking;
13801   ShowThinkingEvent();
13802 }
13803
13804 int
13805 AnalyzeModeEvent ()
13806 {
13807     char buf[MSG_SIZ];
13808
13809     if (!first.analysisSupport) {
13810       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
13811       DisplayError(buf, 0);
13812       return 0;
13813     }
13814     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
13815     if (appData.icsActive) {
13816         if (gameMode != IcsObserving) {
13817           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
13818             DisplayError(buf, 0);
13819             /* secure check */
13820             if (appData.icsEngineAnalyze) {
13821                 if (appData.debugMode)
13822                     fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
13823                 ExitAnalyzeMode();
13824                 ModeHighlight();
13825             }
13826             return 0;
13827         }
13828         /* if enable, user wants to disable icsEngineAnalyze */
13829         if (appData.icsEngineAnalyze) {
13830                 ExitAnalyzeMode();
13831                 ModeHighlight();
13832                 return 0;
13833         }
13834         appData.icsEngineAnalyze = TRUE;
13835         if (appData.debugMode)
13836             fprintf(debugFP, "ICS engine analyze starting... \n");
13837     }
13838
13839     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
13840     if (appData.noChessProgram || gameMode == AnalyzeMode)
13841       return 0;
13842
13843     if (gameMode != AnalyzeFile) {
13844         if (!appData.icsEngineAnalyze) {
13845                EditGameEvent();
13846                if (gameMode != EditGame) return 0;
13847         }
13848         if (!appData.showThinking) ToggleShowThinking();
13849         ResurrectChessProgram();
13850         SendToProgram("analyze\n", &first);
13851         first.analyzing = TRUE;
13852         /*first.maybeThinking = TRUE;*/
13853         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13854         EngineOutputPopUp();
13855     }
13856     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
13857     pausing = FALSE;
13858     ModeHighlight();
13859     SetGameInfo();
13860
13861     StartAnalysisClock();
13862     GetTimeMark(&lastNodeCountTime);
13863     lastNodeCount = 0;
13864     return 1;
13865 }
13866
13867 void
13868 AnalyzeFileEvent ()
13869 {
13870     if (appData.noChessProgram || gameMode == AnalyzeFile)
13871       return;
13872
13873     if (!first.analysisSupport) {
13874       char buf[MSG_SIZ];
13875       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
13876       DisplayError(buf, 0);
13877       return;
13878     }
13879
13880     if (gameMode != AnalyzeMode) {
13881         keepInfo = 1; // mere annotating should not alter PGN tags
13882         EditGameEvent();
13883         keepInfo = 0;
13884         if (gameMode != EditGame) return;
13885         if (!appData.showThinking) ToggleShowThinking();
13886         ResurrectChessProgram();
13887         SendToProgram("analyze\n", &first);
13888         first.analyzing = TRUE;
13889         /*first.maybeThinking = TRUE;*/
13890         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13891         EngineOutputPopUp();
13892     }
13893     gameMode = AnalyzeFile;
13894     pausing = FALSE;
13895     ModeHighlight();
13896
13897     StartAnalysisClock();
13898     GetTimeMark(&lastNodeCountTime);
13899     lastNodeCount = 0;
13900     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
13901     AnalysisPeriodicEvent(1);
13902 }
13903
13904 void
13905 MachineWhiteEvent ()
13906 {
13907     char buf[MSG_SIZ];
13908     char *bookHit = NULL;
13909
13910     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
13911       return;
13912
13913
13914     if (gameMode == PlayFromGameFile ||
13915         gameMode == TwoMachinesPlay  ||
13916         gameMode == Training         ||
13917         gameMode == AnalyzeMode      ||
13918         gameMode == EndOfGame)
13919         EditGameEvent();
13920
13921     if (gameMode == EditPosition)
13922         EditPositionDone(TRUE);
13923
13924     if (!WhiteOnMove(currentMove)) {
13925         DisplayError(_("It is not White's turn"), 0);
13926         return;
13927     }
13928
13929     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13930       ExitAnalyzeMode();
13931
13932     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13933         gameMode == AnalyzeFile)
13934         TruncateGame();
13935
13936     ResurrectChessProgram();    /* in case it isn't running */
13937     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
13938         gameMode = MachinePlaysWhite;
13939         ResetClocks();
13940     } else
13941     gameMode = MachinePlaysWhite;
13942     pausing = FALSE;
13943     ModeHighlight();
13944     SetGameInfo();
13945     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13946     DisplayTitle(buf);
13947     if (first.sendName) {
13948       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
13949       SendToProgram(buf, &first);
13950     }
13951     if (first.sendTime) {
13952       if (first.useColors) {
13953         SendToProgram("black\n", &first); /*gnu kludge*/
13954       }
13955       SendTimeRemaining(&first, TRUE);
13956     }
13957     if (first.useColors) {
13958       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
13959     }
13960     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13961     SetMachineThinkingEnables();
13962     first.maybeThinking = TRUE;
13963     StartClocks();
13964     firstMove = FALSE;
13965
13966     if (appData.autoFlipView && !flipView) {
13967       flipView = !flipView;
13968       DrawPosition(FALSE, NULL);
13969       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13970     }
13971
13972     if(bookHit) { // [HGM] book: simulate book reply
13973         static char bookMove[MSG_SIZ]; // a bit generous?
13974
13975         programStats.nodes = programStats.depth = programStats.time =
13976         programStats.score = programStats.got_only_move = 0;
13977         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13978
13979         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13980         strcat(bookMove, bookHit);
13981         HandleMachineMove(bookMove, &first);
13982     }
13983 }
13984
13985 void
13986 MachineBlackEvent ()
13987 {
13988   char buf[MSG_SIZ];
13989   char *bookHit = NULL;
13990
13991     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
13992         return;
13993
13994
13995     if (gameMode == PlayFromGameFile ||
13996         gameMode == TwoMachinesPlay  ||
13997         gameMode == Training         ||
13998         gameMode == AnalyzeMode      ||
13999         gameMode == EndOfGame)
14000         EditGameEvent();
14001
14002     if (gameMode == EditPosition)
14003         EditPositionDone(TRUE);
14004
14005     if (WhiteOnMove(currentMove)) {
14006         DisplayError(_("It is not Black's turn"), 0);
14007         return;
14008     }
14009
14010     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14011       ExitAnalyzeMode();
14012
14013     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14014         gameMode == AnalyzeFile)
14015         TruncateGame();
14016
14017     ResurrectChessProgram();    /* in case it isn't running */
14018     gameMode = MachinePlaysBlack;
14019     pausing = FALSE;
14020     ModeHighlight();
14021     SetGameInfo();
14022     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14023     DisplayTitle(buf);
14024     if (first.sendName) {
14025       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14026       SendToProgram(buf, &first);
14027     }
14028     if (first.sendTime) {
14029       if (first.useColors) {
14030         SendToProgram("white\n", &first); /*gnu kludge*/
14031       }
14032       SendTimeRemaining(&first, FALSE);
14033     }
14034     if (first.useColors) {
14035       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14036     }
14037     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14038     SetMachineThinkingEnables();
14039     first.maybeThinking = TRUE;
14040     StartClocks();
14041
14042     if (appData.autoFlipView && flipView) {
14043       flipView = !flipView;
14044       DrawPosition(FALSE, NULL);
14045       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14046     }
14047     if(bookHit) { // [HGM] book: simulate book reply
14048         static char bookMove[MSG_SIZ]; // a bit generous?
14049
14050         programStats.nodes = programStats.depth = programStats.time =
14051         programStats.score = programStats.got_only_move = 0;
14052         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14053
14054         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14055         strcat(bookMove, bookHit);
14056         HandleMachineMove(bookMove, &first);
14057     }
14058 }
14059
14060
14061 void
14062 DisplayTwoMachinesTitle ()
14063 {
14064     char buf[MSG_SIZ];
14065     if (appData.matchGames > 0) {
14066         if(appData.tourneyFile[0]) {
14067           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14068                    gameInfo.white, _("vs."), gameInfo.black,
14069                    nextGame+1, appData.matchGames+1,
14070                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14071         } else
14072         if (first.twoMachinesColor[0] == 'w') {
14073           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14074                    gameInfo.white, _("vs."),  gameInfo.black,
14075                    first.matchWins, second.matchWins,
14076                    matchGame - 1 - (first.matchWins + second.matchWins));
14077         } else {
14078           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14079                    gameInfo.white, _("vs."), gameInfo.black,
14080                    second.matchWins, first.matchWins,
14081                    matchGame - 1 - (first.matchWins + second.matchWins));
14082         }
14083     } else {
14084       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14085     }
14086     DisplayTitle(buf);
14087 }
14088
14089 void
14090 SettingsMenuIfReady ()
14091 {
14092   if (second.lastPing != second.lastPong) {
14093     DisplayMessage("", _("Waiting for second chess program"));
14094     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14095     return;
14096   }
14097   ThawUI();
14098   DisplayMessage("", "");
14099   SettingsPopUp(&second);
14100 }
14101
14102 int
14103 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
14104 {
14105     char buf[MSG_SIZ];
14106     if (cps->pr == NoProc) {
14107         StartChessProgram(cps);
14108         if (cps->protocolVersion == 1) {
14109           retry();
14110           ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
14111         } else {
14112           /* kludge: allow timeout for initial "feature" command */
14113           if(retry != TwoMachinesEventIfReady) FreezeUI();
14114           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
14115           DisplayMessage("", buf);
14116           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
14117         }
14118         return 1;
14119     }
14120     return 0;
14121 }
14122
14123 void
14124 TwoMachinesEvent P((void))
14125 {
14126     int i;
14127     char buf[MSG_SIZ];
14128     ChessProgramState *onmove;
14129     char *bookHit = NULL;
14130     static int stalling = 0;
14131     TimeMark now;
14132     long wait;
14133
14134     if (appData.noChessProgram) return;
14135
14136     switch (gameMode) {
14137       case TwoMachinesPlay:
14138         return;
14139       case MachinePlaysWhite:
14140       case MachinePlaysBlack:
14141         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14142             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
14143             return;
14144         }
14145         /* fall through */
14146       case BeginningOfGame:
14147       case PlayFromGameFile:
14148       case EndOfGame:
14149         EditGameEvent();
14150         if (gameMode != EditGame) return;
14151         break;
14152       case EditPosition:
14153         EditPositionDone(TRUE);
14154         break;
14155       case AnalyzeMode:
14156       case AnalyzeFile:
14157         ExitAnalyzeMode();
14158         break;
14159       case EditGame:
14160       default:
14161         break;
14162     }
14163
14164 //    forwardMostMove = currentMove;
14165     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
14166     startingEngine = TRUE;
14167
14168     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
14169
14170     if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14171     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14172       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14173       return;
14174     }
14175     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14176
14177     if(second.protocolVersion >= 2 && !strstr(second.variants, VariantName(gameInfo.variant))) {
14178         startingEngine = FALSE;
14179         DisplayError("second engine does not play this", 0);
14180         return;
14181     }
14182
14183     if(!stalling) {
14184       InitChessProgram(&second, FALSE); // unbalances ping of second engine
14185       SendToProgram("force\n", &second);
14186       stalling = 1;
14187       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14188       return;
14189     }
14190     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14191     if(appData.matchPause>10000 || appData.matchPause<10)
14192                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14193     wait = SubtractTimeMarks(&now, &pauseStart);
14194     if(wait < appData.matchPause) {
14195         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14196         return;
14197     }
14198     // we are now committed to starting the game
14199     stalling = 0;
14200     DisplayMessage("", "");
14201     if (startedFromSetupPosition) {
14202         SendBoard(&second, backwardMostMove);
14203     if (appData.debugMode) {
14204         fprintf(debugFP, "Two Machines\n");
14205     }
14206     }
14207     for (i = backwardMostMove; i < forwardMostMove; i++) {
14208         SendMoveToProgram(i, &second);
14209     }
14210
14211     gameMode = TwoMachinesPlay;
14212     pausing = startingEngine = FALSE;
14213     ModeHighlight(); // [HGM] logo: this triggers display update of logos
14214     SetGameInfo();
14215     DisplayTwoMachinesTitle();
14216     firstMove = TRUE;
14217     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14218         onmove = &first;
14219     } else {
14220         onmove = &second;
14221     }
14222     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14223     SendToProgram(first.computerString, &first);
14224     if (first.sendName) {
14225       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14226       SendToProgram(buf, &first);
14227     }
14228     SendToProgram(second.computerString, &second);
14229     if (second.sendName) {
14230       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
14231       SendToProgram(buf, &second);
14232     }
14233
14234     ResetClocks();
14235     if (!first.sendTime || !second.sendTime) {
14236         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14237         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14238     }
14239     if (onmove->sendTime) {
14240       if (onmove->useColors) {
14241         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
14242       }
14243       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
14244     }
14245     if (onmove->useColors) {
14246       SendToProgram(onmove->twoMachinesColor, onmove);
14247     }
14248     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
14249 //    SendToProgram("go\n", onmove);
14250     onmove->maybeThinking = TRUE;
14251     SetMachineThinkingEnables();
14252
14253     StartClocks();
14254
14255     if(bookHit) { // [HGM] book: simulate book reply
14256         static char bookMove[MSG_SIZ]; // a bit generous?
14257
14258         programStats.nodes = programStats.depth = programStats.time =
14259         programStats.score = programStats.got_only_move = 0;
14260         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14261
14262         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14263         strcat(bookMove, bookHit);
14264         savedMessage = bookMove; // args for deferred call
14265         savedState = onmove;
14266         ScheduleDelayedEvent(DeferredBookMove, 1);
14267     }
14268 }
14269
14270 void
14271 TrainingEvent ()
14272 {
14273     if (gameMode == Training) {
14274       SetTrainingModeOff();
14275       gameMode = PlayFromGameFile;
14276       DisplayMessage("", _("Training mode off"));
14277     } else {
14278       gameMode = Training;
14279       animateTraining = appData.animate;
14280
14281       /* make sure we are not already at the end of the game */
14282       if (currentMove < forwardMostMove) {
14283         SetTrainingModeOn();
14284         DisplayMessage("", _("Training mode on"));
14285       } else {
14286         gameMode = PlayFromGameFile;
14287         DisplayError(_("Already at end of game"), 0);
14288       }
14289     }
14290     ModeHighlight();
14291 }
14292
14293 void
14294 IcsClientEvent ()
14295 {
14296     if (!appData.icsActive) return;
14297     switch (gameMode) {
14298       case IcsPlayingWhite:
14299       case IcsPlayingBlack:
14300       case IcsObserving:
14301       case IcsIdle:
14302       case BeginningOfGame:
14303       case IcsExamining:
14304         return;
14305
14306       case EditGame:
14307         break;
14308
14309       case EditPosition:
14310         EditPositionDone(TRUE);
14311         break;
14312
14313       case AnalyzeMode:
14314       case AnalyzeFile:
14315         ExitAnalyzeMode();
14316         break;
14317
14318       default:
14319         EditGameEvent();
14320         break;
14321     }
14322
14323     gameMode = IcsIdle;
14324     ModeHighlight();
14325     return;
14326 }
14327
14328 void
14329 EditGameEvent ()
14330 {
14331     int i;
14332
14333     switch (gameMode) {
14334       case Training:
14335         SetTrainingModeOff();
14336         break;
14337       case MachinePlaysWhite:
14338       case MachinePlaysBlack:
14339       case BeginningOfGame:
14340         SendToProgram("force\n", &first);
14341         SetUserThinkingEnables();
14342         break;
14343       case PlayFromGameFile:
14344         (void) StopLoadGameTimer();
14345         if (gameFileFP != NULL) {
14346             gameFileFP = NULL;
14347         }
14348         break;
14349       case EditPosition:
14350         EditPositionDone(TRUE);
14351         break;
14352       case AnalyzeMode:
14353       case AnalyzeFile:
14354         ExitAnalyzeMode();
14355         SendToProgram("force\n", &first);
14356         break;
14357       case TwoMachinesPlay:
14358         GameEnds(EndOfFile, NULL, GE_PLAYER);
14359         ResurrectChessProgram();
14360         SetUserThinkingEnables();
14361         break;
14362       case EndOfGame:
14363         ResurrectChessProgram();
14364         break;
14365       case IcsPlayingBlack:
14366       case IcsPlayingWhite:
14367         DisplayError(_("Warning: You are still playing a game"), 0);
14368         break;
14369       case IcsObserving:
14370         DisplayError(_("Warning: You are still observing a game"), 0);
14371         break;
14372       case IcsExamining:
14373         DisplayError(_("Warning: You are still examining a game"), 0);
14374         break;
14375       case IcsIdle:
14376         break;
14377       case EditGame:
14378       default:
14379         return;
14380     }
14381
14382     pausing = FALSE;
14383     StopClocks();
14384     first.offeredDraw = second.offeredDraw = 0;
14385
14386     if (gameMode == PlayFromGameFile) {
14387         whiteTimeRemaining = timeRemaining[0][currentMove];
14388         blackTimeRemaining = timeRemaining[1][currentMove];
14389         DisplayTitle("");
14390     }
14391
14392     if (gameMode == MachinePlaysWhite ||
14393         gameMode == MachinePlaysBlack ||
14394         gameMode == TwoMachinesPlay ||
14395         gameMode == EndOfGame) {
14396         i = forwardMostMove;
14397         while (i > currentMove) {
14398             SendToProgram("undo\n", &first);
14399             i--;
14400         }
14401         if(!adjustedClock) {
14402         whiteTimeRemaining = timeRemaining[0][currentMove];
14403         blackTimeRemaining = timeRemaining[1][currentMove];
14404         DisplayBothClocks();
14405         }
14406         if (whiteFlag || blackFlag) {
14407             whiteFlag = blackFlag = 0;
14408         }
14409         DisplayTitle("");
14410     }
14411
14412     gameMode = EditGame;
14413     ModeHighlight();
14414     SetGameInfo();
14415 }
14416
14417
14418 void
14419 EditPositionEvent ()
14420 {
14421     if (gameMode == EditPosition) {
14422         EditGameEvent();
14423         return;
14424     }
14425
14426     EditGameEvent();
14427     if (gameMode != EditGame) return;
14428
14429     gameMode = EditPosition;
14430     ModeHighlight();
14431     SetGameInfo();
14432     if (currentMove > 0)
14433       CopyBoard(boards[0], boards[currentMove]);
14434
14435     blackPlaysFirst = !WhiteOnMove(currentMove);
14436     ResetClocks();
14437     currentMove = forwardMostMove = backwardMostMove = 0;
14438     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14439     DisplayMove(-1);
14440     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
14441 }
14442
14443 void
14444 ExitAnalyzeMode ()
14445 {
14446     /* [DM] icsEngineAnalyze - possible call from other functions */
14447     if (appData.icsEngineAnalyze) {
14448         appData.icsEngineAnalyze = FALSE;
14449
14450         DisplayMessage("",_("Close ICS engine analyze..."));
14451     }
14452     if (first.analysisSupport && first.analyzing) {
14453       SendToBoth("exit\n");
14454       first.analyzing = second.analyzing = FALSE;
14455     }
14456     thinkOutput[0] = NULLCHAR;
14457 }
14458
14459 void
14460 EditPositionDone (Boolean fakeRights)
14461 {
14462     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
14463
14464     startedFromSetupPosition = TRUE;
14465     InitChessProgram(&first, FALSE);
14466     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
14467       boards[0][EP_STATUS] = EP_NONE;
14468       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
14469       if(boards[0][0][BOARD_WIDTH>>1] == king) {
14470         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
14471         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
14472       } else boards[0][CASTLING][2] = NoRights;
14473       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
14474         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
14475         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
14476       } else boards[0][CASTLING][5] = NoRights;
14477       if(gameInfo.variant == VariantSChess) {
14478         int i;
14479         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
14480           boards[0][VIRGIN][i] = 0;
14481           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
14482           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
14483         }
14484       }
14485     }
14486     SendToProgram("force\n", &first);
14487     if (blackPlaysFirst) {
14488         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
14489         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
14490         currentMove = forwardMostMove = backwardMostMove = 1;
14491         CopyBoard(boards[1], boards[0]);
14492     } else {
14493         currentMove = forwardMostMove = backwardMostMove = 0;
14494     }
14495     SendBoard(&first, forwardMostMove);
14496     if (appData.debugMode) {
14497         fprintf(debugFP, "EditPosDone\n");
14498     }
14499     DisplayTitle("");
14500     DisplayMessage("", "");
14501     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14502     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14503     gameMode = EditGame;
14504     ModeHighlight();
14505     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14506     ClearHighlights(); /* [AS] */
14507 }
14508
14509 /* Pause for `ms' milliseconds */
14510 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14511 void
14512 TimeDelay (long ms)
14513 {
14514     TimeMark m1, m2;
14515
14516     GetTimeMark(&m1);
14517     do {
14518         GetTimeMark(&m2);
14519     } while (SubtractTimeMarks(&m2, &m1) < ms);
14520 }
14521
14522 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14523 void
14524 SendMultiLineToICS (char *buf)
14525 {
14526     char temp[MSG_SIZ+1], *p;
14527     int len;
14528
14529     len = strlen(buf);
14530     if (len > MSG_SIZ)
14531       len = MSG_SIZ;
14532
14533     strncpy(temp, buf, len);
14534     temp[len] = 0;
14535
14536     p = temp;
14537     while (*p) {
14538         if (*p == '\n' || *p == '\r')
14539           *p = ' ';
14540         ++p;
14541     }
14542
14543     strcat(temp, "\n");
14544     SendToICS(temp);
14545     SendToPlayer(temp, strlen(temp));
14546 }
14547
14548 void
14549 SetWhiteToPlayEvent ()
14550 {
14551     if (gameMode == EditPosition) {
14552         blackPlaysFirst = FALSE;
14553         DisplayBothClocks();    /* works because currentMove is 0 */
14554     } else if (gameMode == IcsExamining) {
14555         SendToICS(ics_prefix);
14556         SendToICS("tomove white\n");
14557     }
14558 }
14559
14560 void
14561 SetBlackToPlayEvent ()
14562 {
14563     if (gameMode == EditPosition) {
14564         blackPlaysFirst = TRUE;
14565         currentMove = 1;        /* kludge */
14566         DisplayBothClocks();
14567         currentMove = 0;
14568     } else if (gameMode == IcsExamining) {
14569         SendToICS(ics_prefix);
14570         SendToICS("tomove black\n");
14571     }
14572 }
14573
14574 void
14575 EditPositionMenuEvent (ChessSquare selection, int x, int y)
14576 {
14577     char buf[MSG_SIZ];
14578     ChessSquare piece = boards[0][y][x];
14579
14580     if (gameMode != EditPosition && gameMode != IcsExamining) return;
14581
14582     switch (selection) {
14583       case ClearBoard:
14584         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
14585             SendToICS(ics_prefix);
14586             SendToICS("bsetup clear\n");
14587         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
14588             SendToICS(ics_prefix);
14589             SendToICS("clearboard\n");
14590         } else {
14591             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
14592                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
14593                 for (y = 0; y < BOARD_HEIGHT; y++) {
14594                     if (gameMode == IcsExamining) {
14595                         if (boards[currentMove][y][x] != EmptySquare) {
14596                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
14597                                     AAA + x, ONE + y);
14598                             SendToICS(buf);
14599                         }
14600                     } else {
14601                         boards[0][y][x] = p;
14602                     }
14603                 }
14604             }
14605         }
14606         if (gameMode == EditPosition) {
14607             DrawPosition(FALSE, boards[0]);
14608         }
14609         break;
14610
14611       case WhitePlay:
14612         SetWhiteToPlayEvent();
14613         break;
14614
14615       case BlackPlay:
14616         SetBlackToPlayEvent();
14617         break;
14618
14619       case EmptySquare:
14620         if (gameMode == IcsExamining) {
14621             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14622             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
14623             SendToICS(buf);
14624         } else {
14625             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14626                 if(x == BOARD_LEFT-2) {
14627                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
14628                     boards[0][y][1] = 0;
14629                 } else
14630                 if(x == BOARD_RGHT+1) {
14631                     if(y >= gameInfo.holdingsSize) break;
14632                     boards[0][y][BOARD_WIDTH-2] = 0;
14633                 } else break;
14634             }
14635             boards[0][y][x] = EmptySquare;
14636             DrawPosition(FALSE, boards[0]);
14637         }
14638         break;
14639
14640       case PromotePiece:
14641         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
14642            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
14643             selection = (ChessSquare) (PROMOTED piece);
14644         } else if(piece == EmptySquare) selection = WhiteSilver;
14645         else selection = (ChessSquare)((int)piece - 1);
14646         goto defaultlabel;
14647
14648       case DemotePiece:
14649         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
14650            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
14651             selection = (ChessSquare) (DEMOTED piece);
14652         } else if(piece == EmptySquare) selection = BlackSilver;
14653         else selection = (ChessSquare)((int)piece + 1);
14654         goto defaultlabel;
14655
14656       case WhiteQueen:
14657       case BlackQueen:
14658         if(gameInfo.variant == VariantShatranj ||
14659            gameInfo.variant == VariantXiangqi  ||
14660            gameInfo.variant == VariantCourier  ||
14661            gameInfo.variant == VariantASEAN    ||
14662            gameInfo.variant == VariantMakruk     )
14663             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
14664         goto defaultlabel;
14665
14666       case WhiteKing:
14667       case BlackKing:
14668         if(gameInfo.variant == VariantXiangqi)
14669             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
14670         if(gameInfo.variant == VariantKnightmate)
14671             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
14672       default:
14673         defaultlabel:
14674         if (gameMode == IcsExamining) {
14675             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14676             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
14677                      PieceToChar(selection), AAA + x, ONE + y);
14678             SendToICS(buf);
14679         } else {
14680             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14681                 int n;
14682                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
14683                     n = PieceToNumber(selection - BlackPawn);
14684                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
14685                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
14686                     boards[0][BOARD_HEIGHT-1-n][1]++;
14687                 } else
14688                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
14689                     n = PieceToNumber(selection);
14690                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
14691                     boards[0][n][BOARD_WIDTH-1] = selection;
14692                     boards[0][n][BOARD_WIDTH-2]++;
14693                 }
14694             } else
14695             boards[0][y][x] = selection;
14696             DrawPosition(TRUE, boards[0]);
14697             ClearHighlights();
14698             fromX = fromY = -1;
14699         }
14700         break;
14701     }
14702 }
14703
14704
14705 void
14706 DropMenuEvent (ChessSquare selection, int x, int y)
14707 {
14708     ChessMove moveType;
14709
14710     switch (gameMode) {
14711       case IcsPlayingWhite:
14712       case MachinePlaysBlack:
14713         if (!WhiteOnMove(currentMove)) {
14714             DisplayMoveError(_("It is Black's turn"));
14715             return;
14716         }
14717         moveType = WhiteDrop;
14718         break;
14719       case IcsPlayingBlack:
14720       case MachinePlaysWhite:
14721         if (WhiteOnMove(currentMove)) {
14722             DisplayMoveError(_("It is White's turn"));
14723             return;
14724         }
14725         moveType = BlackDrop;
14726         break;
14727       case EditGame:
14728         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
14729         break;
14730       default:
14731         return;
14732     }
14733
14734     if (moveType == BlackDrop && selection < BlackPawn) {
14735       selection = (ChessSquare) ((int) selection
14736                                  + (int) BlackPawn - (int) WhitePawn);
14737     }
14738     if (boards[currentMove][y][x] != EmptySquare) {
14739         DisplayMoveError(_("That square is occupied"));
14740         return;
14741     }
14742
14743     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
14744 }
14745
14746 void
14747 AcceptEvent ()
14748 {
14749     /* Accept a pending offer of any kind from opponent */
14750
14751     if (appData.icsActive) {
14752         SendToICS(ics_prefix);
14753         SendToICS("accept\n");
14754     } else if (cmailMsgLoaded) {
14755         if (currentMove == cmailOldMove &&
14756             commentList[cmailOldMove] != NULL &&
14757             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14758                    "Black offers a draw" : "White offers a draw")) {
14759             TruncateGame();
14760             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14761             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14762         } else {
14763             DisplayError(_("There is no pending offer on this move"), 0);
14764             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14765         }
14766     } else {
14767         /* Not used for offers from chess program */
14768     }
14769 }
14770
14771 void
14772 DeclineEvent ()
14773 {
14774     /* Decline a pending offer of any kind from opponent */
14775
14776     if (appData.icsActive) {
14777         SendToICS(ics_prefix);
14778         SendToICS("decline\n");
14779     } else if (cmailMsgLoaded) {
14780         if (currentMove == cmailOldMove &&
14781             commentList[cmailOldMove] != NULL &&
14782             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14783                    "Black offers a draw" : "White offers a draw")) {
14784 #ifdef NOTDEF
14785             AppendComment(cmailOldMove, "Draw declined", TRUE);
14786             DisplayComment(cmailOldMove - 1, "Draw declined");
14787 #endif /*NOTDEF*/
14788         } else {
14789             DisplayError(_("There is no pending offer on this move"), 0);
14790         }
14791     } else {
14792         /* Not used for offers from chess program */
14793     }
14794 }
14795
14796 void
14797 RematchEvent ()
14798 {
14799     /* Issue ICS rematch command */
14800     if (appData.icsActive) {
14801         SendToICS(ics_prefix);
14802         SendToICS("rematch\n");
14803     }
14804 }
14805
14806 void
14807 CallFlagEvent ()
14808 {
14809     /* Call your opponent's flag (claim a win on time) */
14810     if (appData.icsActive) {
14811         SendToICS(ics_prefix);
14812         SendToICS("flag\n");
14813     } else {
14814         switch (gameMode) {
14815           default:
14816             return;
14817           case MachinePlaysWhite:
14818             if (whiteFlag) {
14819                 if (blackFlag)
14820                   GameEnds(GameIsDrawn, "Both players ran out of time",
14821                            GE_PLAYER);
14822                 else
14823                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
14824             } else {
14825                 DisplayError(_("Your opponent is not out of time"), 0);
14826             }
14827             break;
14828           case MachinePlaysBlack:
14829             if (blackFlag) {
14830                 if (whiteFlag)
14831                   GameEnds(GameIsDrawn, "Both players ran out of time",
14832                            GE_PLAYER);
14833                 else
14834                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
14835             } else {
14836                 DisplayError(_("Your opponent is not out of time"), 0);
14837             }
14838             break;
14839         }
14840     }
14841 }
14842
14843 void
14844 ClockClick (int which)
14845 {       // [HGM] code moved to back-end from winboard.c
14846         if(which) { // black clock
14847           if (gameMode == EditPosition || gameMode == IcsExamining) {
14848             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14849             SetBlackToPlayEvent();
14850           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
14851           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
14852           } else if (shiftKey) {
14853             AdjustClock(which, -1);
14854           } else if (gameMode == IcsPlayingWhite ||
14855                      gameMode == MachinePlaysBlack) {
14856             CallFlagEvent();
14857           }
14858         } else { // white clock
14859           if (gameMode == EditPosition || gameMode == IcsExamining) {
14860             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14861             SetWhiteToPlayEvent();
14862           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
14863           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
14864           } else if (shiftKey) {
14865             AdjustClock(which, -1);
14866           } else if (gameMode == IcsPlayingBlack ||
14867                    gameMode == MachinePlaysWhite) {
14868             CallFlagEvent();
14869           }
14870         }
14871 }
14872
14873 void
14874 DrawEvent ()
14875 {
14876     /* Offer draw or accept pending draw offer from opponent */
14877
14878     if (appData.icsActive) {
14879         /* Note: tournament rules require draw offers to be
14880            made after you make your move but before you punch
14881            your clock.  Currently ICS doesn't let you do that;
14882            instead, you immediately punch your clock after making
14883            a move, but you can offer a draw at any time. */
14884
14885         SendToICS(ics_prefix);
14886         SendToICS("draw\n");
14887         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
14888     } else if (cmailMsgLoaded) {
14889         if (currentMove == cmailOldMove &&
14890             commentList[cmailOldMove] != NULL &&
14891             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14892                    "Black offers a draw" : "White offers a draw")) {
14893             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14894             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14895         } else if (currentMove == cmailOldMove + 1) {
14896             char *offer = WhiteOnMove(cmailOldMove) ?
14897               "White offers a draw" : "Black offers a draw";
14898             AppendComment(currentMove, offer, TRUE);
14899             DisplayComment(currentMove - 1, offer);
14900             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
14901         } else {
14902             DisplayError(_("You must make your move before offering a draw"), 0);
14903             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14904         }
14905     } else if (first.offeredDraw) {
14906         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
14907     } else {
14908         if (first.sendDrawOffers) {
14909             SendToProgram("draw\n", &first);
14910             userOfferedDraw = TRUE;
14911         }
14912     }
14913 }
14914
14915 void
14916 AdjournEvent ()
14917 {
14918     /* Offer Adjourn or accept pending Adjourn offer from opponent */
14919
14920     if (appData.icsActive) {
14921         SendToICS(ics_prefix);
14922         SendToICS("adjourn\n");
14923     } else {
14924         /* Currently GNU Chess doesn't offer or accept Adjourns */
14925     }
14926 }
14927
14928
14929 void
14930 AbortEvent ()
14931 {
14932     /* Offer Abort or accept pending Abort offer from opponent */
14933
14934     if (appData.icsActive) {
14935         SendToICS(ics_prefix);
14936         SendToICS("abort\n");
14937     } else {
14938         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
14939     }
14940 }
14941
14942 void
14943 ResignEvent ()
14944 {
14945     /* Resign.  You can do this even if it's not your turn. */
14946
14947     if (appData.icsActive) {
14948         SendToICS(ics_prefix);
14949         SendToICS("resign\n");
14950     } else {
14951         switch (gameMode) {
14952           case MachinePlaysWhite:
14953             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14954             break;
14955           case MachinePlaysBlack:
14956             GameEnds(BlackWins, "White resigns", GE_PLAYER);
14957             break;
14958           case EditGame:
14959             if (cmailMsgLoaded) {
14960                 TruncateGame();
14961                 if (WhiteOnMove(cmailOldMove)) {
14962                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
14963                 } else {
14964                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14965                 }
14966                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
14967             }
14968             break;
14969           default:
14970             break;
14971         }
14972     }
14973 }
14974
14975
14976 void
14977 StopObservingEvent ()
14978 {
14979     /* Stop observing current games */
14980     SendToICS(ics_prefix);
14981     SendToICS("unobserve\n");
14982 }
14983
14984 void
14985 StopExaminingEvent ()
14986 {
14987     /* Stop observing current game */
14988     SendToICS(ics_prefix);
14989     SendToICS("unexamine\n");
14990 }
14991
14992 void
14993 ForwardInner (int target)
14994 {
14995     int limit; int oldSeekGraphUp = seekGraphUp;
14996
14997     if (appData.debugMode)
14998         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
14999                 target, currentMove, forwardMostMove);
15000
15001     if (gameMode == EditPosition)
15002       return;
15003
15004     seekGraphUp = FALSE;
15005     MarkTargetSquares(1);
15006
15007     if (gameMode == PlayFromGameFile && !pausing)
15008       PauseEvent();
15009
15010     if (gameMode == IcsExamining && pausing)
15011       limit = pauseExamForwardMostMove;
15012     else
15013       limit = forwardMostMove;
15014
15015     if (target > limit) target = limit;
15016
15017     if (target > 0 && moveList[target - 1][0]) {
15018         int fromX, fromY, toX, toY;
15019         toX = moveList[target - 1][2] - AAA;
15020         toY = moveList[target - 1][3] - ONE;
15021         if (moveList[target - 1][1] == '@') {
15022             if (appData.highlightLastMove) {
15023                 SetHighlights(-1, -1, toX, toY);
15024             }
15025         } else {
15026             fromX = moveList[target - 1][0] - AAA;
15027             fromY = moveList[target - 1][1] - ONE;
15028             if (target == currentMove + 1) {
15029                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
15030             }
15031             if (appData.highlightLastMove) {
15032                 SetHighlights(fromX, fromY, toX, toY);
15033             }
15034         }
15035     }
15036     if (gameMode == EditGame || gameMode == AnalyzeMode ||
15037         gameMode == Training || gameMode == PlayFromGameFile ||
15038         gameMode == AnalyzeFile) {
15039         while (currentMove < target) {
15040             if(second.analyzing) SendMoveToProgram(currentMove, &second);
15041             SendMoveToProgram(currentMove++, &first);
15042         }
15043     } else {
15044         currentMove = target;
15045     }
15046
15047     if (gameMode == EditGame || gameMode == EndOfGame) {
15048         whiteTimeRemaining = timeRemaining[0][currentMove];
15049         blackTimeRemaining = timeRemaining[1][currentMove];
15050     }
15051     DisplayBothClocks();
15052     DisplayMove(currentMove - 1);
15053     DrawPosition(oldSeekGraphUp, boards[currentMove]);
15054     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15055     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
15056         DisplayComment(currentMove - 1, commentList[currentMove]);
15057     }
15058     ClearMap(); // [HGM] exclude: invalidate map
15059 }
15060
15061
15062 void
15063 ForwardEvent ()
15064 {
15065     if (gameMode == IcsExamining && !pausing) {
15066         SendToICS(ics_prefix);
15067         SendToICS("forward\n");
15068     } else {
15069         ForwardInner(currentMove + 1);
15070     }
15071 }
15072
15073 void
15074 ToEndEvent ()
15075 {
15076     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15077         /* to optimze, we temporarily turn off analysis mode while we feed
15078          * the remaining moves to the engine. Otherwise we get analysis output
15079          * after each move.
15080          */
15081         if (first.analysisSupport) {
15082           SendToProgram("exit\nforce\n", &first);
15083           first.analyzing = FALSE;
15084         }
15085     }
15086
15087     if (gameMode == IcsExamining && !pausing) {
15088         SendToICS(ics_prefix);
15089         SendToICS("forward 999999\n");
15090     } else {
15091         ForwardInner(forwardMostMove);
15092     }
15093
15094     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15095         /* we have fed all the moves, so reactivate analysis mode */
15096         SendToProgram("analyze\n", &first);
15097         first.analyzing = TRUE;
15098         /*first.maybeThinking = TRUE;*/
15099         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15100     }
15101 }
15102
15103 void
15104 BackwardInner (int target)
15105 {
15106     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
15107
15108     if (appData.debugMode)
15109         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
15110                 target, currentMove, forwardMostMove);
15111
15112     if (gameMode == EditPosition) return;
15113     seekGraphUp = FALSE;
15114     MarkTargetSquares(1);
15115     if (currentMove <= backwardMostMove) {
15116         ClearHighlights();
15117         DrawPosition(full_redraw, boards[currentMove]);
15118         return;
15119     }
15120     if (gameMode == PlayFromGameFile && !pausing)
15121       PauseEvent();
15122
15123     if (moveList[target][0]) {
15124         int fromX, fromY, toX, toY;
15125         toX = moveList[target][2] - AAA;
15126         toY = moveList[target][3] - ONE;
15127         if (moveList[target][1] == '@') {
15128             if (appData.highlightLastMove) {
15129                 SetHighlights(-1, -1, toX, toY);
15130             }
15131         } else {
15132             fromX = moveList[target][0] - AAA;
15133             fromY = moveList[target][1] - ONE;
15134             if (target == currentMove - 1) {
15135                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
15136             }
15137             if (appData.highlightLastMove) {
15138                 SetHighlights(fromX, fromY, toX, toY);
15139             }
15140         }
15141     }
15142     if (gameMode == EditGame || gameMode==AnalyzeMode ||
15143         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
15144         while (currentMove > target) {
15145             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
15146                 // null move cannot be undone. Reload program with move history before it.
15147                 int i;
15148                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
15149                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
15150                 }
15151                 SendBoard(&first, i);
15152               if(second.analyzing) SendBoard(&second, i);
15153                 for(currentMove=i; currentMove<target; currentMove++) {
15154                     SendMoveToProgram(currentMove, &first);
15155                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
15156                 }
15157                 break;
15158             }
15159             SendToBoth("undo\n");
15160             currentMove--;
15161         }
15162     } else {
15163         currentMove = target;
15164     }
15165
15166     if (gameMode == EditGame || gameMode == EndOfGame) {
15167         whiteTimeRemaining = timeRemaining[0][currentMove];
15168         blackTimeRemaining = timeRemaining[1][currentMove];
15169     }
15170     DisplayBothClocks();
15171     DisplayMove(currentMove - 1);
15172     DrawPosition(full_redraw, boards[currentMove]);
15173     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15174     // [HGM] PV info: routine tests if comment empty
15175     DisplayComment(currentMove - 1, commentList[currentMove]);
15176     ClearMap(); // [HGM] exclude: invalidate map
15177 }
15178
15179 void
15180 BackwardEvent ()
15181 {
15182     if (gameMode == IcsExamining && !pausing) {
15183         SendToICS(ics_prefix);
15184         SendToICS("backward\n");
15185     } else {
15186         BackwardInner(currentMove - 1);
15187     }
15188 }
15189
15190 void
15191 ToStartEvent ()
15192 {
15193     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15194         /* to optimize, we temporarily turn off analysis mode while we undo
15195          * all the moves. Otherwise we get analysis output after each undo.
15196          */
15197         if (first.analysisSupport) {
15198           SendToProgram("exit\nforce\n", &first);
15199           first.analyzing = FALSE;
15200         }
15201     }
15202
15203     if (gameMode == IcsExamining && !pausing) {
15204         SendToICS(ics_prefix);
15205         SendToICS("backward 999999\n");
15206     } else {
15207         BackwardInner(backwardMostMove);
15208     }
15209
15210     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15211         /* we have fed all the moves, so reactivate analysis mode */
15212         SendToProgram("analyze\n", &first);
15213         first.analyzing = TRUE;
15214         /*first.maybeThinking = TRUE;*/
15215         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15216     }
15217 }
15218
15219 void
15220 ToNrEvent (int to)
15221 {
15222   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
15223   if (to >= forwardMostMove) to = forwardMostMove;
15224   if (to <= backwardMostMove) to = backwardMostMove;
15225   if (to < currentMove) {
15226     BackwardInner(to);
15227   } else {
15228     ForwardInner(to);
15229   }
15230 }
15231
15232 void
15233 RevertEvent (Boolean annotate)
15234 {
15235     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
15236         return;
15237     }
15238     if (gameMode != IcsExamining) {
15239         DisplayError(_("You are not examining a game"), 0);
15240         return;
15241     }
15242     if (pausing) {
15243         DisplayError(_("You can't revert while pausing"), 0);
15244         return;
15245     }
15246     SendToICS(ics_prefix);
15247     SendToICS("revert\n");
15248 }
15249
15250 void
15251 RetractMoveEvent ()
15252 {
15253     switch (gameMode) {
15254       case MachinePlaysWhite:
15255       case MachinePlaysBlack:
15256         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15257             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
15258             return;
15259         }
15260         if (forwardMostMove < 2) return;
15261         currentMove = forwardMostMove = forwardMostMove - 2;
15262         whiteTimeRemaining = timeRemaining[0][currentMove];
15263         blackTimeRemaining = timeRemaining[1][currentMove];
15264         DisplayBothClocks();
15265         DisplayMove(currentMove - 1);
15266         ClearHighlights();/*!! could figure this out*/
15267         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
15268         SendToProgram("remove\n", &first);
15269         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
15270         break;
15271
15272       case BeginningOfGame:
15273       default:
15274         break;
15275
15276       case IcsPlayingWhite:
15277       case IcsPlayingBlack:
15278         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
15279             SendToICS(ics_prefix);
15280             SendToICS("takeback 2\n");
15281         } else {
15282             SendToICS(ics_prefix);
15283             SendToICS("takeback 1\n");
15284         }
15285         break;
15286     }
15287 }
15288
15289 void
15290 MoveNowEvent ()
15291 {
15292     ChessProgramState *cps;
15293
15294     switch (gameMode) {
15295       case MachinePlaysWhite:
15296         if (!WhiteOnMove(forwardMostMove)) {
15297             DisplayError(_("It is your turn"), 0);
15298             return;
15299         }
15300         cps = &first;
15301         break;
15302       case MachinePlaysBlack:
15303         if (WhiteOnMove(forwardMostMove)) {
15304             DisplayError(_("It is your turn"), 0);
15305             return;
15306         }
15307         cps = &first;
15308         break;
15309       case TwoMachinesPlay:
15310         if (WhiteOnMove(forwardMostMove) ==
15311             (first.twoMachinesColor[0] == 'w')) {
15312             cps = &first;
15313         } else {
15314             cps = &second;
15315         }
15316         break;
15317       case BeginningOfGame:
15318       default:
15319         return;
15320     }
15321     SendToProgram("?\n", cps);
15322 }
15323
15324 void
15325 TruncateGameEvent ()
15326 {
15327     EditGameEvent();
15328     if (gameMode != EditGame) return;
15329     TruncateGame();
15330 }
15331
15332 void
15333 TruncateGame ()
15334 {
15335     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
15336     if (forwardMostMove > currentMove) {
15337         if (gameInfo.resultDetails != NULL) {
15338             free(gameInfo.resultDetails);
15339             gameInfo.resultDetails = NULL;
15340             gameInfo.result = GameUnfinished;
15341         }
15342         forwardMostMove = currentMove;
15343         HistorySet(parseList, backwardMostMove, forwardMostMove,
15344                    currentMove-1);
15345     }
15346 }
15347
15348 void
15349 HintEvent ()
15350 {
15351     if (appData.noChessProgram) return;
15352     switch (gameMode) {
15353       case MachinePlaysWhite:
15354         if (WhiteOnMove(forwardMostMove)) {
15355             DisplayError(_("Wait until your turn"), 0);
15356             return;
15357         }
15358         break;
15359       case BeginningOfGame:
15360       case MachinePlaysBlack:
15361         if (!WhiteOnMove(forwardMostMove)) {
15362             DisplayError(_("Wait until your turn"), 0);
15363             return;
15364         }
15365         break;
15366       default:
15367         DisplayError(_("No hint available"), 0);
15368         return;
15369     }
15370     SendToProgram("hint\n", &first);
15371     hintRequested = TRUE;
15372 }
15373
15374 void
15375 CreateBookEvent ()
15376 {
15377     ListGame * lg = (ListGame *) gameList.head;
15378     FILE *f, *g;
15379     int nItem;
15380     static int secondTime = FALSE;
15381
15382     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
15383         DisplayError(_("Game list not loaded or empty"), 0);
15384         return;
15385     }
15386
15387     if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
15388         fclose(g);
15389         secondTime++;
15390         DisplayNote(_("Book file exists! Try again for overwrite."));
15391         return;
15392     }
15393
15394     creatingBook = TRUE;
15395     secondTime = FALSE;
15396
15397     /* Get list size */
15398     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
15399         LoadGame(f, nItem, "", TRUE);
15400         AddGameToBook(TRUE);
15401         lg = (ListGame *) lg->node.succ;
15402     }
15403
15404     creatingBook = FALSE;
15405     FlushBook();
15406 }
15407
15408 void
15409 BookEvent ()
15410 {
15411     if (appData.noChessProgram) return;
15412     switch (gameMode) {
15413       case MachinePlaysWhite:
15414         if (WhiteOnMove(forwardMostMove)) {
15415             DisplayError(_("Wait until your turn"), 0);
15416             return;
15417         }
15418         break;
15419       case BeginningOfGame:
15420       case MachinePlaysBlack:
15421         if (!WhiteOnMove(forwardMostMove)) {
15422             DisplayError(_("Wait until your turn"), 0);
15423             return;
15424         }
15425         break;
15426       case EditPosition:
15427         EditPositionDone(TRUE);
15428         break;
15429       case TwoMachinesPlay:
15430         return;
15431       default:
15432         break;
15433     }
15434     SendToProgram("bk\n", &first);
15435     bookOutput[0] = NULLCHAR;
15436     bookRequested = TRUE;
15437 }
15438
15439 void
15440 AboutGameEvent ()
15441 {
15442     char *tags = PGNTags(&gameInfo);
15443     TagsPopUp(tags, CmailMsg());
15444     free(tags);
15445 }
15446
15447 /* end button procedures */
15448
15449 void
15450 PrintPosition (FILE *fp, int move)
15451 {
15452     int i, j;
15453
15454     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15455         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15456             char c = PieceToChar(boards[move][i][j]);
15457             fputc(c == 'x' ? '.' : c, fp);
15458             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
15459         }
15460     }
15461     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
15462       fprintf(fp, "white to play\n");
15463     else
15464       fprintf(fp, "black to play\n");
15465 }
15466
15467 void
15468 PrintOpponents (FILE *fp)
15469 {
15470     if (gameInfo.white != NULL) {
15471         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
15472     } else {
15473         fprintf(fp, "\n");
15474     }
15475 }
15476
15477 /* Find last component of program's own name, using some heuristics */
15478 void
15479 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
15480 {
15481     char *p, *q, c;
15482     int local = (strcmp(host, "localhost") == 0);
15483     while (!local && (p = strchr(prog, ';')) != NULL) {
15484         p++;
15485         while (*p == ' ') p++;
15486         prog = p;
15487     }
15488     if (*prog == '"' || *prog == '\'') {
15489         q = strchr(prog + 1, *prog);
15490     } else {
15491         q = strchr(prog, ' ');
15492     }
15493     if (q == NULL) q = prog + strlen(prog);
15494     p = q;
15495     while (p >= prog && *p != '/' && *p != '\\') p--;
15496     p++;
15497     if(p == prog && *p == '"') p++;
15498     c = *q; *q = 0;
15499     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
15500     memcpy(buf, p, q - p);
15501     buf[q - p] = NULLCHAR;
15502     if (!local) {
15503         strcat(buf, "@");
15504         strcat(buf, host);
15505     }
15506 }
15507
15508 char *
15509 TimeControlTagValue ()
15510 {
15511     char buf[MSG_SIZ];
15512     if (!appData.clockMode) {
15513       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
15514     } else if (movesPerSession > 0) {
15515       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
15516     } else if (timeIncrement == 0) {
15517       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
15518     } else {
15519       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
15520     }
15521     return StrSave(buf);
15522 }
15523
15524 void
15525 SetGameInfo ()
15526 {
15527     /* This routine is used only for certain modes */
15528     VariantClass v = gameInfo.variant;
15529     ChessMove r = GameUnfinished;
15530     char *p = NULL;
15531
15532     if(keepInfo) return;
15533
15534     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
15535         r = gameInfo.result;
15536         p = gameInfo.resultDetails;
15537         gameInfo.resultDetails = NULL;
15538     }
15539     ClearGameInfo(&gameInfo);
15540     gameInfo.variant = v;
15541
15542     switch (gameMode) {
15543       case MachinePlaysWhite:
15544         gameInfo.event = StrSave( appData.pgnEventHeader );
15545         gameInfo.site = StrSave(HostName());
15546         gameInfo.date = PGNDate();
15547         gameInfo.round = StrSave("-");
15548         gameInfo.white = StrSave(first.tidy);
15549         gameInfo.black = StrSave(UserName());
15550         gameInfo.timeControl = TimeControlTagValue();
15551         break;
15552
15553       case MachinePlaysBlack:
15554         gameInfo.event = StrSave( appData.pgnEventHeader );
15555         gameInfo.site = StrSave(HostName());
15556         gameInfo.date = PGNDate();
15557         gameInfo.round = StrSave("-");
15558         gameInfo.white = StrSave(UserName());
15559         gameInfo.black = StrSave(first.tidy);
15560         gameInfo.timeControl = TimeControlTagValue();
15561         break;
15562
15563       case TwoMachinesPlay:
15564         gameInfo.event = StrSave( appData.pgnEventHeader );
15565         gameInfo.site = StrSave(HostName());
15566         gameInfo.date = PGNDate();
15567         if (roundNr > 0) {
15568             char buf[MSG_SIZ];
15569             snprintf(buf, MSG_SIZ, "%d", roundNr);
15570             gameInfo.round = StrSave(buf);
15571         } else {
15572             gameInfo.round = StrSave("-");
15573         }
15574         if (first.twoMachinesColor[0] == 'w') {
15575             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15576             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15577         } else {
15578             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15579             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15580         }
15581         gameInfo.timeControl = TimeControlTagValue();
15582         break;
15583
15584       case EditGame:
15585         gameInfo.event = StrSave("Edited game");
15586         gameInfo.site = StrSave(HostName());
15587         gameInfo.date = PGNDate();
15588         gameInfo.round = StrSave("-");
15589         gameInfo.white = StrSave("-");
15590         gameInfo.black = StrSave("-");
15591         gameInfo.result = r;
15592         gameInfo.resultDetails = p;
15593         break;
15594
15595       case EditPosition:
15596         gameInfo.event = StrSave("Edited position");
15597         gameInfo.site = StrSave(HostName());
15598         gameInfo.date = PGNDate();
15599         gameInfo.round = StrSave("-");
15600         gameInfo.white = StrSave("-");
15601         gameInfo.black = StrSave("-");
15602         break;
15603
15604       case IcsPlayingWhite:
15605       case IcsPlayingBlack:
15606       case IcsObserving:
15607       case IcsExamining:
15608         break;
15609
15610       case PlayFromGameFile:
15611         gameInfo.event = StrSave("Game from non-PGN file");
15612         gameInfo.site = StrSave(HostName());
15613         gameInfo.date = PGNDate();
15614         gameInfo.round = StrSave("-");
15615         gameInfo.white = StrSave("?");
15616         gameInfo.black = StrSave("?");
15617         break;
15618
15619       default:
15620         break;
15621     }
15622 }
15623
15624 void
15625 ReplaceComment (int index, char *text)
15626 {
15627     int len;
15628     char *p;
15629     float score;
15630
15631     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
15632        pvInfoList[index-1].depth == len &&
15633        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
15634        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
15635     while (*text == '\n') text++;
15636     len = strlen(text);
15637     while (len > 0 && text[len - 1] == '\n') len--;
15638
15639     if (commentList[index] != NULL)
15640       free(commentList[index]);
15641
15642     if (len == 0) {
15643         commentList[index] = NULL;
15644         return;
15645     }
15646   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
15647       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
15648       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
15649     commentList[index] = (char *) malloc(len + 2);
15650     strncpy(commentList[index], text, len);
15651     commentList[index][len] = '\n';
15652     commentList[index][len + 1] = NULLCHAR;
15653   } else {
15654     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
15655     char *p;
15656     commentList[index] = (char *) malloc(len + 7);
15657     safeStrCpy(commentList[index], "{\n", 3);
15658     safeStrCpy(commentList[index]+2, text, len+1);
15659     commentList[index][len+2] = NULLCHAR;
15660     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
15661     strcat(commentList[index], "\n}\n");
15662   }
15663 }
15664
15665 void
15666 CrushCRs (char *text)
15667 {
15668   char *p = text;
15669   char *q = text;
15670   char ch;
15671
15672   do {
15673     ch = *p++;
15674     if (ch == '\r') continue;
15675     *q++ = ch;
15676   } while (ch != '\0');
15677 }
15678
15679 void
15680 AppendComment (int index, char *text, Boolean addBraces)
15681 /* addBraces  tells if we should add {} */
15682 {
15683     int oldlen, len;
15684     char *old;
15685
15686 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
15687     if(addBraces == 3) addBraces = 0; else // force appending literally
15688     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
15689
15690     CrushCRs(text);
15691     while (*text == '\n') text++;
15692     len = strlen(text);
15693     while (len > 0 && text[len - 1] == '\n') len--;
15694     text[len] = NULLCHAR;
15695
15696     if (len == 0) return;
15697
15698     if (commentList[index] != NULL) {
15699       Boolean addClosingBrace = addBraces;
15700         old = commentList[index];
15701         oldlen = strlen(old);
15702         while(commentList[index][oldlen-1] ==  '\n')
15703           commentList[index][--oldlen] = NULLCHAR;
15704         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
15705         safeStrCpy(commentList[index], old, oldlen + len + 6);
15706         free(old);
15707         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
15708         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
15709           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
15710           while (*text == '\n') { text++; len--; }
15711           commentList[index][--oldlen] = NULLCHAR;
15712       }
15713         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
15714         else          strcat(commentList[index], "\n");
15715         strcat(commentList[index], text);
15716         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
15717         else          strcat(commentList[index], "\n");
15718     } else {
15719         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
15720         if(addBraces)
15721           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
15722         else commentList[index][0] = NULLCHAR;
15723         strcat(commentList[index], text);
15724         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
15725         if(addBraces == TRUE) strcat(commentList[index], "}\n");
15726     }
15727 }
15728
15729 static char *
15730 FindStr (char * text, char * sub_text)
15731 {
15732     char * result = strstr( text, sub_text );
15733
15734     if( result != NULL ) {
15735         result += strlen( sub_text );
15736     }
15737
15738     return result;
15739 }
15740
15741 /* [AS] Try to extract PV info from PGN comment */
15742 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
15743 char *
15744 GetInfoFromComment (int index, char * text)
15745 {
15746     char * sep = text, *p;
15747
15748     if( text != NULL && index > 0 ) {
15749         int score = 0;
15750         int depth = 0;
15751         int time = -1, sec = 0, deci;
15752         char * s_eval = FindStr( text, "[%eval " );
15753         char * s_emt = FindStr( text, "[%emt " );
15754 #if 0
15755         if( s_eval != NULL || s_emt != NULL ) {
15756 #else
15757         if(0) { // [HGM] this code is not finished, and could actually be detrimental
15758 #endif
15759             /* New style */
15760             char delim;
15761
15762             if( s_eval != NULL ) {
15763                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
15764                     return text;
15765                 }
15766
15767                 if( delim != ']' ) {
15768                     return text;
15769                 }
15770             }
15771
15772             if( s_emt != NULL ) {
15773             }
15774                 return text;
15775         }
15776         else {
15777             /* We expect something like: [+|-]nnn.nn/dd */
15778             int score_lo = 0;
15779
15780             if(*text != '{') return text; // [HGM] braces: must be normal comment
15781
15782             sep = strchr( text, '/' );
15783             if( sep == NULL || sep < (text+4) ) {
15784                 return text;
15785             }
15786
15787             p = text;
15788             if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
15789             if(p[1] == '(') { // comment starts with PV
15790                p = strchr(p, ')'); // locate end of PV
15791                if(p == NULL || sep < p+5) return text;
15792                // at this point we have something like "{(.*) +0.23/6 ..."
15793                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
15794                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
15795                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
15796             }
15797             time = -1; sec = -1; deci = -1;
15798             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
15799                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
15800                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
15801                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
15802                 return text;
15803             }
15804
15805             if( score_lo < 0 || score_lo >= 100 ) {
15806                 return text;
15807             }
15808
15809             if(sec >= 0) time = 600*time + 10*sec; else
15810             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
15811
15812             score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
15813
15814             /* [HGM] PV time: now locate end of PV info */
15815             while( *++sep >= '0' && *sep <= '9'); // strip depth
15816             if(time >= 0)
15817             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
15818             if(sec >= 0)
15819             while( *++sep >= '0' && *sep <= '9'); // strip seconds
15820             if(deci >= 0)
15821             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
15822             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
15823         }
15824
15825         if( depth <= 0 ) {
15826             return text;
15827         }
15828
15829         if( time < 0 ) {
15830             time = -1;
15831         }
15832
15833         pvInfoList[index-1].depth = depth;
15834         pvInfoList[index-1].score = score;
15835         pvInfoList[index-1].time  = 10*time; // centi-sec
15836         if(*sep == '}') *sep = 0; else *--sep = '{';
15837         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
15838     }
15839     return sep;
15840 }
15841
15842 void
15843 SendToProgram (char *message, ChessProgramState *cps)
15844 {
15845     int count, outCount, error;
15846     char buf[MSG_SIZ];
15847
15848     if (cps->pr == NoProc) return;
15849     Attention(cps);
15850
15851     if (appData.debugMode) {
15852         TimeMark now;
15853         GetTimeMark(&now);
15854         fprintf(debugFP, "%ld >%-6s: %s",
15855                 SubtractTimeMarks(&now, &programStartTime),
15856                 cps->which, message);
15857         if(serverFP)
15858             fprintf(serverFP, "%ld >%-6s: %s",
15859                 SubtractTimeMarks(&now, &programStartTime),
15860                 cps->which, message), fflush(serverFP);
15861     }
15862
15863     count = strlen(message);
15864     outCount = OutputToProcess(cps->pr, message, count, &error);
15865     if (outCount < count && !exiting
15866                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
15867       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15868       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
15869         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15870             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15871                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15872                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15873                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15874             } else {
15875                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15876                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15877                 gameInfo.result = res;
15878             }
15879             gameInfo.resultDetails = StrSave(buf);
15880         }
15881         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15882         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15883     }
15884 }
15885
15886 void
15887 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
15888 {
15889     char *end_str;
15890     char buf[MSG_SIZ];
15891     ChessProgramState *cps = (ChessProgramState *)closure;
15892
15893     if (isr != cps->isr) return; /* Killed intentionally */
15894     if (count <= 0) {
15895         if (count == 0) {
15896             RemoveInputSource(cps->isr);
15897             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
15898                     _(cps->which), cps->program);
15899             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
15900             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15901                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15902                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15903                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15904                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15905                 } else {
15906                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15907                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15908                     gameInfo.result = res;
15909                 }
15910                 gameInfo.resultDetails = StrSave(buf);
15911             }
15912             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15913             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
15914         } else {
15915             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
15916                     _(cps->which), cps->program);
15917             RemoveInputSource(cps->isr);
15918
15919             /* [AS] Program is misbehaving badly... kill it */
15920             if( count == -2 ) {
15921                 DestroyChildProcess( cps->pr, 9 );
15922                 cps->pr = NoProc;
15923             }
15924
15925             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15926         }
15927         return;
15928     }
15929
15930     if ((end_str = strchr(message, '\r')) != NULL)
15931       *end_str = NULLCHAR;
15932     if ((end_str = strchr(message, '\n')) != NULL)
15933       *end_str = NULLCHAR;
15934
15935     if (appData.debugMode) {
15936         TimeMark now; int print = 1;
15937         char *quote = ""; char c; int i;
15938
15939         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
15940                 char start = message[0];
15941                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
15942                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
15943                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
15944                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
15945                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
15946                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
15947                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
15948                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
15949                    sscanf(message, "hint: %c", &c)!=1 &&
15950                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
15951                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
15952                     print = (appData.engineComments >= 2);
15953                 }
15954                 message[0] = start; // restore original message
15955         }
15956         if(print) {
15957                 GetTimeMark(&now);
15958                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
15959                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15960                         quote,
15961                         message);
15962                 if(serverFP)
15963                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
15964                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15965                         quote,
15966                         message), fflush(serverFP);
15967         }
15968     }
15969
15970     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
15971     if (appData.icsEngineAnalyze) {
15972         if (strstr(message, "whisper") != NULL ||
15973              strstr(message, "kibitz") != NULL ||
15974             strstr(message, "tellics") != NULL) return;
15975     }
15976
15977     HandleMachineMove(message, cps);
15978 }
15979
15980
15981 void
15982 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
15983 {
15984     char buf[MSG_SIZ];
15985     int seconds;
15986
15987     if( timeControl_2 > 0 ) {
15988         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
15989             tc = timeControl_2;
15990         }
15991     }
15992     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
15993     inc /= cps->timeOdds;
15994     st  /= cps->timeOdds;
15995
15996     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
15997
15998     if (st > 0) {
15999       /* Set exact time per move, normally using st command */
16000       if (cps->stKludge) {
16001         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
16002         seconds = st % 60;
16003         if (seconds == 0) {
16004           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
16005         } else {
16006           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
16007         }
16008       } else {
16009         snprintf(buf, MSG_SIZ, "st %d\n", st);
16010       }
16011     } else {
16012       /* Set conventional or incremental time control, using level command */
16013       if (seconds == 0) {
16014         /* Note old gnuchess bug -- minutes:seconds used to not work.
16015            Fixed in later versions, but still avoid :seconds
16016            when seconds is 0. */
16017         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
16018       } else {
16019         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
16020                  seconds, inc/1000.);
16021       }
16022     }
16023     SendToProgram(buf, cps);
16024
16025     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
16026     /* Orthogonally, limit search to given depth */
16027     if (sd > 0) {
16028       if (cps->sdKludge) {
16029         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
16030       } else {
16031         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
16032       }
16033       SendToProgram(buf, cps);
16034     }
16035
16036     if(cps->nps >= 0) { /* [HGM] nps */
16037         if(cps->supportsNPS == FALSE)
16038           cps->nps = -1; // don't use if engine explicitly says not supported!
16039         else {
16040           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
16041           SendToProgram(buf, cps);
16042         }
16043     }
16044 }
16045
16046 ChessProgramState *
16047 WhitePlayer ()
16048 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
16049 {
16050     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
16051        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
16052         return &second;
16053     return &first;
16054 }
16055
16056 void
16057 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
16058 {
16059     char message[MSG_SIZ];
16060     long time, otime;
16061
16062     /* Note: this routine must be called when the clocks are stopped
16063        or when they have *just* been set or switched; otherwise
16064        it will be off by the time since the current tick started.
16065     */
16066     if (machineWhite) {
16067         time = whiteTimeRemaining / 10;
16068         otime = blackTimeRemaining / 10;
16069     } else {
16070         time = blackTimeRemaining / 10;
16071         otime = whiteTimeRemaining / 10;
16072     }
16073     /* [HGM] translate opponent's time by time-odds factor */
16074     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
16075
16076     if (time <= 0) time = 1;
16077     if (otime <= 0) otime = 1;
16078
16079     snprintf(message, MSG_SIZ, "time %ld\n", time);
16080     SendToProgram(message, cps);
16081
16082     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
16083     SendToProgram(message, cps);
16084 }
16085
16086 char *
16087 EngineDefinedVariant (ChessProgramState *cps, int n)
16088 {   // return name of n-th unknown variant that engine supports
16089     static char buf[MSG_SIZ];
16090     char *p, *s = cps->variants;
16091     if(!s) return NULL;
16092     do { // parse string from variants feature
16093       VariantClass v;
16094         p = strchr(s, ',');
16095         if(p) *p = NULLCHAR;
16096       v = StringToVariant(s);
16097       if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
16098         if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
16099             if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
16100         }
16101         if(p) *p++ = ',';
16102         if(n < 0) return buf;
16103     } while(s = p);
16104     return NULL;
16105 }
16106
16107 int
16108 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16109 {
16110   char buf[MSG_SIZ];
16111   int len = strlen(name);
16112   int val;
16113
16114   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16115     (*p) += len + 1;
16116     sscanf(*p, "%d", &val);
16117     *loc = (val != 0);
16118     while (**p && **p != ' ')
16119       (*p)++;
16120     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16121     SendToProgram(buf, cps);
16122     return TRUE;
16123   }
16124   return FALSE;
16125 }
16126
16127 int
16128 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16129 {
16130   char buf[MSG_SIZ];
16131   int len = strlen(name);
16132   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16133     (*p) += len + 1;
16134     sscanf(*p, "%d", loc);
16135     while (**p && **p != ' ') (*p)++;
16136     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16137     SendToProgram(buf, cps);
16138     return TRUE;
16139   }
16140   return FALSE;
16141 }
16142
16143 int
16144 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
16145 {
16146   char buf[MSG_SIZ];
16147   int len = strlen(name);
16148   if (strncmp((*p), name, len) == 0
16149       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
16150     (*p) += len + 2;
16151     ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
16152     sscanf(*p, "%[^\"]", *loc);
16153     while (**p && **p != '\"') (*p)++;
16154     if (**p == '\"') (*p)++;
16155     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16156     SendToProgram(buf, cps);
16157     return TRUE;
16158   }
16159   return FALSE;
16160 }
16161
16162 int
16163 ParseOption (Option *opt, ChessProgramState *cps)
16164 // [HGM] options: process the string that defines an engine option, and determine
16165 // name, type, default value, and allowed value range
16166 {
16167         char *p, *q, buf[MSG_SIZ];
16168         int n, min = (-1)<<31, max = 1<<31, def;
16169
16170         if(p = strstr(opt->name, " -spin ")) {
16171             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16172             if(max < min) max = min; // enforce consistency
16173             if(def < min) def = min;
16174             if(def > max) def = max;
16175             opt->value = def;
16176             opt->min = min;
16177             opt->max = max;
16178             opt->type = Spin;
16179         } else if((p = strstr(opt->name, " -slider "))) {
16180             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
16181             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16182             if(max < min) max = min; // enforce consistency
16183             if(def < min) def = min;
16184             if(def > max) def = max;
16185             opt->value = def;
16186             opt->min = min;
16187             opt->max = max;
16188             opt->type = Spin; // Slider;
16189         } else if((p = strstr(opt->name, " -string "))) {
16190             opt->textValue = p+9;
16191             opt->type = TextBox;
16192         } else if((p = strstr(opt->name, " -file "))) {
16193             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16194             opt->textValue = p+7;
16195             opt->type = FileName; // FileName;
16196         } else if((p = strstr(opt->name, " -path "))) {
16197             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16198             opt->textValue = p+7;
16199             opt->type = PathName; // PathName;
16200         } else if(p = strstr(opt->name, " -check ")) {
16201             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
16202             opt->value = (def != 0);
16203             opt->type = CheckBox;
16204         } else if(p = strstr(opt->name, " -combo ")) {
16205             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
16206             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
16207             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
16208             opt->value = n = 0;
16209             while(q = StrStr(q, " /// ")) {
16210                 n++; *q = 0;    // count choices, and null-terminate each of them
16211                 q += 5;
16212                 if(*q == '*') { // remember default, which is marked with * prefix
16213                     q++;
16214                     opt->value = n;
16215                 }
16216                 cps->comboList[cps->comboCnt++] = q;
16217             }
16218             cps->comboList[cps->comboCnt++] = NULL;
16219             opt->max = n + 1;
16220             opt->type = ComboBox;
16221         } else if(p = strstr(opt->name, " -button")) {
16222             opt->type = Button;
16223         } else if(p = strstr(opt->name, " -save")) {
16224             opt->type = SaveButton;
16225         } else return FALSE;
16226         *p = 0; // terminate option name
16227         // now look if the command-line options define a setting for this engine option.
16228         if(cps->optionSettings && cps->optionSettings[0])
16229             p = strstr(cps->optionSettings, opt->name); else p = NULL;
16230         if(p && (p == cps->optionSettings || p[-1] == ',')) {
16231           snprintf(buf, MSG_SIZ, "option %s", p);
16232                 if(p = strstr(buf, ",")) *p = 0;
16233                 if(q = strchr(buf, '=')) switch(opt->type) {
16234                     case ComboBox:
16235                         for(n=0; n<opt->max; n++)
16236                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
16237                         break;
16238                     case TextBox:
16239                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
16240                         break;
16241                     case Spin:
16242                     case CheckBox:
16243                         opt->value = atoi(q+1);
16244                     default:
16245                         break;
16246                 }
16247                 strcat(buf, "\n");
16248                 SendToProgram(buf, cps);
16249         }
16250         return TRUE;
16251 }
16252
16253 void
16254 FeatureDone (ChessProgramState *cps, int val)
16255 {
16256   DelayedEventCallback cb = GetDelayedEvent();
16257   if ((cb == InitBackEnd3 && cps == &first) ||
16258       (cb == SettingsMenuIfReady && cps == &second) ||
16259       (cb == LoadEngine) ||
16260       (cb == TwoMachinesEventIfReady)) {
16261     CancelDelayedEvent();
16262     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
16263   }
16264   cps->initDone = val;
16265   if(val) cps->reload = FALSE;
16266 }
16267
16268 /* Parse feature command from engine */
16269 void
16270 ParseFeatures (char *args, ChessProgramState *cps)
16271 {
16272   char *p = args;
16273   char *q = NULL;
16274   int val;
16275   char buf[MSG_SIZ];
16276
16277   for (;;) {
16278     while (*p == ' ') p++;
16279     if (*p == NULLCHAR) return;
16280
16281     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
16282     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
16283     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
16284     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
16285     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
16286     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
16287     if (BoolFeature(&p, "reuse", &val, cps)) {
16288       /* Engine can disable reuse, but can't enable it if user said no */
16289       if (!val) cps->reuse = FALSE;
16290       continue;
16291     }
16292     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
16293     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
16294       if (gameMode == TwoMachinesPlay) {
16295         DisplayTwoMachinesTitle();
16296       } else {
16297         DisplayTitle("");
16298       }
16299       continue;
16300     }
16301     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
16302     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
16303     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
16304     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
16305     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
16306     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
16307     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
16308     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
16309     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
16310     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
16311     if (IntFeature(&p, "done", &val, cps)) {
16312       FeatureDone(cps, val);
16313       continue;
16314     }
16315     /* Added by Tord: */
16316     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
16317     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
16318     /* End of additions by Tord */
16319
16320     /* [HGM] added features: */
16321     if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
16322     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
16323     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
16324     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
16325     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
16326     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
16327     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
16328     if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
16329         if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
16330         FREE(cps->option[cps->nrOptions].name);
16331         cps->option[cps->nrOptions].name = q; q = NULL;
16332         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
16333           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
16334             SendToProgram(buf, cps);
16335             continue;
16336         }
16337         if(cps->nrOptions >= MAX_OPTIONS) {
16338             cps->nrOptions--;
16339             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
16340             DisplayError(buf, 0);
16341         }
16342         continue;
16343     }
16344     /* End of additions by HGM */
16345
16346     /* unknown feature: complain and skip */
16347     q = p;
16348     while (*q && *q != '=') q++;
16349     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
16350     SendToProgram(buf, cps);
16351     p = q;
16352     if (*p == '=') {
16353       p++;
16354       if (*p == '\"') {
16355         p++;
16356         while (*p && *p != '\"') p++;
16357         if (*p == '\"') p++;
16358       } else {
16359         while (*p && *p != ' ') p++;
16360       }
16361     }
16362   }
16363
16364 }
16365
16366 void
16367 PeriodicUpdatesEvent (int newState)
16368 {
16369     if (newState == appData.periodicUpdates)
16370       return;
16371
16372     appData.periodicUpdates=newState;
16373
16374     /* Display type changes, so update it now */
16375 //    DisplayAnalysis();
16376
16377     /* Get the ball rolling again... */
16378     if (newState) {
16379         AnalysisPeriodicEvent(1);
16380         StartAnalysisClock();
16381     }
16382 }
16383
16384 void
16385 PonderNextMoveEvent (int newState)
16386 {
16387     if (newState == appData.ponderNextMove) return;
16388     if (gameMode == EditPosition) EditPositionDone(TRUE);
16389     if (newState) {
16390         SendToProgram("hard\n", &first);
16391         if (gameMode == TwoMachinesPlay) {
16392             SendToProgram("hard\n", &second);
16393         }
16394     } else {
16395         SendToProgram("easy\n", &first);
16396         thinkOutput[0] = NULLCHAR;
16397         if (gameMode == TwoMachinesPlay) {
16398             SendToProgram("easy\n", &second);
16399         }
16400     }
16401     appData.ponderNextMove = newState;
16402 }
16403
16404 void
16405 NewSettingEvent (int option, int *feature, char *command, int value)
16406 {
16407     char buf[MSG_SIZ];
16408
16409     if (gameMode == EditPosition) EditPositionDone(TRUE);
16410     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
16411     if(feature == NULL || *feature) SendToProgram(buf, &first);
16412     if (gameMode == TwoMachinesPlay) {
16413         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
16414     }
16415 }
16416
16417 void
16418 ShowThinkingEvent ()
16419 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
16420 {
16421     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
16422     int newState = appData.showThinking
16423         // [HGM] thinking: other features now need thinking output as well
16424         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
16425
16426     if (oldState == newState) return;
16427     oldState = newState;
16428     if (gameMode == EditPosition) EditPositionDone(TRUE);
16429     if (oldState) {
16430         SendToProgram("post\n", &first);
16431         if (gameMode == TwoMachinesPlay) {
16432             SendToProgram("post\n", &second);
16433         }
16434     } else {
16435         SendToProgram("nopost\n", &first);
16436         thinkOutput[0] = NULLCHAR;
16437         if (gameMode == TwoMachinesPlay) {
16438             SendToProgram("nopost\n", &second);
16439         }
16440     }
16441 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
16442 }
16443
16444 void
16445 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
16446 {
16447   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
16448   if (pr == NoProc) return;
16449   AskQuestion(title, question, replyPrefix, pr);
16450 }
16451
16452 void
16453 TypeInEvent (char firstChar)
16454 {
16455     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
16456         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
16457         gameMode == AnalyzeMode || gameMode == EditGame ||
16458         gameMode == EditPosition || gameMode == IcsExamining ||
16459         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
16460         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
16461                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
16462                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
16463         gameMode == Training) PopUpMoveDialog(firstChar);
16464 }
16465
16466 void
16467 TypeInDoneEvent (char *move)
16468 {
16469         Board board;
16470         int n, fromX, fromY, toX, toY;
16471         char promoChar;
16472         ChessMove moveType;
16473
16474         // [HGM] FENedit
16475         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
16476                 EditPositionPasteFEN(move);
16477                 return;
16478         }
16479         // [HGM] movenum: allow move number to be typed in any mode
16480         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
16481           ToNrEvent(2*n-1);
16482           return;
16483         }
16484         // undocumented kludge: allow command-line option to be typed in!
16485         // (potentially fatal, and does not implement the effect of the option.)
16486         // should only be used for options that are values on which future decisions will be made,
16487         // and definitely not on options that would be used during initialization.
16488         if(strstr(move, "!!! -") == move) {
16489             ParseArgsFromString(move+4);
16490             return;
16491         }
16492
16493       if (gameMode != EditGame && currentMove != forwardMostMove &&
16494         gameMode != Training) {
16495         DisplayMoveError(_("Displayed move is not current"));
16496       } else {
16497         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16498           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
16499         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
16500         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16501           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
16502           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
16503         } else {
16504           DisplayMoveError(_("Could not parse move"));
16505         }
16506       }
16507 }
16508
16509 void
16510 DisplayMove (int moveNumber)
16511 {
16512     char message[MSG_SIZ];
16513     char res[MSG_SIZ];
16514     char cpThinkOutput[MSG_SIZ];
16515
16516     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
16517
16518     if (moveNumber == forwardMostMove - 1 ||
16519         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16520
16521         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
16522
16523         if (strchr(cpThinkOutput, '\n')) {
16524             *strchr(cpThinkOutput, '\n') = NULLCHAR;
16525         }
16526     } else {
16527         *cpThinkOutput = NULLCHAR;
16528     }
16529
16530     /* [AS] Hide thinking from human user */
16531     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
16532         *cpThinkOutput = NULLCHAR;
16533         if( thinkOutput[0] != NULLCHAR ) {
16534             int i;
16535
16536             for( i=0; i<=hiddenThinkOutputState; i++ ) {
16537                 cpThinkOutput[i] = '.';
16538             }
16539             cpThinkOutput[i] = NULLCHAR;
16540             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
16541         }
16542     }
16543
16544     if (moveNumber == forwardMostMove - 1 &&
16545         gameInfo.resultDetails != NULL) {
16546         if (gameInfo.resultDetails[0] == NULLCHAR) {
16547           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
16548         } else {
16549           snprintf(res, MSG_SIZ, " {%s} %s",
16550                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
16551         }
16552     } else {
16553         res[0] = NULLCHAR;
16554     }
16555
16556     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16557         DisplayMessage(res, cpThinkOutput);
16558     } else {
16559       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
16560                 WhiteOnMove(moveNumber) ? " " : ".. ",
16561                 parseList[moveNumber], res);
16562         DisplayMessage(message, cpThinkOutput);
16563     }
16564 }
16565
16566 void
16567 DisplayComment (int moveNumber, char *text)
16568 {
16569     char title[MSG_SIZ];
16570
16571     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16572       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
16573     } else {
16574       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
16575               WhiteOnMove(moveNumber) ? " " : ".. ",
16576               parseList[moveNumber]);
16577     }
16578     if (text != NULL && (appData.autoDisplayComment || commentUp))
16579         CommentPopUp(title, text);
16580 }
16581
16582 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
16583  * might be busy thinking or pondering.  It can be omitted if your
16584  * gnuchess is configured to stop thinking immediately on any user
16585  * input.  However, that gnuchess feature depends on the FIONREAD
16586  * ioctl, which does not work properly on some flavors of Unix.
16587  */
16588 void
16589 Attention (ChessProgramState *cps)
16590 {
16591 #if ATTENTION
16592     if (!cps->useSigint) return;
16593     if (appData.noChessProgram || (cps->pr == NoProc)) return;
16594     switch (gameMode) {
16595       case MachinePlaysWhite:
16596       case MachinePlaysBlack:
16597       case TwoMachinesPlay:
16598       case IcsPlayingWhite:
16599       case IcsPlayingBlack:
16600       case AnalyzeMode:
16601       case AnalyzeFile:
16602         /* Skip if we know it isn't thinking */
16603         if (!cps->maybeThinking) return;
16604         if (appData.debugMode)
16605           fprintf(debugFP, "Interrupting %s\n", cps->which);
16606         InterruptChildProcess(cps->pr);
16607         cps->maybeThinking = FALSE;
16608         break;
16609       default:
16610         break;
16611     }
16612 #endif /*ATTENTION*/
16613 }
16614
16615 int
16616 CheckFlags ()
16617 {
16618     if (whiteTimeRemaining <= 0) {
16619         if (!whiteFlag) {
16620             whiteFlag = TRUE;
16621             if (appData.icsActive) {
16622                 if (appData.autoCallFlag &&
16623                     gameMode == IcsPlayingBlack && !blackFlag) {
16624                   SendToICS(ics_prefix);
16625                   SendToICS("flag\n");
16626                 }
16627             } else {
16628                 if (blackFlag) {
16629                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16630                 } else {
16631                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
16632                     if (appData.autoCallFlag) {
16633                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
16634                         return TRUE;
16635                     }
16636                 }
16637             }
16638         }
16639     }
16640     if (blackTimeRemaining <= 0) {
16641         if (!blackFlag) {
16642             blackFlag = TRUE;
16643             if (appData.icsActive) {
16644                 if (appData.autoCallFlag &&
16645                     gameMode == IcsPlayingWhite && !whiteFlag) {
16646                   SendToICS(ics_prefix);
16647                   SendToICS("flag\n");
16648                 }
16649             } else {
16650                 if (whiteFlag) {
16651                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16652                 } else {
16653                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
16654                     if (appData.autoCallFlag) {
16655                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
16656                         return TRUE;
16657                     }
16658                 }
16659             }
16660         }
16661     }
16662     return FALSE;
16663 }
16664
16665 void
16666 CheckTimeControl ()
16667 {
16668     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
16669         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
16670
16671     /*
16672      * add time to clocks when time control is achieved ([HGM] now also used for increment)
16673      */
16674     if ( !WhiteOnMove(forwardMostMove) ) {
16675         /* White made time control */
16676         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
16677         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
16678         /* [HGM] time odds: correct new time quota for time odds! */
16679                                             / WhitePlayer()->timeOdds;
16680         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
16681     } else {
16682         lastBlack -= blackTimeRemaining;
16683         /* Black made time control */
16684         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
16685                                             / WhitePlayer()->other->timeOdds;
16686         lastWhite = whiteTimeRemaining;
16687     }
16688 }
16689
16690 void
16691 DisplayBothClocks ()
16692 {
16693     int wom = gameMode == EditPosition ?
16694       !blackPlaysFirst : WhiteOnMove(currentMove);
16695     DisplayWhiteClock(whiteTimeRemaining, wom);
16696     DisplayBlackClock(blackTimeRemaining, !wom);
16697 }
16698
16699
16700 /* Timekeeping seems to be a portability nightmare.  I think everyone
16701    has ftime(), but I'm really not sure, so I'm including some ifdefs
16702    to use other calls if you don't.  Clocks will be less accurate if
16703    you have neither ftime nor gettimeofday.
16704 */
16705
16706 /* VS 2008 requires the #include outside of the function */
16707 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
16708 #include <sys/timeb.h>
16709 #endif
16710
16711 /* Get the current time as a TimeMark */
16712 void
16713 GetTimeMark (TimeMark *tm)
16714 {
16715 #if HAVE_GETTIMEOFDAY
16716
16717     struct timeval timeVal;
16718     struct timezone timeZone;
16719
16720     gettimeofday(&timeVal, &timeZone);
16721     tm->sec = (long) timeVal.tv_sec;
16722     tm->ms = (int) (timeVal.tv_usec / 1000L);
16723
16724 #else /*!HAVE_GETTIMEOFDAY*/
16725 #if HAVE_FTIME
16726
16727 // include <sys/timeb.h> / moved to just above start of function
16728     struct timeb timeB;
16729
16730     ftime(&timeB);
16731     tm->sec = (long) timeB.time;
16732     tm->ms = (int) timeB.millitm;
16733
16734 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
16735     tm->sec = (long) time(NULL);
16736     tm->ms = 0;
16737 #endif
16738 #endif
16739 }
16740
16741 /* Return the difference in milliseconds between two
16742    time marks.  We assume the difference will fit in a long!
16743 */
16744 long
16745 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
16746 {
16747     return 1000L*(tm2->sec - tm1->sec) +
16748            (long) (tm2->ms - tm1->ms);
16749 }
16750
16751
16752 /*
16753  * Code to manage the game clocks.
16754  *
16755  * In tournament play, black starts the clock and then white makes a move.
16756  * We give the human user a slight advantage if he is playing white---the
16757  * clocks don't run until he makes his first move, so it takes zero time.
16758  * Also, we don't account for network lag, so we could get out of sync
16759  * with GNU Chess's clock -- but then, referees are always right.
16760  */
16761
16762 static TimeMark tickStartTM;
16763 static long intendedTickLength;
16764
16765 long
16766 NextTickLength (long timeRemaining)
16767 {
16768     long nominalTickLength, nextTickLength;
16769
16770     if (timeRemaining > 0L && timeRemaining <= 10000L)
16771       nominalTickLength = 100L;
16772     else
16773       nominalTickLength = 1000L;
16774     nextTickLength = timeRemaining % nominalTickLength;
16775     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
16776
16777     return nextTickLength;
16778 }
16779
16780 /* Adjust clock one minute up or down */
16781 void
16782 AdjustClock (Boolean which, int dir)
16783 {
16784     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
16785     if(which) blackTimeRemaining += 60000*dir;
16786     else      whiteTimeRemaining += 60000*dir;
16787     DisplayBothClocks();
16788     adjustedClock = TRUE;
16789 }
16790
16791 /* Stop clocks and reset to a fresh time control */
16792 void
16793 ResetClocks ()
16794 {
16795     (void) StopClockTimer();
16796     if (appData.icsActive) {
16797         whiteTimeRemaining = blackTimeRemaining = 0;
16798     } else if (searchTime) {
16799         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16800         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16801     } else { /* [HGM] correct new time quote for time odds */
16802         whiteTC = blackTC = fullTimeControlString;
16803         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
16804         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
16805     }
16806     if (whiteFlag || blackFlag) {
16807         DisplayTitle("");
16808         whiteFlag = blackFlag = FALSE;
16809     }
16810     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
16811     DisplayBothClocks();
16812     adjustedClock = FALSE;
16813 }
16814
16815 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
16816
16817 /* Decrement running clock by amount of time that has passed */
16818 void
16819 DecrementClocks ()
16820 {
16821     long timeRemaining;
16822     long lastTickLength, fudge;
16823     TimeMark now;
16824
16825     if (!appData.clockMode) return;
16826     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
16827
16828     GetTimeMark(&now);
16829
16830     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16831
16832     /* Fudge if we woke up a little too soon */
16833     fudge = intendedTickLength - lastTickLength;
16834     if (fudge < 0 || fudge > FUDGE) fudge = 0;
16835
16836     if (WhiteOnMove(forwardMostMove)) {
16837         if(whiteNPS >= 0) lastTickLength = 0;
16838         timeRemaining = whiteTimeRemaining -= lastTickLength;
16839         if(timeRemaining < 0 && !appData.icsActive) {
16840             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
16841             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
16842                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
16843                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
16844             }
16845         }
16846         DisplayWhiteClock(whiteTimeRemaining - fudge,
16847                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16848     } else {
16849         if(blackNPS >= 0) lastTickLength = 0;
16850         timeRemaining = blackTimeRemaining -= lastTickLength;
16851         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
16852             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
16853             if(suddenDeath) {
16854                 blackStartMove = forwardMostMove;
16855                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
16856             }
16857         }
16858         DisplayBlackClock(blackTimeRemaining - fudge,
16859                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16860     }
16861     if (CheckFlags()) return;
16862
16863     if(twoBoards) { // count down secondary board's clocks as well
16864         activePartnerTime -= lastTickLength;
16865         partnerUp = 1;
16866         if(activePartner == 'W')
16867             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
16868         else
16869             DisplayBlackClock(activePartnerTime, TRUE);
16870         partnerUp = 0;
16871     }
16872
16873     tickStartTM = now;
16874     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
16875     StartClockTimer(intendedTickLength);
16876
16877     /* if the time remaining has fallen below the alarm threshold, sound the
16878      * alarm. if the alarm has sounded and (due to a takeback or time control
16879      * with increment) the time remaining has increased to a level above the
16880      * threshold, reset the alarm so it can sound again.
16881      */
16882
16883     if (appData.icsActive && appData.icsAlarm) {
16884
16885         /* make sure we are dealing with the user's clock */
16886         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
16887                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
16888            )) return;
16889
16890         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
16891             alarmSounded = FALSE;
16892         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
16893             PlayAlarmSound();
16894             alarmSounded = TRUE;
16895         }
16896     }
16897 }
16898
16899
16900 /* A player has just moved, so stop the previously running
16901    clock and (if in clock mode) start the other one.
16902    We redisplay both clocks in case we're in ICS mode, because
16903    ICS gives us an update to both clocks after every move.
16904    Note that this routine is called *after* forwardMostMove
16905    is updated, so the last fractional tick must be subtracted
16906    from the color that is *not* on move now.
16907 */
16908 void
16909 SwitchClocks (int newMoveNr)
16910 {
16911     long lastTickLength;
16912     TimeMark now;
16913     int flagged = FALSE;
16914
16915     GetTimeMark(&now);
16916
16917     if (StopClockTimer() && appData.clockMode) {
16918         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16919         if (!WhiteOnMove(forwardMostMove)) {
16920             if(blackNPS >= 0) lastTickLength = 0;
16921             blackTimeRemaining -= lastTickLength;
16922            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16923 //         if(pvInfoList[forwardMostMove].time == -1)
16924                  pvInfoList[forwardMostMove].time =               // use GUI time
16925                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
16926         } else {
16927            if(whiteNPS >= 0) lastTickLength = 0;
16928            whiteTimeRemaining -= lastTickLength;
16929            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16930 //         if(pvInfoList[forwardMostMove].time == -1)
16931                  pvInfoList[forwardMostMove].time =
16932                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
16933         }
16934         flagged = CheckFlags();
16935     }
16936     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
16937     CheckTimeControl();
16938
16939     if (flagged || !appData.clockMode) return;
16940
16941     switch (gameMode) {
16942       case MachinePlaysBlack:
16943       case MachinePlaysWhite:
16944       case BeginningOfGame:
16945         if (pausing) return;
16946         break;
16947
16948       case EditGame:
16949       case PlayFromGameFile:
16950       case IcsExamining:
16951         return;
16952
16953       default:
16954         break;
16955     }
16956
16957     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
16958         if(WhiteOnMove(forwardMostMove))
16959              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16960         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16961     }
16962
16963     tickStartTM = now;
16964     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16965       whiteTimeRemaining : blackTimeRemaining);
16966     StartClockTimer(intendedTickLength);
16967 }
16968
16969
16970 /* Stop both clocks */
16971 void
16972 StopClocks ()
16973 {
16974     long lastTickLength;
16975     TimeMark now;
16976
16977     if (!StopClockTimer()) return;
16978     if (!appData.clockMode) return;
16979
16980     GetTimeMark(&now);
16981
16982     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16983     if (WhiteOnMove(forwardMostMove)) {
16984         if(whiteNPS >= 0) lastTickLength = 0;
16985         whiteTimeRemaining -= lastTickLength;
16986         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
16987     } else {
16988         if(blackNPS >= 0) lastTickLength = 0;
16989         blackTimeRemaining -= lastTickLength;
16990         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
16991     }
16992     CheckFlags();
16993 }
16994
16995 /* Start clock of player on move.  Time may have been reset, so
16996    if clock is already running, stop and restart it. */
16997 void
16998 StartClocks ()
16999 {
17000     (void) StopClockTimer(); /* in case it was running already */
17001     DisplayBothClocks();
17002     if (CheckFlags()) return;
17003
17004     if (!appData.clockMode) return;
17005     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
17006
17007     GetTimeMark(&tickStartTM);
17008     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17009       whiteTimeRemaining : blackTimeRemaining);
17010
17011    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
17012     whiteNPS = blackNPS = -1;
17013     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
17014        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
17015         whiteNPS = first.nps;
17016     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
17017        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
17018         blackNPS = first.nps;
17019     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
17020         whiteNPS = second.nps;
17021     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
17022         blackNPS = second.nps;
17023     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
17024
17025     StartClockTimer(intendedTickLength);
17026 }
17027
17028 char *
17029 TimeString (long ms)
17030 {
17031     long second, minute, hour, day;
17032     char *sign = "";
17033     static char buf[32];
17034
17035     if (ms > 0 && ms <= 9900) {
17036       /* convert milliseconds to tenths, rounding up */
17037       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
17038
17039       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
17040       return buf;
17041     }
17042
17043     /* convert milliseconds to seconds, rounding up */
17044     /* use floating point to avoid strangeness of integer division
17045        with negative dividends on many machines */
17046     second = (long) floor(((double) (ms + 999L)) / 1000.0);
17047
17048     if (second < 0) {
17049         sign = "-";
17050         second = -second;
17051     }
17052
17053     day = second / (60 * 60 * 24);
17054     second = second % (60 * 60 * 24);
17055     hour = second / (60 * 60);
17056     second = second % (60 * 60);
17057     minute = second / 60;
17058     second = second % 60;
17059
17060     if (day > 0)
17061       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
17062               sign, day, hour, minute, second);
17063     else if (hour > 0)
17064       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
17065     else
17066       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
17067
17068     return buf;
17069 }
17070
17071
17072 /*
17073  * This is necessary because some C libraries aren't ANSI C compliant yet.
17074  */
17075 char *
17076 StrStr (char *string, char *match)
17077 {
17078     int i, length;
17079
17080     length = strlen(match);
17081
17082     for (i = strlen(string) - length; i >= 0; i--, string++)
17083       if (!strncmp(match, string, length))
17084         return string;
17085
17086     return NULL;
17087 }
17088
17089 char *
17090 StrCaseStr (char *string, char *match)
17091 {
17092     int i, j, length;
17093
17094     length = strlen(match);
17095
17096     for (i = strlen(string) - length; i >= 0; i--, string++) {
17097         for (j = 0; j < length; j++) {
17098             if (ToLower(match[j]) != ToLower(string[j]))
17099               break;
17100         }
17101         if (j == length) return string;
17102     }
17103
17104     return NULL;
17105 }
17106
17107 #ifndef _amigados
17108 int
17109 StrCaseCmp (char *s1, char *s2)
17110 {
17111     char c1, c2;
17112
17113     for (;;) {
17114         c1 = ToLower(*s1++);
17115         c2 = ToLower(*s2++);
17116         if (c1 > c2) return 1;
17117         if (c1 < c2) return -1;
17118         if (c1 == NULLCHAR) return 0;
17119     }
17120 }
17121
17122
17123 int
17124 ToLower (int c)
17125 {
17126     return isupper(c) ? tolower(c) : c;
17127 }
17128
17129
17130 int
17131 ToUpper (int c)
17132 {
17133     return islower(c) ? toupper(c) : c;
17134 }
17135 #endif /* !_amigados    */
17136
17137 char *
17138 StrSave (char *s)
17139 {
17140   char *ret;
17141
17142   if ((ret = (char *) malloc(strlen(s) + 1)))
17143     {
17144       safeStrCpy(ret, s, strlen(s)+1);
17145     }
17146   return ret;
17147 }
17148
17149 char *
17150 StrSavePtr (char *s, char **savePtr)
17151 {
17152     if (*savePtr) {
17153         free(*savePtr);
17154     }
17155     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
17156       safeStrCpy(*savePtr, s, strlen(s)+1);
17157     }
17158     return(*savePtr);
17159 }
17160
17161 char *
17162 PGNDate ()
17163 {
17164     time_t clock;
17165     struct tm *tm;
17166     char buf[MSG_SIZ];
17167
17168     clock = time((time_t *)NULL);
17169     tm = localtime(&clock);
17170     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
17171             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
17172     return StrSave(buf);
17173 }
17174
17175
17176 char *
17177 PositionToFEN (int move, char *overrideCastling, int moveCounts)
17178 {
17179     int i, j, fromX, fromY, toX, toY;
17180     int whiteToPlay;
17181     char buf[MSG_SIZ];
17182     char *p, *q;
17183     int emptycount;
17184     ChessSquare piece;
17185
17186     whiteToPlay = (gameMode == EditPosition) ?
17187       !blackPlaysFirst : (move % 2 == 0);
17188     p = buf;
17189
17190     /* Piece placement data */
17191     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17192         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
17193         emptycount = 0;
17194         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
17195             if (boards[move][i][j] == EmptySquare) {
17196                 emptycount++;
17197             } else { ChessSquare piece = boards[move][i][j];
17198                 if (emptycount > 0) {
17199                     if(emptycount<10) /* [HGM] can be >= 10 */
17200                         *p++ = '0' + emptycount;
17201                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17202                     emptycount = 0;
17203                 }
17204                 if(PieceToChar(piece) == '+') {
17205                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
17206                     *p++ = '+';
17207                     piece = (ChessSquare)(DEMOTED piece);
17208                 }
17209                 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
17210                 if(p[-1] == '~') {
17211                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
17212                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
17213                     *p++ = '~';
17214                 }
17215             }
17216         }
17217         if (emptycount > 0) {
17218             if(emptycount<10) /* [HGM] can be >= 10 */
17219                 *p++ = '0' + emptycount;
17220             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17221             emptycount = 0;
17222         }
17223         *p++ = '/';
17224     }
17225     *(p - 1) = ' ';
17226
17227     /* [HGM] print Crazyhouse or Shogi holdings */
17228     if( gameInfo.holdingsWidth ) {
17229         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
17230         q = p;
17231         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
17232             piece = boards[move][i][BOARD_WIDTH-1];
17233             if( piece != EmptySquare )
17234               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
17235                   *p++ = PieceToChar(piece);
17236         }
17237         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
17238             piece = boards[move][BOARD_HEIGHT-i-1][0];
17239             if( piece != EmptySquare )
17240               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
17241                   *p++ = PieceToChar(piece);
17242         }
17243
17244         if( q == p ) *p++ = '-';
17245         *p++ = ']';
17246         *p++ = ' ';
17247     }
17248
17249     /* Active color */
17250     *p++ = whiteToPlay ? 'w' : 'b';
17251     *p++ = ' ';
17252
17253   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
17254     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
17255   } else {
17256   if(nrCastlingRights) {
17257      q = p;
17258      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
17259        /* [HGM] write directly from rights */
17260            if(boards[move][CASTLING][2] != NoRights &&
17261               boards[move][CASTLING][0] != NoRights   )
17262                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
17263            if(boards[move][CASTLING][2] != NoRights &&
17264               boards[move][CASTLING][1] != NoRights   )
17265                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
17266            if(boards[move][CASTLING][5] != NoRights &&
17267               boards[move][CASTLING][3] != NoRights   )
17268                 *p++ = boards[move][CASTLING][3] + AAA;
17269            if(boards[move][CASTLING][5] != NoRights &&
17270               boards[move][CASTLING][4] != NoRights   )
17271                 *p++ = boards[move][CASTLING][4] + AAA;
17272      } else {
17273
17274         /* [HGM] write true castling rights */
17275         if( nrCastlingRights == 6 ) {
17276             int q, k=0;
17277             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
17278                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
17279             q = (boards[move][CASTLING][1] == BOARD_LEFT &&
17280                  boards[move][CASTLING][2] != NoRights  );
17281             if(gameInfo.variant == VariantSChess) { // for S-Chess, indicate all vrgin backrank pieces
17282                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_RGHT]; // count white held pieces
17283                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17284                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
17285                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
17286             }
17287             if(q) *p++ = 'Q';
17288             k = 0;
17289             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
17290                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
17291             q = (boards[move][CASTLING][4] == BOARD_LEFT &&
17292                  boards[move][CASTLING][5] != NoRights  );
17293             if(gameInfo.variant == VariantSChess) {
17294                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_LEFT-1]; // count black held pieces
17295                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17296                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
17297                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17298             }
17299             if(q) *p++ = 'q';
17300         }
17301      }
17302      if (q == p) *p++ = '-'; /* No castling rights */
17303      *p++ = ' ';
17304   }
17305
17306   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17307      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17308      gameInfo.variant != VariantMakruk   && gameInfo.variant != VariantASEAN ) {
17309     /* En passant target square */
17310     if (move > backwardMostMove) {
17311         fromX = moveList[move - 1][0] - AAA;
17312         fromY = moveList[move - 1][1] - ONE;
17313         toX = moveList[move - 1][2] - AAA;
17314         toY = moveList[move - 1][3] - ONE;
17315         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
17316             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
17317             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
17318             fromX == toX) {
17319             /* 2-square pawn move just happened */
17320             *p++ = toX + AAA;
17321             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17322         } else {
17323             *p++ = '-';
17324         }
17325     } else if(move == backwardMostMove) {
17326         // [HGM] perhaps we should always do it like this, and forget the above?
17327         if((signed char)boards[move][EP_STATUS] >= 0) {
17328             *p++ = boards[move][EP_STATUS] + AAA;
17329             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17330         } else {
17331             *p++ = '-';
17332         }
17333     } else {
17334         *p++ = '-';
17335     }
17336     *p++ = ' ';
17337   }
17338   }
17339
17340     if(moveCounts)
17341     {   int i = 0, j=move;
17342
17343         /* [HGM] find reversible plies */
17344         if (appData.debugMode) { int k;
17345             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
17346             for(k=backwardMostMove; k<=forwardMostMove; k++)
17347                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
17348
17349         }
17350
17351         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
17352         if( j == backwardMostMove ) i += initialRulePlies;
17353         sprintf(p, "%d ", i);
17354         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
17355
17356         /* Fullmove number */
17357         sprintf(p, "%d", (move / 2) + 1);
17358     } else *--p = NULLCHAR;
17359
17360     return StrSave(buf);
17361 }
17362
17363 Boolean
17364 ParseFEN (Board board, int *blackPlaysFirst, char *fen)
17365 {
17366     int i, j;
17367     char *p, c;
17368     int emptycount, virgin[BOARD_FILES];
17369     ChessSquare piece;
17370
17371     p = fen;
17372
17373     /* [HGM] by default clear Crazyhouse holdings, if present */
17374     if(gameInfo.holdingsWidth) {
17375        for(i=0; i<BOARD_HEIGHT; i++) {
17376            board[i][0]             = EmptySquare; /* black holdings */
17377            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
17378            board[i][1]             = (ChessSquare) 0; /* black counts */
17379            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
17380        }
17381     }
17382
17383     /* Piece placement data */
17384     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17385         j = 0;
17386         for (;;) {
17387             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
17388                 if (*p == '/') p++;
17389                 emptycount = gameInfo.boardWidth - j;
17390                 while (emptycount--)
17391                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17392                 break;
17393 #if(BOARD_FILES >= 10)
17394             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
17395                 p++; emptycount=10;
17396                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17397                 while (emptycount--)
17398                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17399 #endif
17400             } else if (*p == '*') {
17401                 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
17402             } else if (isdigit(*p)) {
17403                 emptycount = *p++ - '0';
17404                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
17405                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17406                 while (emptycount--)
17407                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17408             } else if (*p == '+' || isalpha(*p)) {
17409                 if (j >= gameInfo.boardWidth) return FALSE;
17410                 if(*p=='+') {
17411                     piece = CharToPiece(*++p);
17412                     if(piece == EmptySquare) return FALSE; /* unknown piece */
17413                     piece = (ChessSquare) (PROMOTED piece ); p++;
17414                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
17415                 } else piece = CharToPiece(*p++);
17416
17417                 if(piece==EmptySquare) return FALSE; /* unknown piece */
17418                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
17419                     piece = (ChessSquare) (PROMOTED piece);
17420                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
17421                     p++;
17422                 }
17423                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
17424             } else {
17425                 return FALSE;
17426             }
17427         }
17428     }
17429     while (*p == '/' || *p == ' ') p++;
17430
17431     /* [HGM] look for Crazyhouse holdings here */
17432     while(*p==' ') p++;
17433     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
17434         if(*p == '[') p++;
17435         if(*p == '-' ) p++; /* empty holdings */ else {
17436             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
17437             /* if we would allow FEN reading to set board size, we would   */
17438             /* have to add holdings and shift the board read so far here   */
17439             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
17440                 p++;
17441                 if((int) piece >= (int) BlackPawn ) {
17442                     i = (int)piece - (int)BlackPawn;
17443                     i = PieceToNumber((ChessSquare)i);
17444                     if( i >= gameInfo.holdingsSize ) return FALSE;
17445                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
17446                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
17447                 } else {
17448                     i = (int)piece - (int)WhitePawn;
17449                     i = PieceToNumber((ChessSquare)i);
17450                     if( i >= gameInfo.holdingsSize ) return FALSE;
17451                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
17452                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
17453                 }
17454             }
17455         }
17456         if(*p == ']') p++;
17457     }
17458
17459     while(*p == ' ') p++;
17460
17461     /* Active color */
17462     c = *p++;
17463     if(appData.colorNickNames) {
17464       if( c == appData.colorNickNames[0] ) c = 'w'; else
17465       if( c == appData.colorNickNames[1] ) c = 'b';
17466     }
17467     switch (c) {
17468       case 'w':
17469         *blackPlaysFirst = FALSE;
17470         break;
17471       case 'b':
17472         *blackPlaysFirst = TRUE;
17473         break;
17474       default:
17475         return FALSE;
17476     }
17477
17478     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
17479     /* return the extra info in global variiables             */
17480
17481     /* set defaults in case FEN is incomplete */
17482     board[EP_STATUS] = EP_UNKNOWN;
17483     for(i=0; i<nrCastlingRights; i++ ) {
17484         board[CASTLING][i] =
17485             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
17486     }   /* assume possible unless obviously impossible */
17487     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
17488     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
17489     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
17490                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
17491     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
17492     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
17493     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
17494                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
17495     FENrulePlies = 0;
17496
17497     while(*p==' ') p++;
17498     if(nrCastlingRights) {
17499       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
17500       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
17501           /* castling indicator present, so default becomes no castlings */
17502           for(i=0; i<nrCastlingRights; i++ ) {
17503                  board[CASTLING][i] = NoRights;
17504           }
17505       }
17506       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
17507              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess) &&
17508              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
17509              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
17510         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
17511
17512         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
17513             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
17514             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
17515         }
17516         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
17517             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
17518         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
17519                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
17520         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
17521                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
17522         switch(c) {
17523           case'K':
17524               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
17525               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
17526               board[CASTLING][2] = whiteKingFile;
17527               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
17528               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17529               break;
17530           case'Q':
17531               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
17532               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
17533               board[CASTLING][2] = whiteKingFile;
17534               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
17535               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17536               break;
17537           case'k':
17538               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
17539               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
17540               board[CASTLING][5] = blackKingFile;
17541               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
17542               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17543               break;
17544           case'q':
17545               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
17546               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
17547               board[CASTLING][5] = blackKingFile;
17548               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
17549               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17550           case '-':
17551               break;
17552           default: /* FRC castlings */
17553               if(c >= 'a') { /* black rights */
17554                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
17555                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17556                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
17557                   if(i == BOARD_RGHT) break;
17558                   board[CASTLING][5] = i;
17559                   c -= AAA;
17560                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
17561                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
17562                   if(c > i)
17563                       board[CASTLING][3] = c;
17564                   else
17565                       board[CASTLING][4] = c;
17566               } else { /* white rights */
17567                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
17568                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17569                     if(board[0][i] == WhiteKing) break;
17570                   if(i == BOARD_RGHT) break;
17571                   board[CASTLING][2] = i;
17572                   c -= AAA - 'a' + 'A';
17573                   if(board[0][c] >= WhiteKing) break;
17574                   if(c > i)
17575                       board[CASTLING][0] = c;
17576                   else
17577                       board[CASTLING][1] = c;
17578               }
17579         }
17580       }
17581       for(i=0; i<nrCastlingRights; i++)
17582         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
17583       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = virgin[i];
17584     if (appData.debugMode) {
17585         fprintf(debugFP, "FEN castling rights:");
17586         for(i=0; i<nrCastlingRights; i++)
17587         fprintf(debugFP, " %d", board[CASTLING][i]);
17588         fprintf(debugFP, "\n");
17589     }
17590
17591       while(*p==' ') p++;
17592     }
17593
17594     /* read e.p. field in games that know e.p. capture */
17595     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17596        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17597        gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
17598       if(*p=='-') {
17599         p++; board[EP_STATUS] = EP_NONE;
17600       } else {
17601          char c = *p++ - AAA;
17602
17603          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
17604          if(*p >= '0' && *p <='9') p++;
17605          board[EP_STATUS] = c;
17606       }
17607     }
17608
17609
17610     if(sscanf(p, "%d", &i) == 1) {
17611         FENrulePlies = i; /* 50-move ply counter */
17612         /* (The move number is still ignored)    */
17613     }
17614
17615     return TRUE;
17616 }
17617
17618 void
17619 EditPositionPasteFEN (char *fen)
17620 {
17621   if (fen != NULL) {
17622     Board initial_position;
17623
17624     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
17625       DisplayError(_("Bad FEN position in clipboard"), 0);
17626       return ;
17627     } else {
17628       int savedBlackPlaysFirst = blackPlaysFirst;
17629       EditPositionEvent();
17630       blackPlaysFirst = savedBlackPlaysFirst;
17631       CopyBoard(boards[0], initial_position);
17632       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
17633       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
17634       DisplayBothClocks();
17635       DrawPosition(FALSE, boards[currentMove]);
17636     }
17637   }
17638 }
17639
17640 static char cseq[12] = "\\   ";
17641
17642 Boolean
17643 set_cont_sequence (char *new_seq)
17644 {
17645     int len;
17646     Boolean ret;
17647
17648     // handle bad attempts to set the sequence
17649         if (!new_seq)
17650                 return 0; // acceptable error - no debug
17651
17652     len = strlen(new_seq);
17653     ret = (len > 0) && (len < sizeof(cseq));
17654     if (ret)
17655       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
17656     else if (appData.debugMode)
17657       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
17658     return ret;
17659 }
17660
17661 /*
17662     reformat a source message so words don't cross the width boundary.  internal
17663     newlines are not removed.  returns the wrapped size (no null character unless
17664     included in source message).  If dest is NULL, only calculate the size required
17665     for the dest buffer.  lp argument indicats line position upon entry, and it's
17666     passed back upon exit.
17667 */
17668 int
17669 wrap (char *dest, char *src, int count, int width, int *lp)
17670 {
17671     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
17672
17673     cseq_len = strlen(cseq);
17674     old_line = line = *lp;
17675     ansi = len = clen = 0;
17676
17677     for (i=0; i < count; i++)
17678     {
17679         if (src[i] == '\033')
17680             ansi = 1;
17681
17682         // if we hit the width, back up
17683         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
17684         {
17685             // store i & len in case the word is too long
17686             old_i = i, old_len = len;
17687
17688             // find the end of the last word
17689             while (i && src[i] != ' ' && src[i] != '\n')
17690             {
17691                 i--;
17692                 len--;
17693             }
17694
17695             // word too long?  restore i & len before splitting it
17696             if ((old_i-i+clen) >= width)
17697             {
17698                 i = old_i;
17699                 len = old_len;
17700             }
17701
17702             // extra space?
17703             if (i && src[i-1] == ' ')
17704                 len--;
17705
17706             if (src[i] != ' ' && src[i] != '\n')
17707             {
17708                 i--;
17709                 if (len)
17710                     len--;
17711             }
17712
17713             // now append the newline and continuation sequence
17714             if (dest)
17715                 dest[len] = '\n';
17716             len++;
17717             if (dest)
17718                 strncpy(dest+len, cseq, cseq_len);
17719             len += cseq_len;
17720             line = cseq_len;
17721             clen = cseq_len;
17722             continue;
17723         }
17724
17725         if (dest)
17726             dest[len] = src[i];
17727         len++;
17728         if (!ansi)
17729             line++;
17730         if (src[i] == '\n')
17731             line = 0;
17732         if (src[i] == 'm')
17733             ansi = 0;
17734     }
17735     if (dest && appData.debugMode)
17736     {
17737         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
17738             count, width, line, len, *lp);
17739         show_bytes(debugFP, src, count);
17740         fprintf(debugFP, "\ndest: ");
17741         show_bytes(debugFP, dest, len);
17742         fprintf(debugFP, "\n");
17743     }
17744     *lp = dest ? line : old_line;
17745
17746     return len;
17747 }
17748
17749 // [HGM] vari: routines for shelving variations
17750 Boolean modeRestore = FALSE;
17751
17752 void
17753 PushInner (int firstMove, int lastMove)
17754 {
17755         int i, j, nrMoves = lastMove - firstMove;
17756
17757         // push current tail of game on stack
17758         savedResult[storedGames] = gameInfo.result;
17759         savedDetails[storedGames] = gameInfo.resultDetails;
17760         gameInfo.resultDetails = NULL;
17761         savedFirst[storedGames] = firstMove;
17762         savedLast [storedGames] = lastMove;
17763         savedFramePtr[storedGames] = framePtr;
17764         framePtr -= nrMoves; // reserve space for the boards
17765         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
17766             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
17767             for(j=0; j<MOVE_LEN; j++)
17768                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
17769             for(j=0; j<2*MOVE_LEN; j++)
17770                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
17771             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
17772             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
17773             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
17774             pvInfoList[firstMove+i-1].depth = 0;
17775             commentList[framePtr+i] = commentList[firstMove+i];
17776             commentList[firstMove+i] = NULL;
17777         }
17778
17779         storedGames++;
17780         forwardMostMove = firstMove; // truncate game so we can start variation
17781 }
17782
17783 void
17784 PushTail (int firstMove, int lastMove)
17785 {
17786         if(appData.icsActive) { // only in local mode
17787                 forwardMostMove = currentMove; // mimic old ICS behavior
17788                 return;
17789         }
17790         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
17791
17792         PushInner(firstMove, lastMove);
17793         if(storedGames == 1) GreyRevert(FALSE);
17794         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
17795 }
17796
17797 void
17798 PopInner (Boolean annotate)
17799 {
17800         int i, j, nrMoves;
17801         char buf[8000], moveBuf[20];
17802
17803         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
17804         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
17805         nrMoves = savedLast[storedGames] - currentMove;
17806         if(annotate) {
17807                 int cnt = 10;
17808                 if(!WhiteOnMove(currentMove))
17809                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
17810                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
17811                 for(i=currentMove; i<forwardMostMove; i++) {
17812                         if(WhiteOnMove(i))
17813                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
17814                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
17815                         strcat(buf, moveBuf);
17816                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
17817                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
17818                 }
17819                 strcat(buf, ")");
17820         }
17821         for(i=1; i<=nrMoves; i++) { // copy last variation back
17822             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
17823             for(j=0; j<MOVE_LEN; j++)
17824                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
17825             for(j=0; j<2*MOVE_LEN; j++)
17826                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
17827             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
17828             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
17829             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
17830             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
17831             commentList[currentMove+i] = commentList[framePtr+i];
17832             commentList[framePtr+i] = NULL;
17833         }
17834         if(annotate) AppendComment(currentMove+1, buf, FALSE);
17835         framePtr = savedFramePtr[storedGames];
17836         gameInfo.result = savedResult[storedGames];
17837         if(gameInfo.resultDetails != NULL) {
17838             free(gameInfo.resultDetails);
17839       }
17840         gameInfo.resultDetails = savedDetails[storedGames];
17841         forwardMostMove = currentMove + nrMoves;
17842 }
17843
17844 Boolean
17845 PopTail (Boolean annotate)
17846 {
17847         if(appData.icsActive) return FALSE; // only in local mode
17848         if(!storedGames) return FALSE; // sanity
17849         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
17850
17851         PopInner(annotate);
17852         if(currentMove < forwardMostMove) ForwardEvent(); else
17853         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
17854
17855         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
17856         return TRUE;
17857 }
17858
17859 void
17860 CleanupTail ()
17861 {       // remove all shelved variations
17862         int i;
17863         for(i=0; i<storedGames; i++) {
17864             if(savedDetails[i])
17865                 free(savedDetails[i]);
17866             savedDetails[i] = NULL;
17867         }
17868         for(i=framePtr; i<MAX_MOVES; i++) {
17869                 if(commentList[i]) free(commentList[i]);
17870                 commentList[i] = NULL;
17871         }
17872         framePtr = MAX_MOVES-1;
17873         storedGames = 0;
17874 }
17875
17876 void
17877 LoadVariation (int index, char *text)
17878 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
17879         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
17880         int level = 0, move;
17881
17882         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
17883         // first find outermost bracketing variation
17884         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
17885             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
17886                 if(*p == '{') wait = '}'; else
17887                 if(*p == '[') wait = ']'; else
17888                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
17889                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
17890             }
17891             if(*p == wait) wait = NULLCHAR; // closing ]} found
17892             p++;
17893         }
17894         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
17895         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
17896         end[1] = NULLCHAR; // clip off comment beyond variation
17897         ToNrEvent(currentMove-1);
17898         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
17899         // kludge: use ParsePV() to append variation to game
17900         move = currentMove;
17901         ParsePV(start, TRUE, TRUE);
17902         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
17903         ClearPremoveHighlights();
17904         CommentPopDown();
17905         ToNrEvent(currentMove+1);
17906 }
17907
17908 void
17909 LoadTheme ()
17910 {
17911     char *p, *q, buf[MSG_SIZ];
17912     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
17913         snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
17914         ParseArgsFromString(buf);
17915         ActivateTheme(TRUE); // also redo colors
17916         return;
17917     }
17918     p = nickName;
17919     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
17920     {
17921         int len;
17922         q = appData.themeNames;
17923         snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
17924       if(appData.useBitmaps) {
17925         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
17926                 appData.liteBackTextureFile, appData.darkBackTextureFile,
17927                 appData.liteBackTextureMode,
17928                 appData.darkBackTextureMode );
17929       } else {
17930         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
17931                 Col2Text(2),   // lightSquareColor
17932                 Col2Text(3) ); // darkSquareColor
17933       }
17934       if(appData.useBorder) {
17935         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
17936                 appData.border);
17937       } else {
17938         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
17939       }
17940       if(appData.useFont) {
17941         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
17942                 appData.renderPiecesWithFont,
17943                 appData.fontToPieceTable,
17944                 Col2Text(9),    // appData.fontBackColorWhite
17945                 Col2Text(10) ); // appData.fontForeColorBlack
17946       } else {
17947         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
17948                 appData.pieceDirectory);
17949         if(!appData.pieceDirectory[0])
17950           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
17951                 Col2Text(0),   // whitePieceColor
17952                 Col2Text(1) ); // blackPieceColor
17953       }
17954       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
17955                 Col2Text(4),   // highlightSquareColor
17956                 Col2Text(5) ); // premoveHighlightColor
17957         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
17958         if(insert != q) insert[-1] = NULLCHAR;
17959         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
17960         if(q)   free(q);
17961     }
17962     ActivateTheme(FALSE);
17963 }