4633377bf5b26ad51427b0edf9a4b29adfc15891
[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], buf3[MSG_SIZ], jar;
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     jar = (strstr(p, ".jar") == p + strlen(p) - 4);
953     if(params[0]) {
954         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
955         snprintf(command, MSG_SIZ, "%s %s", p, params);
956         p = command;
957     }
958     if(jar) { snprintf(buf3, MSG_SIZ, "java -jar %s", p); p = buf3; }
959     ASSIGN(appData.chessProgram[i], p);
960     appData.isUCI[i] = isUCI;
961     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
962     appData.hasOwnBookUCI[i] = hasBook;
963     if(!nickName[0]) useNick = FALSE;
964     if(useNick) ASSIGN(appData.pgnName[i], nickName);
965     if(addToList) {
966         int len;
967         char quote;
968         q = firstChessProgramNames;
969         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
970         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
971         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
972                         quote, p, quote, appData.directory[i],
973                         useNick ? " -fn \"" : "",
974                         useNick ? nickName : "",
975                         useNick ? "\"" : "",
976                         v1 ? " -firstProtocolVersion 1" : "",
977                         hasBook ? "" : " -fNoOwnBookUCI",
978                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
979                         storeVariant ? " -variant " : "",
980                         storeVariant ? VariantName(gameInfo.variant) : "");
981         if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
982         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
983         if(insert != q) insert[-1] = NULLCHAR;
984         snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
985         if(q)   free(q);
986         FloatToFront(&appData.recentEngineList, buf);
987     }
988     ReplaceEngine(cps, i);
989 }
990
991 void
992 InitTimeControls ()
993 {
994     int matched, min, sec;
995     /*
996      * Parse timeControl resource
997      */
998     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
999                           appData.movesPerSession)) {
1000         char buf[MSG_SIZ];
1001         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
1002         DisplayFatalError(buf, 0, 2);
1003     }
1004
1005     /*
1006      * Parse searchTime resource
1007      */
1008     if (*appData.searchTime != NULLCHAR) {
1009         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
1010         if (matched == 1) {
1011             searchTime = min * 60;
1012         } else if (matched == 2) {
1013             searchTime = min * 60 + sec;
1014         } else {
1015             char buf[MSG_SIZ];
1016             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
1017             DisplayFatalError(buf, 0, 2);
1018         }
1019     }
1020 }
1021
1022 void
1023 InitBackEnd1 ()
1024 {
1025
1026     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1027     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1028
1029     GetTimeMark(&programStartTime);
1030     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1031     appData.seedBase = random() + (random()<<15);
1032     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1033
1034     ClearProgramStats();
1035     programStats.ok_to_send = 1;
1036     programStats.seen_stat = 0;
1037
1038     /*
1039      * Initialize game list
1040      */
1041     ListNew(&gameList);
1042
1043
1044     /*
1045      * Internet chess server status
1046      */
1047     if (appData.icsActive) {
1048         appData.matchMode = FALSE;
1049         appData.matchGames = 0;
1050 #if ZIPPY
1051         appData.noChessProgram = !appData.zippyPlay;
1052 #else
1053         appData.zippyPlay = FALSE;
1054         appData.zippyTalk = FALSE;
1055         appData.noChessProgram = TRUE;
1056 #endif
1057         if (*appData.icsHelper != NULLCHAR) {
1058             appData.useTelnet = TRUE;
1059             appData.telnetProgram = appData.icsHelper;
1060         }
1061     } else {
1062         appData.zippyTalk = appData.zippyPlay = FALSE;
1063     }
1064
1065     /* [AS] Initialize pv info list [HGM] and game state */
1066     {
1067         int i, j;
1068
1069         for( i=0; i<=framePtr; i++ ) {
1070             pvInfoList[i].depth = -1;
1071             boards[i][EP_STATUS] = EP_NONE;
1072             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1073         }
1074     }
1075
1076     InitTimeControls();
1077
1078     /* [AS] Adjudication threshold */
1079     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1080
1081     InitEngine(&first, 0);
1082     InitEngine(&second, 1);
1083     CommonEngineInit();
1084
1085     pairing.which = "pairing"; // pairing engine
1086     pairing.pr = NoProc;
1087     pairing.isr = NULL;
1088     pairing.program = appData.pairingEngine;
1089     pairing.host = "localhost";
1090     pairing.dir = ".";
1091
1092     if (appData.icsActive) {
1093         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1094     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1095         appData.clockMode = FALSE;
1096         first.sendTime = second.sendTime = 0;
1097     }
1098
1099 #if ZIPPY
1100     /* Override some settings from environment variables, for backward
1101        compatibility.  Unfortunately it's not feasible to have the env
1102        vars just set defaults, at least in xboard.  Ugh.
1103     */
1104     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1105       ZippyInit();
1106     }
1107 #endif
1108
1109     if (!appData.icsActive) {
1110       char buf[MSG_SIZ];
1111       int len;
1112
1113       /* Check for variants that are supported only in ICS mode,
1114          or not at all.  Some that are accepted here nevertheless
1115          have bugs; see comments below.
1116       */
1117       VariantClass variant = StringToVariant(appData.variant);
1118       switch (variant) {
1119       case VariantBughouse:     /* need four players and two boards */
1120       case VariantKriegspiel:   /* need to hide pieces and move details */
1121         /* case VariantFischeRandom: (Fabien: moved below) */
1122         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1123         if( (len >= MSG_SIZ) && appData.debugMode )
1124           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1125
1126         DisplayFatalError(buf, 0, 2);
1127         return;
1128
1129       case VariantUnknown:
1130       case VariantLoadable:
1131       case Variant29:
1132       case Variant30:
1133       case Variant31:
1134       case Variant32:
1135       case Variant33:
1136       case Variant34:
1137       case Variant35:
1138       case Variant36:
1139       default:
1140         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1141         if( (len >= MSG_SIZ) && appData.debugMode )
1142           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1143
1144         DisplayFatalError(buf, 0, 2);
1145         return;
1146
1147       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1148       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1149       case VariantGothic:     /* [HGM] should work */
1150       case VariantCapablanca: /* [HGM] should work */
1151       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1152       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1153       case VariantKnightmate: /* [HGM] should work */
1154       case VariantCylinder:   /* [HGM] untested */
1155       case VariantFalcon:     /* [HGM] untested */
1156       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1157                                  offboard interposition not understood */
1158       case VariantNormal:     /* definitely works! */
1159       case VariantWildCastle: /* pieces not automatically shuffled */
1160       case VariantNoCastle:   /* pieces not automatically shuffled */
1161       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1162       case VariantLosers:     /* should work except for win condition,
1163                                  and doesn't know captures are mandatory */
1164       case VariantSuicide:    /* should work except for win condition,
1165                                  and doesn't know captures are mandatory */
1166       case VariantGiveaway:   /* should work except for win condition,
1167                                  and doesn't know captures are mandatory */
1168       case VariantTwoKings:   /* should work */
1169       case VariantAtomic:     /* should work except for win condition */
1170       case Variant3Check:     /* should work except for win condition */
1171       case VariantShatranj:   /* should work except for all win conditions */
1172       case VariantMakruk:     /* should work except for draw countdown */
1173       case VariantASEAN :     /* should work except for draw countdown */
1174       case VariantBerolina:   /* might work if TestLegality is off */
1175       case VariantCapaRandom: /* should work */
1176       case VariantJanus:      /* should work */
1177       case VariantSuper:      /* experimental */
1178       case VariantGreat:      /* experimental, requires legality testing to be off */
1179       case VariantSChess:     /* S-Chess, should work */
1180       case VariantGrand:      /* should work */
1181       case VariantSpartan:    /* should work */
1182         break;
1183       }
1184     }
1185
1186 }
1187
1188 int
1189 NextIntegerFromString (char ** str, long * value)
1190 {
1191     int result = -1;
1192     char * s = *str;
1193
1194     while( *s == ' ' || *s == '\t' ) {
1195         s++;
1196     }
1197
1198     *value = 0;
1199
1200     if( *s >= '0' && *s <= '9' ) {
1201         while( *s >= '0' && *s <= '9' ) {
1202             *value = *value * 10 + (*s - '0');
1203             s++;
1204         }
1205
1206         result = 0;
1207     }
1208
1209     *str = s;
1210
1211     return result;
1212 }
1213
1214 int
1215 NextTimeControlFromString (char ** str, long * value)
1216 {
1217     long temp;
1218     int result = NextIntegerFromString( str, &temp );
1219
1220     if( result == 0 ) {
1221         *value = temp * 60; /* Minutes */
1222         if( **str == ':' ) {
1223             (*str)++;
1224             result = NextIntegerFromString( str, &temp );
1225             *value += temp; /* Seconds */
1226         }
1227     }
1228
1229     return result;
1230 }
1231
1232 int
1233 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1234 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1235     int result = -1, type = 0; long temp, temp2;
1236
1237     if(**str != ':') return -1; // old params remain in force!
1238     (*str)++;
1239     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1240     if( NextIntegerFromString( str, &temp ) ) return -1;
1241     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1242
1243     if(**str != '/') {
1244         /* time only: incremental or sudden-death time control */
1245         if(**str == '+') { /* increment follows; read it */
1246             (*str)++;
1247             if(**str == '!') type = *(*str)++; // Bronstein TC
1248             if(result = NextIntegerFromString( str, &temp2)) return -1;
1249             *inc = temp2 * 1000;
1250             if(**str == '.') { // read fraction of increment
1251                 char *start = ++(*str);
1252                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1253                 temp2 *= 1000;
1254                 while(start++ < *str) temp2 /= 10;
1255                 *inc += temp2;
1256             }
1257         } else *inc = 0;
1258         *moves = 0; *tc = temp * 1000; *incType = type;
1259         return 0;
1260     }
1261
1262     (*str)++; /* classical time control */
1263     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1264
1265     if(result == 0) {
1266         *moves = temp;
1267         *tc    = temp2 * 1000;
1268         *inc   = 0;
1269         *incType = type;
1270     }
1271     return result;
1272 }
1273
1274 int
1275 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1276 {   /* [HGM] get time to add from the multi-session time-control string */
1277     int incType, moves=1; /* kludge to force reading of first session */
1278     long time, increment;
1279     char *s = tcString;
1280
1281     if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1282     do {
1283         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1284         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1285         if(movenr == -1) return time;    /* last move before new session     */
1286         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1287         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1288         if(!moves) return increment;     /* current session is incremental   */
1289         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1290     } while(movenr >= -1);               /* try again for next session       */
1291
1292     return 0; // no new time quota on this move
1293 }
1294
1295 int
1296 ParseTimeControl (char *tc, float ti, int mps)
1297 {
1298   long tc1;
1299   long tc2;
1300   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1301   int min, sec=0;
1302
1303   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1304   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1305       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1306   if(ti > 0) {
1307
1308     if(mps)
1309       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1310     else
1311       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1312   } else {
1313     if(mps)
1314       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1315     else
1316       snprintf(buf, MSG_SIZ, ":%s", mytc);
1317   }
1318   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1319
1320   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1321     return FALSE;
1322   }
1323
1324   if( *tc == '/' ) {
1325     /* Parse second time control */
1326     tc++;
1327
1328     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1329       return FALSE;
1330     }
1331
1332     if( tc2 == 0 ) {
1333       return FALSE;
1334     }
1335
1336     timeControl_2 = tc2 * 1000;
1337   }
1338   else {
1339     timeControl_2 = 0;
1340   }
1341
1342   if( tc1 == 0 ) {
1343     return FALSE;
1344   }
1345
1346   timeControl = tc1 * 1000;
1347
1348   if (ti >= 0) {
1349     timeIncrement = ti * 1000;  /* convert to ms */
1350     movesPerSession = 0;
1351   } else {
1352     timeIncrement = 0;
1353     movesPerSession = mps;
1354   }
1355   return TRUE;
1356 }
1357
1358 void
1359 InitBackEnd2 ()
1360 {
1361     if (appData.debugMode) {
1362 #    ifdef __GIT_VERSION
1363       fprintf(debugFP, "Version: %s (%s)\n", programVersion, __GIT_VERSION);
1364 #    else
1365       fprintf(debugFP, "Version: %s\n", programVersion);
1366 #    endif
1367     }
1368     ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1369
1370     set_cont_sequence(appData.wrapContSeq);
1371     if (appData.matchGames > 0) {
1372         appData.matchMode = TRUE;
1373     } else if (appData.matchMode) {
1374         appData.matchGames = 1;
1375     }
1376     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1377         appData.matchGames = appData.sameColorGames;
1378     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1379         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1380         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1381     }
1382     Reset(TRUE, FALSE);
1383     if (appData.noChessProgram || first.protocolVersion == 1) {
1384       InitBackEnd3();
1385     } else {
1386       /* kludge: allow timeout for initial "feature" commands */
1387       FreezeUI();
1388       DisplayMessage("", _("Starting chess program"));
1389       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1390     }
1391 }
1392
1393 int
1394 CalculateIndex (int index, int gameNr)
1395 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1396     int res;
1397     if(index > 0) return index; // fixed nmber
1398     if(index == 0) return 1;
1399     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1400     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1401     return res;
1402 }
1403
1404 int
1405 LoadGameOrPosition (int gameNr)
1406 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1407     if (*appData.loadGameFile != NULLCHAR) {
1408         if (!LoadGameFromFile(appData.loadGameFile,
1409                 CalculateIndex(appData.loadGameIndex, gameNr),
1410                               appData.loadGameFile, FALSE)) {
1411             DisplayFatalError(_("Bad game file"), 0, 1);
1412             return 0;
1413         }
1414     } else if (*appData.loadPositionFile != NULLCHAR) {
1415         if (!LoadPositionFromFile(appData.loadPositionFile,
1416                 CalculateIndex(appData.loadPositionIndex, gameNr),
1417                                   appData.loadPositionFile)) {
1418             DisplayFatalError(_("Bad position file"), 0, 1);
1419             return 0;
1420         }
1421     }
1422     return 1;
1423 }
1424
1425 void
1426 ReserveGame (int gameNr, char resChar)
1427 {
1428     FILE *tf = fopen(appData.tourneyFile, "r+");
1429     char *p, *q, c, buf[MSG_SIZ];
1430     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1431     safeStrCpy(buf, lastMsg, MSG_SIZ);
1432     DisplayMessage(_("Pick new game"), "");
1433     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1434     ParseArgsFromFile(tf);
1435     p = q = appData.results;
1436     if(appData.debugMode) {
1437       char *r = appData.participants;
1438       fprintf(debugFP, "results = '%s'\n", p);
1439       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1440       fprintf(debugFP, "\n");
1441     }
1442     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1443     nextGame = q - p;
1444     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1445     safeStrCpy(q, p, strlen(p) + 2);
1446     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1447     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1448     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1449         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1450         q[nextGame] = '*';
1451     }
1452     fseek(tf, -(strlen(p)+4), SEEK_END);
1453     c = fgetc(tf);
1454     if(c != '"') // depending on DOS or Unix line endings we can be one off
1455          fseek(tf, -(strlen(p)+2), SEEK_END);
1456     else fseek(tf, -(strlen(p)+3), SEEK_END);
1457     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1458     DisplayMessage(buf, "");
1459     free(p); appData.results = q;
1460     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1461        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1462       int round = appData.defaultMatchGames * appData.tourneyType;
1463       if(gameNr < 0 || appData.tourneyType < 1 ||  // gauntlet engine can always stay loaded as first engine
1464          appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1465         UnloadEngine(&first);  // next game belongs to other pairing;
1466         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1467     }
1468     if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1469 }
1470
1471 void
1472 MatchEvent (int mode)
1473 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1474         int dummy;
1475         if(matchMode) { // already in match mode: switch it off
1476             abortMatch = TRUE;
1477             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1478             return;
1479         }
1480 //      if(gameMode != BeginningOfGame) {
1481 //          DisplayError(_("You can only start a match from the initial position."), 0);
1482 //          return;
1483 //      }
1484         abortMatch = FALSE;
1485         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1486         /* Set up machine vs. machine match */
1487         nextGame = 0;
1488         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1489         if(appData.tourneyFile[0]) {
1490             ReserveGame(-1, 0);
1491             if(nextGame > appData.matchGames) {
1492                 char buf[MSG_SIZ];
1493                 if(strchr(appData.results, '*') == NULL) {
1494                     FILE *f;
1495                     appData.tourneyCycles++;
1496                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1497                         fclose(f);
1498                         NextTourneyGame(-1, &dummy);
1499                         ReserveGame(-1, 0);
1500                         if(nextGame <= appData.matchGames) {
1501                             DisplayNote(_("You restarted an already completed tourney.\nOne more cycle will now be added to it.\nGames commence in 10 sec."));
1502                             matchMode = mode;
1503                             ScheduleDelayedEvent(NextMatchGame, 10000);
1504                             return;
1505                         }
1506                     }
1507                 }
1508                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1509                 DisplayError(buf, 0);
1510                 appData.tourneyFile[0] = 0;
1511                 return;
1512             }
1513         } else
1514         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1515             DisplayFatalError(_("Can't have a match with no chess programs"),
1516                               0, 2);
1517             return;
1518         }
1519         matchMode = mode;
1520         matchGame = roundNr = 1;
1521         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1522         NextMatchGame();
1523 }
1524
1525 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1526
1527 void
1528 InitBackEnd3 P((void))
1529 {
1530     GameMode initialMode;
1531     char buf[MSG_SIZ];
1532     int err, len;
1533
1534     InitChessProgram(&first, startedFromSetupPosition);
1535
1536     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1537         free(programVersion);
1538         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1539         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1540         FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1541     }
1542
1543     if (appData.icsActive) {
1544 #ifdef WIN32
1545         /* [DM] Make a console window if needed [HGM] merged ifs */
1546         ConsoleCreate();
1547 #endif
1548         err = establish();
1549         if (err != 0)
1550           {
1551             if (*appData.icsCommPort != NULLCHAR)
1552               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1553                              appData.icsCommPort);
1554             else
1555               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1556                         appData.icsHost, appData.icsPort);
1557
1558             if( (len >= MSG_SIZ) && appData.debugMode )
1559               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1560
1561             DisplayFatalError(buf, err, 1);
1562             return;
1563         }
1564         SetICSMode();
1565         telnetISR =
1566           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1567         fromUserISR =
1568           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1569         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1570             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1571     } else if (appData.noChessProgram) {
1572         SetNCPMode();
1573     } else {
1574         SetGNUMode();
1575     }
1576
1577     if (*appData.cmailGameName != NULLCHAR) {
1578         SetCmailMode();
1579         OpenLoopback(&cmailPR);
1580         cmailISR =
1581           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1582     }
1583
1584     ThawUI();
1585     DisplayMessage("", "");
1586     if (StrCaseCmp(appData.initialMode, "") == 0) {
1587       initialMode = BeginningOfGame;
1588       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1589         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1590         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1591         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1592         ModeHighlight();
1593       }
1594     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1595       initialMode = TwoMachinesPlay;
1596     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1597       initialMode = AnalyzeFile;
1598     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1599       initialMode = AnalyzeMode;
1600     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1601       initialMode = MachinePlaysWhite;
1602     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1603       initialMode = MachinePlaysBlack;
1604     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1605       initialMode = EditGame;
1606     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1607       initialMode = EditPosition;
1608     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1609       initialMode = Training;
1610     } else {
1611       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1612       if( (len >= MSG_SIZ) && appData.debugMode )
1613         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1614
1615       DisplayFatalError(buf, 0, 2);
1616       return;
1617     }
1618
1619     if (appData.matchMode) {
1620         if(appData.tourneyFile[0]) { // start tourney from command line
1621             FILE *f;
1622             if(f = fopen(appData.tourneyFile, "r")) {
1623                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1624                 fclose(f);
1625                 appData.clockMode = TRUE;
1626                 SetGNUMode();
1627             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1628         }
1629         MatchEvent(TRUE);
1630     } else if (*appData.cmailGameName != NULLCHAR) {
1631         /* Set up cmail mode */
1632         ReloadCmailMsgEvent(TRUE);
1633     } else {
1634         /* Set up other modes */
1635         if (initialMode == AnalyzeFile) {
1636           if (*appData.loadGameFile == NULLCHAR) {
1637             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1638             return;
1639           }
1640         }
1641         if (*appData.loadGameFile != NULLCHAR) {
1642             (void) LoadGameFromFile(appData.loadGameFile,
1643                                     appData.loadGameIndex,
1644                                     appData.loadGameFile, TRUE);
1645         } else if (*appData.loadPositionFile != NULLCHAR) {
1646             (void) LoadPositionFromFile(appData.loadPositionFile,
1647                                         appData.loadPositionIndex,
1648                                         appData.loadPositionFile);
1649             /* [HGM] try to make self-starting even after FEN load */
1650             /* to allow automatic setup of fairy variants with wtm */
1651             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1652                 gameMode = BeginningOfGame;
1653                 setboardSpoiledMachineBlack = 1;
1654             }
1655             /* [HGM] loadPos: make that every new game uses the setup */
1656             /* from file as long as we do not switch variant          */
1657             if(!blackPlaysFirst) {
1658                 startedFromPositionFile = TRUE;
1659                 CopyBoard(filePosition, boards[0]);
1660             }
1661         }
1662         if (initialMode == AnalyzeMode) {
1663           if (appData.noChessProgram) {
1664             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1665             return;
1666           }
1667           if (appData.icsActive) {
1668             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1669             return;
1670           }
1671           AnalyzeModeEvent();
1672         } else if (initialMode == AnalyzeFile) {
1673           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1674           ShowThinkingEvent();
1675           AnalyzeFileEvent();
1676           AnalysisPeriodicEvent(1);
1677         } else if (initialMode == MachinePlaysWhite) {
1678           if (appData.noChessProgram) {
1679             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1680                               0, 2);
1681             return;
1682           }
1683           if (appData.icsActive) {
1684             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1685                               0, 2);
1686             return;
1687           }
1688           MachineWhiteEvent();
1689         } else if (initialMode == MachinePlaysBlack) {
1690           if (appData.noChessProgram) {
1691             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1692                               0, 2);
1693             return;
1694           }
1695           if (appData.icsActive) {
1696             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1697                               0, 2);
1698             return;
1699           }
1700           MachineBlackEvent();
1701         } else if (initialMode == TwoMachinesPlay) {
1702           if (appData.noChessProgram) {
1703             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1704                               0, 2);
1705             return;
1706           }
1707           if (appData.icsActive) {
1708             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1709                               0, 2);
1710             return;
1711           }
1712           TwoMachinesEvent();
1713         } else if (initialMode == EditGame) {
1714           EditGameEvent();
1715         } else if (initialMode == EditPosition) {
1716           EditPositionEvent();
1717         } else if (initialMode == Training) {
1718           if (*appData.loadGameFile == NULLCHAR) {
1719             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1720             return;
1721           }
1722           TrainingEvent();
1723         }
1724     }
1725 }
1726
1727 void
1728 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1729 {
1730     DisplayBook(current+1);
1731
1732     MoveHistorySet( movelist, first, last, current, pvInfoList );
1733
1734     EvalGraphSet( first, last, current, pvInfoList );
1735
1736     MakeEngineOutputTitle();
1737 }
1738
1739 /*
1740  * Establish will establish a contact to a remote host.port.
1741  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1742  *  used to talk to the host.
1743  * Returns 0 if okay, error code if not.
1744  */
1745 int
1746 establish ()
1747 {
1748     char buf[MSG_SIZ];
1749
1750     if (*appData.icsCommPort != NULLCHAR) {
1751         /* Talk to the host through a serial comm port */
1752         return OpenCommPort(appData.icsCommPort, &icsPR);
1753
1754     } else if (*appData.gateway != NULLCHAR) {
1755         if (*appData.remoteShell == NULLCHAR) {
1756             /* Use the rcmd protocol to run telnet program on a gateway host */
1757             snprintf(buf, sizeof(buf), "%s %s %s",
1758                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1759             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1760
1761         } else {
1762             /* Use the rsh program to run telnet program on a gateway host */
1763             if (*appData.remoteUser == NULLCHAR) {
1764                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1765                         appData.gateway, appData.telnetProgram,
1766                         appData.icsHost, appData.icsPort);
1767             } else {
1768                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1769                         appData.remoteShell, appData.gateway,
1770                         appData.remoteUser, appData.telnetProgram,
1771                         appData.icsHost, appData.icsPort);
1772             }
1773             return StartChildProcess(buf, "", &icsPR);
1774
1775         }
1776     } else if (appData.useTelnet) {
1777         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1778
1779     } else {
1780         /* TCP socket interface differs somewhat between
1781            Unix and NT; handle details in the front end.
1782            */
1783         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1784     }
1785 }
1786
1787 void
1788 EscapeExpand (char *p, char *q)
1789 {       // [HGM] initstring: routine to shape up string arguments
1790         while(*p++ = *q++) if(p[-1] == '\\')
1791             switch(*q++) {
1792                 case 'n': p[-1] = '\n'; break;
1793                 case 'r': p[-1] = '\r'; break;
1794                 case 't': p[-1] = '\t'; break;
1795                 case '\\': p[-1] = '\\'; break;
1796                 case 0: *p = 0; return;
1797                 default: p[-1] = q[-1]; break;
1798             }
1799 }
1800
1801 void
1802 show_bytes (FILE *fp, char *buf, int count)
1803 {
1804     while (count--) {
1805         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1806             fprintf(fp, "\\%03o", *buf & 0xff);
1807         } else {
1808             putc(*buf, fp);
1809         }
1810         buf++;
1811     }
1812     fflush(fp);
1813 }
1814
1815 /* Returns an errno value */
1816 int
1817 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1818 {
1819     char buf[8192], *p, *q, *buflim;
1820     int left, newcount, outcount;
1821
1822     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1823         *appData.gateway != NULLCHAR) {
1824         if (appData.debugMode) {
1825             fprintf(debugFP, ">ICS: ");
1826             show_bytes(debugFP, message, count);
1827             fprintf(debugFP, "\n");
1828         }
1829         return OutputToProcess(pr, message, count, outError);
1830     }
1831
1832     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1833     p = message;
1834     q = buf;
1835     left = count;
1836     newcount = 0;
1837     while (left) {
1838         if (q >= buflim) {
1839             if (appData.debugMode) {
1840                 fprintf(debugFP, ">ICS: ");
1841                 show_bytes(debugFP, buf, newcount);
1842                 fprintf(debugFP, "\n");
1843             }
1844             outcount = OutputToProcess(pr, buf, newcount, outError);
1845             if (outcount < newcount) return -1; /* to be sure */
1846             q = buf;
1847             newcount = 0;
1848         }
1849         if (*p == '\n') {
1850             *q++ = '\r';
1851             newcount++;
1852         } else if (((unsigned char) *p) == TN_IAC) {
1853             *q++ = (char) TN_IAC;
1854             newcount ++;
1855         }
1856         *q++ = *p++;
1857         newcount++;
1858         left--;
1859     }
1860     if (appData.debugMode) {
1861         fprintf(debugFP, ">ICS: ");
1862         show_bytes(debugFP, buf, newcount);
1863         fprintf(debugFP, "\n");
1864     }
1865     outcount = OutputToProcess(pr, buf, newcount, outError);
1866     if (outcount < newcount) return -1; /* to be sure */
1867     return count;
1868 }
1869
1870 void
1871 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1872 {
1873     int outError, outCount;
1874     static int gotEof = 0;
1875     static FILE *ini;
1876
1877     /* Pass data read from player on to ICS */
1878     if (count > 0) {
1879         gotEof = 0;
1880         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1881         if (outCount < count) {
1882             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1883         }
1884         if(have_sent_ICS_logon == 2) {
1885           if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1886             fprintf(ini, "%s", message);
1887             have_sent_ICS_logon = 3;
1888           } else
1889             have_sent_ICS_logon = 1;
1890         } else if(have_sent_ICS_logon == 3) {
1891             fprintf(ini, "%s", message);
1892             fclose(ini);
1893           have_sent_ICS_logon = 1;
1894         }
1895     } else if (count < 0) {
1896         RemoveInputSource(isr);
1897         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1898     } else if (gotEof++ > 0) {
1899         RemoveInputSource(isr);
1900         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1901     }
1902 }
1903
1904 void
1905 KeepAlive ()
1906 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1907     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1908     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1909     SendToICS("date\n");
1910     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1911 }
1912
1913 /* added routine for printf style output to ics */
1914 void
1915 ics_printf (char *format, ...)
1916 {
1917     char buffer[MSG_SIZ];
1918     va_list args;
1919
1920     va_start(args, format);
1921     vsnprintf(buffer, sizeof(buffer), format, args);
1922     buffer[sizeof(buffer)-1] = '\0';
1923     SendToICS(buffer);
1924     va_end(args);
1925 }
1926
1927 void
1928 SendToICS (char *s)
1929 {
1930     int count, outCount, outError;
1931
1932     if (icsPR == NoProc) return;
1933
1934     count = strlen(s);
1935     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1936     if (outCount < count) {
1937         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1938     }
1939 }
1940
1941 /* This is used for sending logon scripts to the ICS. Sending
1942    without a delay causes problems when using timestamp on ICC
1943    (at least on my machine). */
1944 void
1945 SendToICSDelayed (char *s, long msdelay)
1946 {
1947     int count, outCount, outError;
1948
1949     if (icsPR == NoProc) return;
1950
1951     count = strlen(s);
1952     if (appData.debugMode) {
1953         fprintf(debugFP, ">ICS: ");
1954         show_bytes(debugFP, s, count);
1955         fprintf(debugFP, "\n");
1956     }
1957     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1958                                       msdelay);
1959     if (outCount < count) {
1960         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1961     }
1962 }
1963
1964
1965 /* Remove all highlighting escape sequences in s
1966    Also deletes any suffix starting with '('
1967    */
1968 char *
1969 StripHighlightAndTitle (char *s)
1970 {
1971     static char retbuf[MSG_SIZ];
1972     char *p = retbuf;
1973
1974     while (*s != NULLCHAR) {
1975         while (*s == '\033') {
1976             while (*s != NULLCHAR && !isalpha(*s)) s++;
1977             if (*s != NULLCHAR) s++;
1978         }
1979         while (*s != NULLCHAR && *s != '\033') {
1980             if (*s == '(' || *s == '[') {
1981                 *p = NULLCHAR;
1982                 return retbuf;
1983             }
1984             *p++ = *s++;
1985         }
1986     }
1987     *p = NULLCHAR;
1988     return retbuf;
1989 }
1990
1991 /* Remove all highlighting escape sequences in s */
1992 char *
1993 StripHighlight (char *s)
1994 {
1995     static char retbuf[MSG_SIZ];
1996     char *p = retbuf;
1997
1998     while (*s != NULLCHAR) {
1999         while (*s == '\033') {
2000             while (*s != NULLCHAR && !isalpha(*s)) s++;
2001             if (*s != NULLCHAR) s++;
2002         }
2003         while (*s != NULLCHAR && *s != '\033') {
2004             *p++ = *s++;
2005         }
2006     }
2007     *p = NULLCHAR;
2008     return retbuf;
2009 }
2010
2011 char engineVariant[MSG_SIZ];
2012 char *variantNames[] = VARIANT_NAMES;
2013 char *
2014 VariantName (VariantClass v)
2015 {
2016     if(v == VariantUnknown || *engineVariant) return engineVariant;
2017     return variantNames[v];
2018 }
2019
2020
2021 /* Identify a variant from the strings the chess servers use or the
2022    PGN Variant tag names we use. */
2023 VariantClass
2024 StringToVariant (char *e)
2025 {
2026     char *p;
2027     int wnum = -1;
2028     VariantClass v = VariantNormal;
2029     int i, found = FALSE;
2030     char buf[MSG_SIZ];
2031     int len;
2032
2033     if (!e) return v;
2034
2035     /* [HGM] skip over optional board-size prefixes */
2036     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
2037         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
2038         while( *e++ != '_');
2039     }
2040
2041     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2042         v = VariantNormal;
2043         found = TRUE;
2044     } else
2045     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2046       if (StrCaseStr(e, variantNames[i])) {
2047         v = (VariantClass) i;
2048         found = TRUE;
2049         break;
2050       }
2051     }
2052
2053     if (!found) {
2054       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2055           || StrCaseStr(e, "wild/fr")
2056           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2057         v = VariantFischeRandom;
2058       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2059                  (i = 1, p = StrCaseStr(e, "w"))) {
2060         p += i;
2061         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2062         if (isdigit(*p)) {
2063           wnum = atoi(p);
2064         } else {
2065           wnum = -1;
2066         }
2067         switch (wnum) {
2068         case 0: /* FICS only, actually */
2069         case 1:
2070           /* Castling legal even if K starts on d-file */
2071           v = VariantWildCastle;
2072           break;
2073         case 2:
2074         case 3:
2075         case 4:
2076           /* Castling illegal even if K & R happen to start in
2077              normal positions. */
2078           v = VariantNoCastle;
2079           break;
2080         case 5:
2081         case 7:
2082         case 8:
2083         case 10:
2084         case 11:
2085         case 12:
2086         case 13:
2087         case 14:
2088         case 15:
2089         case 18:
2090         case 19:
2091           /* Castling legal iff K & R start in normal positions */
2092           v = VariantNormal;
2093           break;
2094         case 6:
2095         case 20:
2096         case 21:
2097           /* Special wilds for position setup; unclear what to do here */
2098           v = VariantLoadable;
2099           break;
2100         case 9:
2101           /* Bizarre ICC game */
2102           v = VariantTwoKings;
2103           break;
2104         case 16:
2105           v = VariantKriegspiel;
2106           break;
2107         case 17:
2108           v = VariantLosers;
2109           break;
2110         case 22:
2111           v = VariantFischeRandom;
2112           break;
2113         case 23:
2114           v = VariantCrazyhouse;
2115           break;
2116         case 24:
2117           v = VariantBughouse;
2118           break;
2119         case 25:
2120           v = Variant3Check;
2121           break;
2122         case 26:
2123           /* Not quite the same as FICS suicide! */
2124           v = VariantGiveaway;
2125           break;
2126         case 27:
2127           v = VariantAtomic;
2128           break;
2129         case 28:
2130           v = VariantShatranj;
2131           break;
2132
2133         /* Temporary names for future ICC types.  The name *will* change in
2134            the next xboard/WinBoard release after ICC defines it. */
2135         case 29:
2136           v = Variant29;
2137           break;
2138         case 30:
2139           v = Variant30;
2140           break;
2141         case 31:
2142           v = Variant31;
2143           break;
2144         case 32:
2145           v = Variant32;
2146           break;
2147         case 33:
2148           v = Variant33;
2149           break;
2150         case 34:
2151           v = Variant34;
2152           break;
2153         case 35:
2154           v = Variant35;
2155           break;
2156         case 36:
2157           v = Variant36;
2158           break;
2159         case 37:
2160           v = VariantShogi;
2161           break;
2162         case 38:
2163           v = VariantXiangqi;
2164           break;
2165         case 39:
2166           v = VariantCourier;
2167           break;
2168         case 40:
2169           v = VariantGothic;
2170           break;
2171         case 41:
2172           v = VariantCapablanca;
2173           break;
2174         case 42:
2175           v = VariantKnightmate;
2176           break;
2177         case 43:
2178           v = VariantFairy;
2179           break;
2180         case 44:
2181           v = VariantCylinder;
2182           break;
2183         case 45:
2184           v = VariantFalcon;
2185           break;
2186         case 46:
2187           v = VariantCapaRandom;
2188           break;
2189         case 47:
2190           v = VariantBerolina;
2191           break;
2192         case 48:
2193           v = VariantJanus;
2194           break;
2195         case 49:
2196           v = VariantSuper;
2197           break;
2198         case 50:
2199           v = VariantGreat;
2200           break;
2201         case -1:
2202           /* Found "wild" or "w" in the string but no number;
2203              must assume it's normal chess. */
2204           v = VariantNormal;
2205           break;
2206         default:
2207           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2208           if( (len >= MSG_SIZ) && appData.debugMode )
2209             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2210
2211           DisplayError(buf, 0);
2212           v = VariantUnknown;
2213           break;
2214         }
2215       }
2216     }
2217     if (appData.debugMode) {
2218       fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
2219               e, wnum, VariantName(v));
2220     }
2221     return v;
2222 }
2223
2224 static int leftover_start = 0, leftover_len = 0;
2225 char star_match[STAR_MATCH_N][MSG_SIZ];
2226
2227 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2228    advance *index beyond it, and set leftover_start to the new value of
2229    *index; else return FALSE.  If pattern contains the character '*', it
2230    matches any sequence of characters not containing '\r', '\n', or the
2231    character following the '*' (if any), and the matched sequence(s) are
2232    copied into star_match.
2233    */
2234 int
2235 looking_at ( char *buf, int *index, char *pattern)
2236 {
2237     char *bufp = &buf[*index], *patternp = pattern;
2238     int star_count = 0;
2239     char *matchp = star_match[0];
2240
2241     for (;;) {
2242         if (*patternp == NULLCHAR) {
2243             *index = leftover_start = bufp - buf;
2244             *matchp = NULLCHAR;
2245             return TRUE;
2246         }
2247         if (*bufp == NULLCHAR) return FALSE;
2248         if (*patternp == '*') {
2249             if (*bufp == *(patternp + 1)) {
2250                 *matchp = NULLCHAR;
2251                 matchp = star_match[++star_count];
2252                 patternp += 2;
2253                 bufp++;
2254                 continue;
2255             } else if (*bufp == '\n' || *bufp == '\r') {
2256                 patternp++;
2257                 if (*patternp == NULLCHAR)
2258                   continue;
2259                 else
2260                   return FALSE;
2261             } else {
2262                 *matchp++ = *bufp++;
2263                 continue;
2264             }
2265         }
2266         if (*patternp != *bufp) return FALSE;
2267         patternp++;
2268         bufp++;
2269     }
2270 }
2271
2272 void
2273 SendToPlayer (char *data, int length)
2274 {
2275     int error, outCount;
2276     outCount = OutputToProcess(NoProc, data, length, &error);
2277     if (outCount < length) {
2278         DisplayFatalError(_("Error writing to display"), error, 1);
2279     }
2280 }
2281
2282 void
2283 PackHolding (char packed[], char *holding)
2284 {
2285     char *p = holding;
2286     char *q = packed;
2287     int runlength = 0;
2288     int curr = 9999;
2289     do {
2290         if (*p == curr) {
2291             runlength++;
2292         } else {
2293             switch (runlength) {
2294               case 0:
2295                 break;
2296               case 1:
2297                 *q++ = curr;
2298                 break;
2299               case 2:
2300                 *q++ = curr;
2301                 *q++ = curr;
2302                 break;
2303               default:
2304                 sprintf(q, "%d", runlength);
2305                 while (*q) q++;
2306                 *q++ = curr;
2307                 break;
2308             }
2309             runlength = 1;
2310             curr = *p;
2311         }
2312     } while (*p++);
2313     *q = NULLCHAR;
2314 }
2315
2316 /* Telnet protocol requests from the front end */
2317 void
2318 TelnetRequest (unsigned char ddww, unsigned char option)
2319 {
2320     unsigned char msg[3];
2321     int outCount, outError;
2322
2323     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2324
2325     if (appData.debugMode) {
2326         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2327         switch (ddww) {
2328           case TN_DO:
2329             ddwwStr = "DO";
2330             break;
2331           case TN_DONT:
2332             ddwwStr = "DONT";
2333             break;
2334           case TN_WILL:
2335             ddwwStr = "WILL";
2336             break;
2337           case TN_WONT:
2338             ddwwStr = "WONT";
2339             break;
2340           default:
2341             ddwwStr = buf1;
2342             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2343             break;
2344         }
2345         switch (option) {
2346           case TN_ECHO:
2347             optionStr = "ECHO";
2348             break;
2349           default:
2350             optionStr = buf2;
2351             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2352             break;
2353         }
2354         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2355     }
2356     msg[0] = TN_IAC;
2357     msg[1] = ddww;
2358     msg[2] = option;
2359     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2360     if (outCount < 3) {
2361         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2362     }
2363 }
2364
2365 void
2366 DoEcho ()
2367 {
2368     if (!appData.icsActive) return;
2369     TelnetRequest(TN_DO, TN_ECHO);
2370 }
2371
2372 void
2373 DontEcho ()
2374 {
2375     if (!appData.icsActive) return;
2376     TelnetRequest(TN_DONT, TN_ECHO);
2377 }
2378
2379 void
2380 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2381 {
2382     /* put the holdings sent to us by the server on the board holdings area */
2383     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2384     char p;
2385     ChessSquare piece;
2386
2387     if(gameInfo.holdingsWidth < 2)  return;
2388     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2389         return; // prevent overwriting by pre-board holdings
2390
2391     if( (int)lowestPiece >= BlackPawn ) {
2392         holdingsColumn = 0;
2393         countsColumn = 1;
2394         holdingsStartRow = BOARD_HEIGHT-1;
2395         direction = -1;
2396     } else {
2397         holdingsColumn = BOARD_WIDTH-1;
2398         countsColumn = BOARD_WIDTH-2;
2399         holdingsStartRow = 0;
2400         direction = 1;
2401     }
2402
2403     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2404         board[i][holdingsColumn] = EmptySquare;
2405         board[i][countsColumn]   = (ChessSquare) 0;
2406     }
2407     while( (p=*holdings++) != NULLCHAR ) {
2408         piece = CharToPiece( ToUpper(p) );
2409         if(piece == EmptySquare) continue;
2410         /*j = (int) piece - (int) WhitePawn;*/
2411         j = PieceToNumber(piece);
2412         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2413         if(j < 0) continue;               /* should not happen */
2414         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2415         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2416         board[holdingsStartRow+j*direction][countsColumn]++;
2417     }
2418 }
2419
2420
2421 void
2422 VariantSwitch (Board board, VariantClass newVariant)
2423 {
2424    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2425    static Board oldBoard;
2426
2427    startedFromPositionFile = FALSE;
2428    if(gameInfo.variant == newVariant) return;
2429
2430    /* [HGM] This routine is called each time an assignment is made to
2431     * gameInfo.variant during a game, to make sure the board sizes
2432     * are set to match the new variant. If that means adding or deleting
2433     * holdings, we shift the playing board accordingly
2434     * This kludge is needed because in ICS observe mode, we get boards
2435     * of an ongoing game without knowing the variant, and learn about the
2436     * latter only later. This can be because of the move list we requested,
2437     * in which case the game history is refilled from the beginning anyway,
2438     * but also when receiving holdings of a crazyhouse game. In the latter
2439     * case we want to add those holdings to the already received position.
2440     */
2441
2442
2443    if (appData.debugMode) {
2444      fprintf(debugFP, "Switch board from %s to %s\n",
2445              VariantName(gameInfo.variant), VariantName(newVariant));
2446      setbuf(debugFP, NULL);
2447    }
2448    shuffleOpenings = 0;       /* [HGM] shuffle */
2449    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2450    switch(newVariant)
2451      {
2452      case VariantShogi:
2453        newWidth = 9;  newHeight = 9;
2454        gameInfo.holdingsSize = 7;
2455      case VariantBughouse:
2456      case VariantCrazyhouse:
2457        newHoldingsWidth = 2; break;
2458      case VariantGreat:
2459        newWidth = 10;
2460      case VariantSuper:
2461        newHoldingsWidth = 2;
2462        gameInfo.holdingsSize = 8;
2463        break;
2464      case VariantGothic:
2465      case VariantCapablanca:
2466      case VariantCapaRandom:
2467        newWidth = 10;
2468      default:
2469        newHoldingsWidth = gameInfo.holdingsSize = 0;
2470      };
2471
2472    if(newWidth  != gameInfo.boardWidth  ||
2473       newHeight != gameInfo.boardHeight ||
2474       newHoldingsWidth != gameInfo.holdingsWidth ) {
2475
2476      /* shift position to new playing area, if needed */
2477      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2478        for(i=0; i<BOARD_HEIGHT; i++)
2479          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2480            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2481              board[i][j];
2482        for(i=0; i<newHeight; i++) {
2483          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2484          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2485        }
2486      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2487        for(i=0; i<BOARD_HEIGHT; i++)
2488          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2489            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2490              board[i][j];
2491      }
2492      board[HOLDINGS_SET] = 0;
2493      gameInfo.boardWidth  = newWidth;
2494      gameInfo.boardHeight = newHeight;
2495      gameInfo.holdingsWidth = newHoldingsWidth;
2496      gameInfo.variant = newVariant;
2497      InitDrawingSizes(-2, 0);
2498    } else gameInfo.variant = newVariant;
2499    CopyBoard(oldBoard, board);   // remember correctly formatted board
2500      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2501    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2502 }
2503
2504 static int loggedOn = FALSE;
2505
2506 /*-- Game start info cache: --*/
2507 int gs_gamenum;
2508 char gs_kind[MSG_SIZ];
2509 static char player1Name[128] = "";
2510 static char player2Name[128] = "";
2511 static char cont_seq[] = "\n\\   ";
2512 static int player1Rating = -1;
2513 static int player2Rating = -1;
2514 /*----------------------------*/
2515
2516 ColorClass curColor = ColorNormal;
2517 int suppressKibitz = 0;
2518
2519 // [HGM] seekgraph
2520 Boolean soughtPending = FALSE;
2521 Boolean seekGraphUp;
2522 #define MAX_SEEK_ADS 200
2523 #define SQUARE 0x80
2524 char *seekAdList[MAX_SEEK_ADS];
2525 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2526 float tcList[MAX_SEEK_ADS];
2527 char colorList[MAX_SEEK_ADS];
2528 int nrOfSeekAds = 0;
2529 int minRating = 1010, maxRating = 2800;
2530 int hMargin = 10, vMargin = 20, h, w;
2531 extern int squareSize, lineGap;
2532
2533 void
2534 PlotSeekAd (int i)
2535 {
2536         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2537         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2538         if(r < minRating+100 && r >=0 ) r = minRating+100;
2539         if(r > maxRating) r = maxRating;
2540         if(tc < 1.f) tc = 1.f;
2541         if(tc > 95.f) tc = 95.f;
2542         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2543         y = ((double)r - minRating)/(maxRating - minRating)
2544             * (h-vMargin-squareSize/8-1) + vMargin;
2545         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2546         if(strstr(seekAdList[i], " u ")) color = 1;
2547         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2548            !strstr(seekAdList[i], "bullet") &&
2549            !strstr(seekAdList[i], "blitz") &&
2550            !strstr(seekAdList[i], "standard") ) color = 2;
2551         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2552         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2553 }
2554
2555 void
2556 PlotSingleSeekAd (int i)
2557 {
2558         PlotSeekAd(i);
2559 }
2560
2561 void
2562 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2563 {
2564         char buf[MSG_SIZ], *ext = "";
2565         VariantClass v = StringToVariant(type);
2566         if(strstr(type, "wild")) {
2567             ext = type + 4; // append wild number
2568             if(v == VariantFischeRandom) type = "chess960"; else
2569             if(v == VariantLoadable) type = "setup"; else
2570             type = VariantName(v);
2571         }
2572         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2573         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2574             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2575             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2576             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2577             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2578             seekNrList[nrOfSeekAds] = nr;
2579             zList[nrOfSeekAds] = 0;
2580             seekAdList[nrOfSeekAds++] = StrSave(buf);
2581             if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2582         }
2583 }
2584
2585 void
2586 EraseSeekDot (int i)
2587 {
2588     int x = xList[i], y = yList[i], d=squareSize/4, k;
2589     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2590     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2591     // now replot every dot that overlapped
2592     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2593         int xx = xList[k], yy = yList[k];
2594         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2595             DrawSeekDot(xx, yy, colorList[k]);
2596     }
2597 }
2598
2599 void
2600 RemoveSeekAd (int nr)
2601 {
2602         int i;
2603         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2604             EraseSeekDot(i);
2605             if(seekAdList[i]) free(seekAdList[i]);
2606             seekAdList[i] = seekAdList[--nrOfSeekAds];
2607             seekNrList[i] = seekNrList[nrOfSeekAds];
2608             ratingList[i] = ratingList[nrOfSeekAds];
2609             colorList[i]  = colorList[nrOfSeekAds];
2610             tcList[i] = tcList[nrOfSeekAds];
2611             xList[i]  = xList[nrOfSeekAds];
2612             yList[i]  = yList[nrOfSeekAds];
2613             zList[i]  = zList[nrOfSeekAds];
2614             seekAdList[nrOfSeekAds] = NULL;
2615             break;
2616         }
2617 }
2618
2619 Boolean
2620 MatchSoughtLine (char *line)
2621 {
2622     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2623     int nr, base, inc, u=0; char dummy;
2624
2625     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2626        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2627        (u=1) &&
2628        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2629         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2630         // match: compact and save the line
2631         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2632         return TRUE;
2633     }
2634     return FALSE;
2635 }
2636
2637 int
2638 DrawSeekGraph ()
2639 {
2640     int i;
2641     if(!seekGraphUp) return FALSE;
2642     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2643     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2644
2645     DrawSeekBackground(0, 0, w, h);
2646     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2647     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2648     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2649         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2650         yy = h-1-yy;
2651         DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2652         if(i%500 == 0) {
2653             char buf[MSG_SIZ];
2654             snprintf(buf, MSG_SIZ, "%d", i);
2655             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2656         }
2657     }
2658     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2659     for(i=1; i<100; i+=(i<10?1:5)) {
2660         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2661         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2662         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2663             char buf[MSG_SIZ];
2664             snprintf(buf, MSG_SIZ, "%d", i);
2665             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2666         }
2667     }
2668     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2669     return TRUE;
2670 }
2671
2672 int
2673 SeekGraphClick (ClickType click, int x, int y, int moving)
2674 {
2675     static int lastDown = 0, displayed = 0, lastSecond;
2676     if(y < 0) return FALSE;
2677     if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2678         (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2679         if(!seekGraphUp) return FALSE;
2680         seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2681         DrawPosition(TRUE, NULL);
2682         return TRUE;
2683     }
2684     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2685         if(click == Release || moving) return FALSE;
2686         nrOfSeekAds = 0;
2687         soughtPending = TRUE;
2688         SendToICS(ics_prefix);
2689         SendToICS("sought\n"); // should this be "sought all"?
2690     } else { // issue challenge based on clicked ad
2691         int dist = 10000; int i, closest = 0, second = 0;
2692         for(i=0; i<nrOfSeekAds; i++) {
2693             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2694             if(d < dist) { dist = d; closest = i; }
2695             second += (d - zList[i] < 120); // count in-range ads
2696             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2697         }
2698         if(dist < 120) {
2699             char buf[MSG_SIZ];
2700             second = (second > 1);
2701             if(displayed != closest || second != lastSecond) {
2702                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2703                 lastSecond = second; displayed = closest;
2704             }
2705             if(click == Press) {
2706                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2707                 lastDown = closest;
2708                 return TRUE;
2709             } // on press 'hit', only show info
2710             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2711             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2712             SendToICS(ics_prefix);
2713             SendToICS(buf);
2714             return TRUE; // let incoming board of started game pop down the graph
2715         } else if(click == Release) { // release 'miss' is ignored
2716             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2717             if(moving == 2) { // right up-click
2718                 nrOfSeekAds = 0; // refresh graph
2719                 soughtPending = TRUE;
2720                 SendToICS(ics_prefix);
2721                 SendToICS("sought\n"); // should this be "sought all"?
2722             }
2723             return TRUE;
2724         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2725         // press miss or release hit 'pop down' seek graph
2726         seekGraphUp = FALSE;
2727         DrawPosition(TRUE, NULL);
2728     }
2729     return TRUE;
2730 }
2731
2732 void
2733 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2734 {
2735 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2736 #define STARTED_NONE 0
2737 #define STARTED_MOVES 1
2738 #define STARTED_BOARD 2
2739 #define STARTED_OBSERVE 3
2740 #define STARTED_HOLDINGS 4
2741 #define STARTED_CHATTER 5
2742 #define STARTED_COMMENT 6
2743 #define STARTED_MOVES_NOHIDE 7
2744
2745     static int started = STARTED_NONE;
2746     static char parse[20000];
2747     static int parse_pos = 0;
2748     static char buf[BUF_SIZE + 1];
2749     static int firstTime = TRUE, intfSet = FALSE;
2750     static ColorClass prevColor = ColorNormal;
2751     static int savingComment = FALSE;
2752     static int cmatch = 0; // continuation sequence match
2753     char *bp;
2754     char str[MSG_SIZ];
2755     int i, oldi;
2756     int buf_len;
2757     int next_out;
2758     int tkind;
2759     int backup;    /* [DM] For zippy color lines */
2760     char *p;
2761     char talker[MSG_SIZ]; // [HGM] chat
2762     int channel;
2763
2764     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2765
2766     if (appData.debugMode) {
2767       if (!error) {
2768         fprintf(debugFP, "<ICS: ");
2769         show_bytes(debugFP, data, count);
2770         fprintf(debugFP, "\n");
2771       }
2772     }
2773
2774     if (appData.debugMode) { int f = forwardMostMove;
2775         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2776                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2777                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2778     }
2779     if (count > 0) {
2780         /* If last read ended with a partial line that we couldn't parse,
2781            prepend it to the new read and try again. */
2782         if (leftover_len > 0) {
2783             for (i=0; i<leftover_len; i++)
2784               buf[i] = buf[leftover_start + i];
2785         }
2786
2787     /* copy new characters into the buffer */
2788     bp = buf + leftover_len;
2789     buf_len=leftover_len;
2790     for (i=0; i<count; i++)
2791     {
2792         // ignore these
2793         if (data[i] == '\r')
2794             continue;
2795
2796         // join lines split by ICS?
2797         if (!appData.noJoin)
2798         {
2799             /*
2800                 Joining just consists of finding matches against the
2801                 continuation sequence, and discarding that sequence
2802                 if found instead of copying it.  So, until a match
2803                 fails, there's nothing to do since it might be the
2804                 complete sequence, and thus, something we don't want
2805                 copied.
2806             */
2807             if (data[i] == cont_seq[cmatch])
2808             {
2809                 cmatch++;
2810                 if (cmatch == strlen(cont_seq))
2811                 {
2812                     cmatch = 0; // complete match.  just reset the counter
2813
2814                     /*
2815                         it's possible for the ICS to not include the space
2816                         at the end of the last word, making our [correct]
2817                         join operation fuse two separate words.  the server
2818                         does this when the space occurs at the width setting.
2819                     */
2820                     if (!buf_len || buf[buf_len-1] != ' ')
2821                     {
2822                         *bp++ = ' ';
2823                         buf_len++;
2824                     }
2825                 }
2826                 continue;
2827             }
2828             else if (cmatch)
2829             {
2830                 /*
2831                     match failed, so we have to copy what matched before
2832                     falling through and copying this character.  In reality,
2833                     this will only ever be just the newline character, but
2834                     it doesn't hurt to be precise.
2835                 */
2836                 strncpy(bp, cont_seq, cmatch);
2837                 bp += cmatch;
2838                 buf_len += cmatch;
2839                 cmatch = 0;
2840             }
2841         }
2842
2843         // copy this char
2844         *bp++ = data[i];
2845         buf_len++;
2846     }
2847
2848         buf[buf_len] = NULLCHAR;
2849 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2850         next_out = 0;
2851         leftover_start = 0;
2852
2853         i = 0;
2854         while (i < buf_len) {
2855             /* Deal with part of the TELNET option negotiation
2856                protocol.  We refuse to do anything beyond the
2857                defaults, except that we allow the WILL ECHO option,
2858                which ICS uses to turn off password echoing when we are
2859                directly connected to it.  We reject this option
2860                if localLineEditing mode is on (always on in xboard)
2861                and we are talking to port 23, which might be a real
2862                telnet server that will try to keep WILL ECHO on permanently.
2863              */
2864             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2865                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2866                 unsigned char option;
2867                 oldi = i;
2868                 switch ((unsigned char) buf[++i]) {
2869                   case TN_WILL:
2870                     if (appData.debugMode)
2871                       fprintf(debugFP, "\n<WILL ");
2872                     switch (option = (unsigned char) buf[++i]) {
2873                       case TN_ECHO:
2874                         if (appData.debugMode)
2875                           fprintf(debugFP, "ECHO ");
2876                         /* Reply only if this is a change, according
2877                            to the protocol rules. */
2878                         if (remoteEchoOption) break;
2879                         if (appData.localLineEditing &&
2880                             atoi(appData.icsPort) == TN_PORT) {
2881                             TelnetRequest(TN_DONT, TN_ECHO);
2882                         } else {
2883                             EchoOff();
2884                             TelnetRequest(TN_DO, TN_ECHO);
2885                             remoteEchoOption = TRUE;
2886                         }
2887                         break;
2888                       default:
2889                         if (appData.debugMode)
2890                           fprintf(debugFP, "%d ", option);
2891                         /* Whatever this is, we don't want it. */
2892                         TelnetRequest(TN_DONT, option);
2893                         break;
2894                     }
2895                     break;
2896                   case TN_WONT:
2897                     if (appData.debugMode)
2898                       fprintf(debugFP, "\n<WONT ");
2899                     switch (option = (unsigned char) buf[++i]) {
2900                       case TN_ECHO:
2901                         if (appData.debugMode)
2902                           fprintf(debugFP, "ECHO ");
2903                         /* Reply only if this is a change, according
2904                            to the protocol rules. */
2905                         if (!remoteEchoOption) break;
2906                         EchoOn();
2907                         TelnetRequest(TN_DONT, TN_ECHO);
2908                         remoteEchoOption = FALSE;
2909                         break;
2910                       default:
2911                         if (appData.debugMode)
2912                           fprintf(debugFP, "%d ", (unsigned char) option);
2913                         /* Whatever this is, it must already be turned
2914                            off, because we never agree to turn on
2915                            anything non-default, so according to the
2916                            protocol rules, we don't reply. */
2917                         break;
2918                     }
2919                     break;
2920                   case TN_DO:
2921                     if (appData.debugMode)
2922                       fprintf(debugFP, "\n<DO ");
2923                     switch (option = (unsigned char) buf[++i]) {
2924                       default:
2925                         /* Whatever this is, we refuse to do it. */
2926                         if (appData.debugMode)
2927                           fprintf(debugFP, "%d ", option);
2928                         TelnetRequest(TN_WONT, option);
2929                         break;
2930                     }
2931                     break;
2932                   case TN_DONT:
2933                     if (appData.debugMode)
2934                       fprintf(debugFP, "\n<DONT ");
2935                     switch (option = (unsigned char) buf[++i]) {
2936                       default:
2937                         if (appData.debugMode)
2938                           fprintf(debugFP, "%d ", option);
2939                         /* Whatever this is, we are already not doing
2940                            it, because we never agree to do anything
2941                            non-default, so according to the protocol
2942                            rules, we don't reply. */
2943                         break;
2944                     }
2945                     break;
2946                   case TN_IAC:
2947                     if (appData.debugMode)
2948                       fprintf(debugFP, "\n<IAC ");
2949                     /* Doubled IAC; pass it through */
2950                     i--;
2951                     break;
2952                   default:
2953                     if (appData.debugMode)
2954                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2955                     /* Drop all other telnet commands on the floor */
2956                     break;
2957                 }
2958                 if (oldi > next_out)
2959                   SendToPlayer(&buf[next_out], oldi - next_out);
2960                 if (++i > next_out)
2961                   next_out = i;
2962                 continue;
2963             }
2964
2965             /* OK, this at least will *usually* work */
2966             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2967                 loggedOn = TRUE;
2968             }
2969
2970             if (loggedOn && !intfSet) {
2971                 if (ics_type == ICS_ICC) {
2972                   snprintf(str, MSG_SIZ,
2973                           "/set-quietly interface %s\n/set-quietly style 12\n",
2974                           programVersion);
2975                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2976                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2977                 } else if (ics_type == ICS_CHESSNET) {
2978                   snprintf(str, MSG_SIZ, "/style 12\n");
2979                 } else {
2980                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2981                   strcat(str, programVersion);
2982                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2983                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2984                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2985 #ifdef WIN32
2986                   strcat(str, "$iset nohighlight 1\n");
2987 #endif
2988                   strcat(str, "$iset lock 1\n$style 12\n");
2989                 }
2990                 SendToICS(str);
2991                 NotifyFrontendLogin();
2992                 intfSet = TRUE;
2993             }
2994
2995             if (started == STARTED_COMMENT) {
2996                 /* Accumulate characters in comment */
2997                 parse[parse_pos++] = buf[i];
2998                 if (buf[i] == '\n') {
2999                     parse[parse_pos] = NULLCHAR;
3000                     if(chattingPartner>=0) {
3001                         char mess[MSG_SIZ];
3002                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
3003                         OutputChatMessage(chattingPartner, mess);
3004                         chattingPartner = -1;
3005                         next_out = i+1; // [HGM] suppress printing in ICS window
3006                     } else
3007                     if(!suppressKibitz) // [HGM] kibitz
3008                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
3009                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
3010                         int nrDigit = 0, nrAlph = 0, j;
3011                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
3012                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
3013                         parse[parse_pos] = NULLCHAR;
3014                         // try to be smart: if it does not look like search info, it should go to
3015                         // ICS interaction window after all, not to engine-output window.
3016                         for(j=0; j<parse_pos; j++) { // count letters and digits
3017                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
3018                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
3019                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
3020                         }
3021                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
3022                             int depth=0; float score;
3023                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
3024                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
3025                                 pvInfoList[forwardMostMove-1].depth = depth;
3026                                 pvInfoList[forwardMostMove-1].score = 100*score;
3027                             }
3028                             OutputKibitz(suppressKibitz, parse);
3029                         } else {
3030                             char tmp[MSG_SIZ];
3031                             if(gameMode == IcsObserving) // restore original ICS messages
3032                               /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3033                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3034                             else
3035                             /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3036                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3037                             SendToPlayer(tmp, strlen(tmp));
3038                         }
3039                         next_out = i+1; // [HGM] suppress printing in ICS window
3040                     }
3041                     started = STARTED_NONE;
3042                 } else {
3043                     /* Don't match patterns against characters in comment */
3044                     i++;
3045                     continue;
3046                 }
3047             }
3048             if (started == STARTED_CHATTER) {
3049                 if (buf[i] != '\n') {
3050                     /* Don't match patterns against characters in chatter */
3051                     i++;
3052                     continue;
3053                 }
3054                 started = STARTED_NONE;
3055                 if(suppressKibitz) next_out = i+1;
3056             }
3057
3058             /* Kludge to deal with rcmd protocol */
3059             if (firstTime && looking_at(buf, &i, "\001*")) {
3060                 DisplayFatalError(&buf[1], 0, 1);
3061                 continue;
3062             } else {
3063                 firstTime = FALSE;
3064             }
3065
3066             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3067                 ics_type = ICS_ICC;
3068                 ics_prefix = "/";
3069                 if (appData.debugMode)
3070                   fprintf(debugFP, "ics_type %d\n", ics_type);
3071                 continue;
3072             }
3073             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3074                 ics_type = ICS_FICS;
3075                 ics_prefix = "$";
3076                 if (appData.debugMode)
3077                   fprintf(debugFP, "ics_type %d\n", ics_type);
3078                 continue;
3079             }
3080             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3081                 ics_type = ICS_CHESSNET;
3082                 ics_prefix = "/";
3083                 if (appData.debugMode)
3084                   fprintf(debugFP, "ics_type %d\n", ics_type);
3085                 continue;
3086             }
3087
3088             if (!loggedOn &&
3089                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3090                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3091                  looking_at(buf, &i, "will be \"*\""))) {
3092               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3093               continue;
3094             }
3095
3096             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3097               char buf[MSG_SIZ];
3098               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3099               DisplayIcsInteractionTitle(buf);
3100               have_set_title = TRUE;
3101             }
3102
3103             /* skip finger notes */
3104             if (started == STARTED_NONE &&
3105                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3106                  (buf[i] == '1' && buf[i+1] == '0')) &&
3107                 buf[i+2] == ':' && buf[i+3] == ' ') {
3108               started = STARTED_CHATTER;
3109               i += 3;
3110               continue;
3111             }
3112
3113             oldi = i;
3114             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3115             if(appData.seekGraph) {
3116                 if(soughtPending && MatchSoughtLine(buf+i)) {
3117                     i = strstr(buf+i, "rated") - buf;
3118                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3119                     next_out = leftover_start = i;
3120                     started = STARTED_CHATTER;
3121                     suppressKibitz = TRUE;
3122                     continue;
3123                 }
3124                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3125                         && looking_at(buf, &i, "* ads displayed")) {
3126                     soughtPending = FALSE;
3127                     seekGraphUp = TRUE;
3128                     DrawSeekGraph();
3129                     continue;
3130                 }
3131                 if(appData.autoRefresh) {
3132                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3133                         int s = (ics_type == ICS_ICC); // ICC format differs
3134                         if(seekGraphUp)
3135                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3136                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3137                         looking_at(buf, &i, "*% "); // eat prompt
3138                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3139                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3140                         next_out = i; // suppress
3141                         continue;
3142                     }
3143                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3144                         char *p = star_match[0];
3145                         while(*p) {
3146                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3147                             while(*p && *p++ != ' '); // next
3148                         }
3149                         looking_at(buf, &i, "*% "); // eat prompt
3150                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3151                         next_out = i;
3152                         continue;
3153                     }
3154                 }
3155             }
3156
3157             /* skip formula vars */
3158             if (started == STARTED_NONE &&
3159                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3160               started = STARTED_CHATTER;
3161               i += 3;
3162               continue;
3163             }
3164
3165             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3166             if (appData.autoKibitz && started == STARTED_NONE &&
3167                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3168                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3169                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3170                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3171                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3172                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3173                         suppressKibitz = TRUE;
3174                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3175                         next_out = i;
3176                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3177                                 && (gameMode == IcsPlayingWhite)) ||
3178                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3179                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3180                             started = STARTED_CHATTER; // own kibitz we simply discard
3181                         else {
3182                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3183                             parse_pos = 0; parse[0] = NULLCHAR;
3184                             savingComment = TRUE;
3185                             suppressKibitz = gameMode != IcsObserving ? 2 :
3186                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3187                         }
3188                         continue;
3189                 } else
3190                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3191                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3192                          && atoi(star_match[0])) {
3193                     // suppress the acknowledgements of our own autoKibitz
3194                     char *p;
3195                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3196                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3197                     SendToPlayer(star_match[0], strlen(star_match[0]));
3198                     if(looking_at(buf, &i, "*% ")) // eat prompt
3199                         suppressKibitz = FALSE;
3200                     next_out = i;
3201                     continue;
3202                 }
3203             } // [HGM] kibitz: end of patch
3204
3205             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3206
3207             // [HGM] chat: intercept tells by users for which we have an open chat window
3208             channel = -1;
3209             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3210                                            looking_at(buf, &i, "* whispers:") ||
3211                                            looking_at(buf, &i, "* kibitzes:") ||
3212                                            looking_at(buf, &i, "* shouts:") ||
3213                                            looking_at(buf, &i, "* c-shouts:") ||
3214                                            looking_at(buf, &i, "--> * ") ||
3215                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3216                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3217                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3218                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3219                 int p;
3220                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3221                 chattingPartner = -1;
3222
3223                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3224                 for(p=0; p<MAX_CHAT; p++) {
3225                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3226                     talker[0] = '['; strcat(talker, "] ");
3227                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3228                     chattingPartner = p; break;
3229                     }
3230                 } else
3231                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3232                 for(p=0; p<MAX_CHAT; p++) {
3233                     if(!strcmp("kibitzes", chatPartner[p])) {
3234                         talker[0] = '['; strcat(talker, "] ");
3235                         chattingPartner = p; break;
3236                     }
3237                 } else
3238                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3239                 for(p=0; p<MAX_CHAT; p++) {
3240                     if(!strcmp("whispers", chatPartner[p])) {
3241                         talker[0] = '['; strcat(talker, "] ");
3242                         chattingPartner = p; break;
3243                     }
3244                 } else
3245                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3246                   if(buf[i-8] == '-' && buf[i-3] == 't')
3247                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3248                     if(!strcmp("c-shouts", chatPartner[p])) {
3249                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3250                         chattingPartner = p; break;
3251                     }
3252                   }
3253                   if(chattingPartner < 0)
3254                   for(p=0; p<MAX_CHAT; p++) {
3255                     if(!strcmp("shouts", chatPartner[p])) {
3256                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3257                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3258                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3259                         chattingPartner = p; break;
3260                     }
3261                   }
3262                 }
3263                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3264                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3265                     talker[0] = 0; Colorize(ColorTell, FALSE);
3266                     chattingPartner = p; break;
3267                 }
3268                 if(chattingPartner<0) i = oldi; else {
3269                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3270                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3271                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3272                     started = STARTED_COMMENT;
3273                     parse_pos = 0; parse[0] = NULLCHAR;
3274                     savingComment = 3 + chattingPartner; // counts as TRUE
3275                     suppressKibitz = TRUE;
3276                     continue;
3277                 }
3278             } // [HGM] chat: end of patch
3279
3280           backup = i;
3281             if (appData.zippyTalk || appData.zippyPlay) {
3282                 /* [DM] Backup address for color zippy lines */
3283 #if ZIPPY
3284                if (loggedOn == TRUE)
3285                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3286                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3287 #endif
3288             } // [DM] 'else { ' deleted
3289                 if (
3290                     /* Regular tells and says */
3291                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3292                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3293                     looking_at(buf, &i, "* says: ") ||
3294                     /* Don't color "message" or "messages" output */
3295                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3296                     looking_at(buf, &i, "*. * at *:*: ") ||
3297                     looking_at(buf, &i, "--* (*:*): ") ||
3298                     /* Message notifications (same color as tells) */
3299                     looking_at(buf, &i, "* has left a message ") ||
3300                     looking_at(buf, &i, "* just sent you a message:\n") ||
3301                     /* Whispers and kibitzes */
3302                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3303                     looking_at(buf, &i, "* kibitzes: ") ||
3304                     /* Channel tells */
3305                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3306
3307                   if (tkind == 1 && strchr(star_match[0], ':')) {
3308                       /* Avoid "tells you:" spoofs in channels */
3309                      tkind = 3;
3310                   }
3311                   if (star_match[0][0] == NULLCHAR ||
3312                       strchr(star_match[0], ' ') ||
3313                       (tkind == 3 && strchr(star_match[1], ' '))) {
3314                     /* Reject bogus matches */
3315                     i = oldi;
3316                   } else {
3317                     if (appData.colorize) {
3318                       if (oldi > next_out) {
3319                         SendToPlayer(&buf[next_out], oldi - next_out);
3320                         next_out = oldi;
3321                       }
3322                       switch (tkind) {
3323                       case 1:
3324                         Colorize(ColorTell, FALSE);
3325                         curColor = ColorTell;
3326                         break;
3327                       case 2:
3328                         Colorize(ColorKibitz, FALSE);
3329                         curColor = ColorKibitz;
3330                         break;
3331                       case 3:
3332                         p = strrchr(star_match[1], '(');
3333                         if (p == NULL) {
3334                           p = star_match[1];
3335                         } else {
3336                           p++;
3337                         }
3338                         if (atoi(p) == 1) {
3339                           Colorize(ColorChannel1, FALSE);
3340                           curColor = ColorChannel1;
3341                         } else {
3342                           Colorize(ColorChannel, FALSE);
3343                           curColor = ColorChannel;
3344                         }
3345                         break;
3346                       case 5:
3347                         curColor = ColorNormal;
3348                         break;
3349                       }
3350                     }
3351                     if (started == STARTED_NONE && appData.autoComment &&
3352                         (gameMode == IcsObserving ||
3353                          gameMode == IcsPlayingWhite ||
3354                          gameMode == IcsPlayingBlack)) {
3355                       parse_pos = i - oldi;
3356                       memcpy(parse, &buf[oldi], parse_pos);
3357                       parse[parse_pos] = NULLCHAR;
3358                       started = STARTED_COMMENT;
3359                       savingComment = TRUE;
3360                     } else {
3361                       started = STARTED_CHATTER;
3362                       savingComment = FALSE;
3363                     }
3364                     loggedOn = TRUE;
3365                     continue;
3366                   }
3367                 }
3368
3369                 if (looking_at(buf, &i, "* s-shouts: ") ||
3370                     looking_at(buf, &i, "* c-shouts: ")) {
3371                     if (appData.colorize) {
3372                         if (oldi > next_out) {
3373                             SendToPlayer(&buf[next_out], oldi - next_out);
3374                             next_out = oldi;
3375                         }
3376                         Colorize(ColorSShout, FALSE);
3377                         curColor = ColorSShout;
3378                     }
3379                     loggedOn = TRUE;
3380                     started = STARTED_CHATTER;
3381                     continue;
3382                 }
3383
3384                 if (looking_at(buf, &i, "--->")) {
3385                     loggedOn = TRUE;
3386                     continue;
3387                 }
3388
3389                 if (looking_at(buf, &i, "* shouts: ") ||
3390                     looking_at(buf, &i, "--> ")) {
3391                     if (appData.colorize) {
3392                         if (oldi > next_out) {
3393                             SendToPlayer(&buf[next_out], oldi - next_out);
3394                             next_out = oldi;
3395                         }
3396                         Colorize(ColorShout, FALSE);
3397                         curColor = ColorShout;
3398                     }
3399                     loggedOn = TRUE;
3400                     started = STARTED_CHATTER;
3401                     continue;
3402                 }
3403
3404                 if (looking_at( buf, &i, "Challenge:")) {
3405                     if (appData.colorize) {
3406                         if (oldi > next_out) {
3407                             SendToPlayer(&buf[next_out], oldi - next_out);
3408                             next_out = oldi;
3409                         }
3410                         Colorize(ColorChallenge, FALSE);
3411                         curColor = ColorChallenge;
3412                     }
3413                     loggedOn = TRUE;
3414                     continue;
3415                 }
3416
3417                 if (looking_at(buf, &i, "* offers you") ||
3418                     looking_at(buf, &i, "* offers to be") ||
3419                     looking_at(buf, &i, "* would like to") ||
3420                     looking_at(buf, &i, "* requests to") ||
3421                     looking_at(buf, &i, "Your opponent offers") ||
3422                     looking_at(buf, &i, "Your opponent requests")) {
3423
3424                     if (appData.colorize) {
3425                         if (oldi > next_out) {
3426                             SendToPlayer(&buf[next_out], oldi - next_out);
3427                             next_out = oldi;
3428                         }
3429                         Colorize(ColorRequest, FALSE);
3430                         curColor = ColorRequest;
3431                     }
3432                     continue;
3433                 }
3434
3435                 if (looking_at(buf, &i, "* (*) seeking")) {
3436                     if (appData.colorize) {
3437                         if (oldi > next_out) {
3438                             SendToPlayer(&buf[next_out], oldi - next_out);
3439                             next_out = oldi;
3440                         }
3441                         Colorize(ColorSeek, FALSE);
3442                         curColor = ColorSeek;
3443                     }
3444                     continue;
3445             }
3446
3447           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3448
3449             if (looking_at(buf, &i, "\\   ")) {
3450                 if (prevColor != ColorNormal) {
3451                     if (oldi > next_out) {
3452                         SendToPlayer(&buf[next_out], oldi - next_out);
3453                         next_out = oldi;
3454                     }
3455                     Colorize(prevColor, TRUE);
3456                     curColor = prevColor;
3457                 }
3458                 if (savingComment) {
3459                     parse_pos = i - oldi;
3460                     memcpy(parse, &buf[oldi], parse_pos);
3461                     parse[parse_pos] = NULLCHAR;
3462                     started = STARTED_COMMENT;
3463                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3464                         chattingPartner = savingComment - 3; // kludge to remember the box
3465                 } else {
3466                     started = STARTED_CHATTER;
3467                 }
3468                 continue;
3469             }
3470
3471             if (looking_at(buf, &i, "Black Strength :") ||
3472                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3473                 looking_at(buf, &i, "<10>") ||
3474                 looking_at(buf, &i, "#@#")) {
3475                 /* Wrong board style */
3476                 loggedOn = TRUE;
3477                 SendToICS(ics_prefix);
3478                 SendToICS("set style 12\n");
3479                 SendToICS(ics_prefix);
3480                 SendToICS("refresh\n");
3481                 continue;
3482             }
3483
3484             if (looking_at(buf, &i, "login:")) {
3485               if (!have_sent_ICS_logon) {
3486                 if(ICSInitScript())
3487                   have_sent_ICS_logon = 1;
3488                 else // no init script was found
3489                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3490               } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3491                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3492               }
3493                 continue;
3494             }
3495
3496             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3497                 (looking_at(buf, &i, "\n<12> ") ||
3498                  looking_at(buf, &i, "<12> "))) {
3499                 loggedOn = TRUE;
3500                 if (oldi > next_out) {
3501                     SendToPlayer(&buf[next_out], oldi - next_out);
3502                 }
3503                 next_out = i;
3504                 started = STARTED_BOARD;
3505                 parse_pos = 0;
3506                 continue;
3507             }
3508
3509             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3510                 looking_at(buf, &i, "<b1> ")) {
3511                 if (oldi > next_out) {
3512                     SendToPlayer(&buf[next_out], oldi - next_out);
3513                 }
3514                 next_out = i;
3515                 started = STARTED_HOLDINGS;
3516                 parse_pos = 0;
3517                 continue;
3518             }
3519
3520             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3521                 loggedOn = TRUE;
3522                 /* Header for a move list -- first line */
3523
3524                 switch (ics_getting_history) {
3525                   case H_FALSE:
3526                     switch (gameMode) {
3527                       case IcsIdle:
3528                       case BeginningOfGame:
3529                         /* User typed "moves" or "oldmoves" while we
3530                            were idle.  Pretend we asked for these
3531                            moves and soak them up so user can step
3532                            through them and/or save them.
3533                            */
3534                         Reset(FALSE, TRUE);
3535                         gameMode = IcsObserving;
3536                         ModeHighlight();
3537                         ics_gamenum = -1;
3538                         ics_getting_history = H_GOT_UNREQ_HEADER;
3539                         break;
3540                       case EditGame: /*?*/
3541                       case EditPosition: /*?*/
3542                         /* Should above feature work in these modes too? */
3543                         /* For now it doesn't */
3544                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3545                         break;
3546                       default:
3547                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3548                         break;
3549                     }
3550                     break;
3551                   case H_REQUESTED:
3552                     /* Is this the right one? */
3553                     if (gameInfo.white && gameInfo.black &&
3554                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3555                         strcmp(gameInfo.black, star_match[2]) == 0) {
3556                         /* All is well */
3557                         ics_getting_history = H_GOT_REQ_HEADER;
3558                     }
3559                     break;
3560                   case H_GOT_REQ_HEADER:
3561                   case H_GOT_UNREQ_HEADER:
3562                   case H_GOT_UNWANTED_HEADER:
3563                   case H_GETTING_MOVES:
3564                     /* Should not happen */
3565                     DisplayError(_("Error gathering move list: two headers"), 0);
3566                     ics_getting_history = H_FALSE;
3567                     break;
3568                 }
3569
3570                 /* Save player ratings into gameInfo if needed */
3571                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3572                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3573                     (gameInfo.whiteRating == -1 ||
3574                      gameInfo.blackRating == -1)) {
3575
3576                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3577                     gameInfo.blackRating = string_to_rating(star_match[3]);
3578                     if (appData.debugMode)
3579                       fprintf(debugFP, "Ratings from header: W %d, B %d\n",
3580                               gameInfo.whiteRating, gameInfo.blackRating);
3581                 }
3582                 continue;
3583             }
3584
3585             if (looking_at(buf, &i,
3586               "* * match, initial time: * minute*, increment: * second")) {
3587                 /* Header for a move list -- second line */
3588                 /* Initial board will follow if this is a wild game */
3589                 if (gameInfo.event != NULL) free(gameInfo.event);
3590                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3591                 gameInfo.event = StrSave(str);
3592                 /* [HGM] we switched variant. Translate boards if needed. */
3593                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3594                 continue;
3595             }
3596
3597             if (looking_at(buf, &i, "Move  ")) {
3598                 /* Beginning of a move list */
3599                 switch (ics_getting_history) {
3600                   case H_FALSE:
3601                     /* Normally should not happen */
3602                     /* Maybe user hit reset while we were parsing */
3603                     break;
3604                   case H_REQUESTED:
3605                     /* Happens if we are ignoring a move list that is not
3606                      * the one we just requested.  Common if the user
3607                      * tries to observe two games without turning off
3608                      * getMoveList */
3609                     break;
3610                   case H_GETTING_MOVES:
3611                     /* Should not happen */
3612                     DisplayError(_("Error gathering move list: nested"), 0);
3613                     ics_getting_history = H_FALSE;
3614                     break;
3615                   case H_GOT_REQ_HEADER:
3616                     ics_getting_history = H_GETTING_MOVES;
3617                     started = STARTED_MOVES;
3618                     parse_pos = 0;
3619                     if (oldi > next_out) {
3620                         SendToPlayer(&buf[next_out], oldi - next_out);
3621                     }
3622                     break;
3623                   case H_GOT_UNREQ_HEADER:
3624                     ics_getting_history = H_GETTING_MOVES;
3625                     started = STARTED_MOVES_NOHIDE;
3626                     parse_pos = 0;
3627                     break;
3628                   case H_GOT_UNWANTED_HEADER:
3629                     ics_getting_history = H_FALSE;
3630                     break;
3631                 }
3632                 continue;
3633             }
3634
3635             if (looking_at(buf, &i, "% ") ||
3636                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3637                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3638                 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3639                     soughtPending = FALSE;
3640                     seekGraphUp = TRUE;
3641                     DrawSeekGraph();
3642                 }
3643                 if(suppressKibitz) next_out = i;
3644                 savingComment = FALSE;
3645                 suppressKibitz = 0;
3646                 switch (started) {
3647                   case STARTED_MOVES:
3648                   case STARTED_MOVES_NOHIDE:
3649                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3650                     parse[parse_pos + i - oldi] = NULLCHAR;
3651                     ParseGameHistory(parse);
3652 #if ZIPPY
3653                     if (appData.zippyPlay && first.initDone) {
3654                         FeedMovesToProgram(&first, forwardMostMove);
3655                         if (gameMode == IcsPlayingWhite) {
3656                             if (WhiteOnMove(forwardMostMove)) {
3657                                 if (first.sendTime) {
3658                                   if (first.useColors) {
3659                                     SendToProgram("black\n", &first);
3660                                   }
3661                                   SendTimeRemaining(&first, TRUE);
3662                                 }
3663                                 if (first.useColors) {
3664                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3665                                 }
3666                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3667                                 first.maybeThinking = TRUE;
3668                             } else {
3669                                 if (first.usePlayother) {
3670                                   if (first.sendTime) {
3671                                     SendTimeRemaining(&first, TRUE);
3672                                   }
3673                                   SendToProgram("playother\n", &first);
3674                                   firstMove = FALSE;
3675                                 } else {
3676                                   firstMove = TRUE;
3677                                 }
3678                             }
3679                         } else if (gameMode == IcsPlayingBlack) {
3680                             if (!WhiteOnMove(forwardMostMove)) {
3681                                 if (first.sendTime) {
3682                                   if (first.useColors) {
3683                                     SendToProgram("white\n", &first);
3684                                   }
3685                                   SendTimeRemaining(&first, FALSE);
3686                                 }
3687                                 if (first.useColors) {
3688                                   SendToProgram("black\n", &first);
3689                                 }
3690                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3691                                 first.maybeThinking = TRUE;
3692                             } else {
3693                                 if (first.usePlayother) {
3694                                   if (first.sendTime) {
3695                                     SendTimeRemaining(&first, FALSE);
3696                                   }
3697                                   SendToProgram("playother\n", &first);
3698                                   firstMove = FALSE;
3699                                 } else {
3700                                   firstMove = TRUE;
3701                                 }
3702                             }
3703                         }
3704                     }
3705 #endif
3706                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3707                         /* Moves came from oldmoves or moves command
3708                            while we weren't doing anything else.
3709                            */
3710                         currentMove = forwardMostMove;
3711                         ClearHighlights();/*!!could figure this out*/
3712                         flipView = appData.flipView;
3713                         DrawPosition(TRUE, boards[currentMove]);
3714                         DisplayBothClocks();
3715                         snprintf(str, MSG_SIZ, "%s %s %s",
3716                                 gameInfo.white, _("vs."),  gameInfo.black);
3717                         DisplayTitle(str);
3718                         gameMode = IcsIdle;
3719                     } else {
3720                         /* Moves were history of an active game */
3721                         if (gameInfo.resultDetails != NULL) {
3722                             free(gameInfo.resultDetails);
3723                             gameInfo.resultDetails = NULL;
3724                         }
3725                     }
3726                     HistorySet(parseList, backwardMostMove,
3727                                forwardMostMove, currentMove-1);
3728                     DisplayMove(currentMove - 1);
3729                     if (started == STARTED_MOVES) next_out = i;
3730                     started = STARTED_NONE;
3731                     ics_getting_history = H_FALSE;
3732                     break;
3733
3734                   case STARTED_OBSERVE:
3735                     started = STARTED_NONE;
3736                     SendToICS(ics_prefix);
3737                     SendToICS("refresh\n");
3738                     break;
3739
3740                   default:
3741                     break;
3742                 }
3743                 if(bookHit) { // [HGM] book: simulate book reply
3744                     static char bookMove[MSG_SIZ]; // a bit generous?
3745
3746                     programStats.nodes = programStats.depth = programStats.time =
3747                     programStats.score = programStats.got_only_move = 0;
3748                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3749
3750                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3751                     strcat(bookMove, bookHit);
3752                     HandleMachineMove(bookMove, &first);
3753                 }
3754                 continue;
3755             }
3756
3757             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3758                  started == STARTED_HOLDINGS ||
3759                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3760                 /* Accumulate characters in move list or board */
3761                 parse[parse_pos++] = buf[i];
3762             }
3763
3764             /* Start of game messages.  Mostly we detect start of game
3765                when the first board image arrives.  On some versions
3766                of the ICS, though, we need to do a "refresh" after starting
3767                to observe in order to get the current board right away. */
3768             if (looking_at(buf, &i, "Adding game * to observation list")) {
3769                 started = STARTED_OBSERVE;
3770                 continue;
3771             }
3772
3773             /* Handle auto-observe */
3774             if (appData.autoObserve &&
3775                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3776                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3777                 char *player;
3778                 /* Choose the player that was highlighted, if any. */
3779                 if (star_match[0][0] == '\033' ||
3780                     star_match[1][0] != '\033') {
3781                     player = star_match[0];
3782                 } else {
3783                     player = star_match[2];
3784                 }
3785                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3786                         ics_prefix, StripHighlightAndTitle(player));
3787                 SendToICS(str);
3788
3789                 /* Save ratings from notify string */
3790                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3791                 player1Rating = string_to_rating(star_match[1]);
3792                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3793                 player2Rating = string_to_rating(star_match[3]);
3794
3795                 if (appData.debugMode)
3796                   fprintf(debugFP,
3797                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3798                           player1Name, player1Rating,
3799                           player2Name, player2Rating);
3800
3801                 continue;
3802             }
3803
3804             /* Deal with automatic examine mode after a game,
3805                and with IcsObserving -> IcsExamining transition */
3806             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3807                 looking_at(buf, &i, "has made you an examiner of game *")) {
3808
3809                 int gamenum = atoi(star_match[0]);
3810                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3811                     gamenum == ics_gamenum) {
3812                     /* We were already playing or observing this game;
3813                        no need to refetch history */
3814                     gameMode = IcsExamining;
3815                     if (pausing) {
3816                         pauseExamForwardMostMove = forwardMostMove;
3817                     } else if (currentMove < forwardMostMove) {
3818                         ForwardInner(forwardMostMove);
3819                     }
3820                 } else {
3821                     /* I don't think this case really can happen */
3822                     SendToICS(ics_prefix);
3823                     SendToICS("refresh\n");
3824                 }
3825                 continue;
3826             }
3827
3828             /* Error messages */
3829 //          if (ics_user_moved) {
3830             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3831                 if (looking_at(buf, &i, "Illegal move") ||
3832                     looking_at(buf, &i, "Not a legal move") ||
3833                     looking_at(buf, &i, "Your king is in check") ||
3834                     looking_at(buf, &i, "It isn't your turn") ||
3835                     looking_at(buf, &i, "It is not your move")) {
3836                     /* Illegal move */
3837                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3838                         currentMove = forwardMostMove-1;
3839                         DisplayMove(currentMove - 1); /* before DMError */
3840                         DrawPosition(FALSE, boards[currentMove]);
3841                         SwitchClocks(forwardMostMove-1); // [HGM] race
3842                         DisplayBothClocks();
3843                     }
3844                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3845                     ics_user_moved = 0;
3846                     continue;
3847                 }
3848             }
3849
3850             if (looking_at(buf, &i, "still have time") ||
3851                 looking_at(buf, &i, "not out of time") ||
3852                 looking_at(buf, &i, "either player is out of time") ||
3853                 looking_at(buf, &i, "has timeseal; checking")) {
3854                 /* We must have called his flag a little too soon */
3855                 whiteFlag = blackFlag = FALSE;
3856                 continue;
3857             }
3858
3859             if (looking_at(buf, &i, "added * seconds to") ||
3860                 looking_at(buf, &i, "seconds were added to")) {
3861                 /* Update the clocks */
3862                 SendToICS(ics_prefix);
3863                 SendToICS("refresh\n");
3864                 continue;
3865             }
3866
3867             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3868                 ics_clock_paused = TRUE;
3869                 StopClocks();
3870                 continue;
3871             }
3872
3873             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3874                 ics_clock_paused = FALSE;
3875                 StartClocks();
3876                 continue;
3877             }
3878
3879             /* Grab player ratings from the Creating: message.
3880                Note we have to check for the special case when
3881                the ICS inserts things like [white] or [black]. */
3882             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3883                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3884                 /* star_matches:
3885                    0    player 1 name (not necessarily white)
3886                    1    player 1 rating
3887                    2    empty, white, or black (IGNORED)
3888                    3    player 2 name (not necessarily black)
3889                    4    player 2 rating
3890
3891                    The names/ratings are sorted out when the game
3892                    actually starts (below).
3893                 */
3894                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3895                 player1Rating = string_to_rating(star_match[1]);
3896                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3897                 player2Rating = string_to_rating(star_match[4]);
3898
3899                 if (appData.debugMode)
3900                   fprintf(debugFP,
3901                           "Ratings from 'Creating:' %s %d, %s %d\n",
3902                           player1Name, player1Rating,
3903                           player2Name, player2Rating);
3904
3905                 continue;
3906             }
3907
3908             /* Improved generic start/end-of-game messages */
3909             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3910                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3911                 /* If tkind == 0: */
3912                 /* star_match[0] is the game number */
3913                 /*           [1] is the white player's name */
3914                 /*           [2] is the black player's name */
3915                 /* For end-of-game: */
3916                 /*           [3] is the reason for the game end */
3917                 /*           [4] is a PGN end game-token, preceded by " " */
3918                 /* For start-of-game: */
3919                 /*           [3] begins with "Creating" or "Continuing" */
3920                 /*           [4] is " *" or empty (don't care). */
3921                 int gamenum = atoi(star_match[0]);
3922                 char *whitename, *blackname, *why, *endtoken;
3923                 ChessMove endtype = EndOfFile;
3924
3925                 if (tkind == 0) {
3926                   whitename = star_match[1];
3927                   blackname = star_match[2];
3928                   why = star_match[3];
3929                   endtoken = star_match[4];
3930                 } else {
3931                   whitename = star_match[1];
3932                   blackname = star_match[3];
3933                   why = star_match[5];
3934                   endtoken = star_match[6];
3935                 }
3936
3937                 /* Game start messages */
3938                 if (strncmp(why, "Creating ", 9) == 0 ||
3939                     strncmp(why, "Continuing ", 11) == 0) {
3940                     gs_gamenum = gamenum;
3941                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3942                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
3943                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3944 #if ZIPPY
3945                     if (appData.zippyPlay) {
3946                         ZippyGameStart(whitename, blackname);
3947                     }
3948 #endif /*ZIPPY*/
3949                     partnerBoardValid = FALSE; // [HGM] bughouse
3950                     continue;
3951                 }
3952
3953                 /* Game end messages */
3954                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3955                     ics_gamenum != gamenum) {
3956                     continue;
3957                 }
3958                 while (endtoken[0] == ' ') endtoken++;
3959                 switch (endtoken[0]) {
3960                   case '*':
3961                   default:
3962                     endtype = GameUnfinished;
3963                     break;
3964                   case '0':
3965                     endtype = BlackWins;
3966                     break;
3967                   case '1':
3968                     if (endtoken[1] == '/')
3969                       endtype = GameIsDrawn;
3970                     else
3971                       endtype = WhiteWins;
3972                     break;
3973                 }
3974                 GameEnds(endtype, why, GE_ICS);
3975 #if ZIPPY
3976                 if (appData.zippyPlay && first.initDone) {
3977                     ZippyGameEnd(endtype, why);
3978                     if (first.pr == NoProc) {
3979                       /* Start the next process early so that we'll
3980                          be ready for the next challenge */
3981                       StartChessProgram(&first);
3982                     }
3983                     /* Send "new" early, in case this command takes
3984                        a long time to finish, so that we'll be ready
3985                        for the next challenge. */
3986                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3987                     Reset(TRUE, TRUE);
3988                 }
3989 #endif /*ZIPPY*/
3990                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3991                 continue;
3992             }
3993
3994             if (looking_at(buf, &i, "Removing game * from observation") ||
3995                 looking_at(buf, &i, "no longer observing game *") ||
3996                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3997                 if (gameMode == IcsObserving &&
3998                     atoi(star_match[0]) == ics_gamenum)
3999                   {
4000                       /* icsEngineAnalyze */
4001                       if (appData.icsEngineAnalyze) {
4002                             ExitAnalyzeMode();
4003                             ModeHighlight();
4004                       }
4005                       StopClocks();
4006                       gameMode = IcsIdle;
4007                       ics_gamenum = -1;
4008                       ics_user_moved = FALSE;
4009                   }
4010                 continue;
4011             }
4012
4013             if (looking_at(buf, &i, "no longer examining game *")) {
4014                 if (gameMode == IcsExamining &&
4015                     atoi(star_match[0]) == ics_gamenum)
4016                   {
4017                       gameMode = IcsIdle;
4018                       ics_gamenum = -1;
4019                       ics_user_moved = FALSE;
4020                   }
4021                 continue;
4022             }
4023
4024             /* Advance leftover_start past any newlines we find,
4025                so only partial lines can get reparsed */
4026             if (looking_at(buf, &i, "\n")) {
4027                 prevColor = curColor;
4028                 if (curColor != ColorNormal) {
4029                     if (oldi > next_out) {
4030                         SendToPlayer(&buf[next_out], oldi - next_out);
4031                         next_out = oldi;
4032                     }
4033                     Colorize(ColorNormal, FALSE);
4034                     curColor = ColorNormal;
4035                 }
4036                 if (started == STARTED_BOARD) {
4037                     started = STARTED_NONE;
4038                     parse[parse_pos] = NULLCHAR;
4039                     ParseBoard12(parse);
4040                     ics_user_moved = 0;
4041
4042                     /* Send premove here */
4043                     if (appData.premove) {
4044                       char str[MSG_SIZ];
4045                       if (currentMove == 0 &&
4046                           gameMode == IcsPlayingWhite &&
4047                           appData.premoveWhite) {
4048                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
4049                         if (appData.debugMode)
4050                           fprintf(debugFP, "Sending premove:\n");
4051                         SendToICS(str);
4052                       } else if (currentMove == 1 &&
4053                                  gameMode == IcsPlayingBlack &&
4054                                  appData.premoveBlack) {
4055                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
4056                         if (appData.debugMode)
4057                           fprintf(debugFP, "Sending premove:\n");
4058                         SendToICS(str);
4059                       } else if (gotPremove) {
4060                         gotPremove = 0;
4061                         ClearPremoveHighlights();
4062                         if (appData.debugMode)
4063                           fprintf(debugFP, "Sending premove:\n");
4064                           UserMoveEvent(premoveFromX, premoveFromY,
4065                                         premoveToX, premoveToY,
4066                                         premovePromoChar);
4067                       }
4068                     }
4069
4070                     /* Usually suppress following prompt */
4071                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4072                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4073                         if (looking_at(buf, &i, "*% ")) {
4074                             savingComment = FALSE;
4075                             suppressKibitz = 0;
4076                         }
4077                     }
4078                     next_out = i;
4079                 } else if (started == STARTED_HOLDINGS) {
4080                     int gamenum;
4081                     char new_piece[MSG_SIZ];
4082                     started = STARTED_NONE;
4083                     parse[parse_pos] = NULLCHAR;
4084                     if (appData.debugMode)
4085                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4086                                                         parse, currentMove);
4087                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4088                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4089                         if (gameInfo.variant == VariantNormal) {
4090                           /* [HGM] We seem to switch variant during a game!
4091                            * Presumably no holdings were displayed, so we have
4092                            * to move the position two files to the right to
4093                            * create room for them!
4094                            */
4095                           VariantClass newVariant;
4096                           switch(gameInfo.boardWidth) { // base guess on board width
4097                                 case 9:  newVariant = VariantShogi; break;
4098                                 case 10: newVariant = VariantGreat; break;
4099                                 default: newVariant = VariantCrazyhouse; break;
4100                           }
4101                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4102                           /* Get a move list just to see the header, which
4103                              will tell us whether this is really bug or zh */
4104                           if (ics_getting_history == H_FALSE) {
4105                             ics_getting_history = H_REQUESTED;
4106                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4107                             SendToICS(str);
4108                           }
4109                         }
4110                         new_piece[0] = NULLCHAR;
4111                         sscanf(parse, "game %d white [%s black [%s <- %s",
4112                                &gamenum, white_holding, black_holding,
4113                                new_piece);
4114                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4115                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4116                         /* [HGM] copy holdings to board holdings area */
4117                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4118                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4119                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4120 #if ZIPPY
4121                         if (appData.zippyPlay && first.initDone) {
4122                             ZippyHoldings(white_holding, black_holding,
4123                                           new_piece);
4124                         }
4125 #endif /*ZIPPY*/
4126                         if (tinyLayout || smallLayout) {
4127                             char wh[16], bh[16];
4128                             PackHolding(wh, white_holding);
4129                             PackHolding(bh, black_holding);
4130                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4131                                     gameInfo.white, gameInfo.black);
4132                         } else {
4133                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4134                                     gameInfo.white, white_holding, _("vs."),
4135                                     gameInfo.black, black_holding);
4136                         }
4137                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4138                         DrawPosition(FALSE, boards[currentMove]);
4139                         DisplayTitle(str);
4140                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4141                         sscanf(parse, "game %d white [%s black [%s <- %s",
4142                                &gamenum, white_holding, black_holding,
4143                                new_piece);
4144                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4145                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4146                         /* [HGM] copy holdings to partner-board holdings area */
4147                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4148                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4149                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4150                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4151                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4152                       }
4153                     }
4154                     /* Suppress following prompt */
4155                     if (looking_at(buf, &i, "*% ")) {
4156                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4157                         savingComment = FALSE;
4158                         suppressKibitz = 0;
4159                     }
4160                     next_out = i;
4161                 }
4162                 continue;
4163             }
4164
4165             i++;                /* skip unparsed character and loop back */
4166         }
4167
4168         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4169 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4170 //          SendToPlayer(&buf[next_out], i - next_out);
4171             started != STARTED_HOLDINGS && leftover_start > next_out) {
4172             SendToPlayer(&buf[next_out], leftover_start - next_out);
4173             next_out = i;
4174         }
4175
4176         leftover_len = buf_len - leftover_start;
4177         /* if buffer ends with something we couldn't parse,
4178            reparse it after appending the next read */
4179
4180     } else if (count == 0) {
4181         RemoveInputSource(isr);
4182         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4183     } else {
4184         DisplayFatalError(_("Error reading from ICS"), error, 1);
4185     }
4186 }
4187
4188
4189 /* Board style 12 looks like this:
4190
4191    <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
4192
4193  * The "<12> " is stripped before it gets to this routine.  The two
4194  * trailing 0's (flip state and clock ticking) are later addition, and
4195  * some chess servers may not have them, or may have only the first.
4196  * Additional trailing fields may be added in the future.
4197  */
4198
4199 #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"
4200
4201 #define RELATION_OBSERVING_PLAYED    0
4202 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4203 #define RELATION_PLAYING_MYMOVE      1
4204 #define RELATION_PLAYING_NOTMYMOVE  -1
4205 #define RELATION_EXAMINING           2
4206 #define RELATION_ISOLATED_BOARD     -3
4207 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4208
4209 void
4210 ParseBoard12 (char *string)
4211 {
4212 #if ZIPPY
4213     int i, takeback;
4214     char *bookHit = NULL; // [HGM] book
4215 #endif
4216     GameMode newGameMode;
4217     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
4218     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
4219     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4220     char to_play, board_chars[200];
4221     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4222     char black[32], white[32];
4223     Board board;
4224     int prevMove = currentMove;
4225     int ticking = 2;
4226     ChessMove moveType;
4227     int fromX, fromY, toX, toY;
4228     char promoChar;
4229     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4230     Boolean weird = FALSE, reqFlag = FALSE;
4231
4232     fromX = fromY = toX = toY = -1;
4233
4234     newGame = FALSE;
4235
4236     if (appData.debugMode)
4237       fprintf(debugFP, "Parsing board: %s\n", string);
4238
4239     move_str[0] = NULLCHAR;
4240     elapsed_time[0] = NULLCHAR;
4241     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4242         int  i = 0, j;
4243         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4244             if(string[i] == ' ') { ranks++; files = 0; }
4245             else files++;
4246             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4247             i++;
4248         }
4249         for(j = 0; j <i; j++) board_chars[j] = string[j];
4250         board_chars[i] = '\0';
4251         string += i + 1;
4252     }
4253     n = sscanf(string, PATTERN, &to_play, &double_push,
4254                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4255                &gamenum, white, black, &relation, &basetime, &increment,
4256                &white_stren, &black_stren, &white_time, &black_time,
4257                &moveNum, str, elapsed_time, move_str, &ics_flip,
4258                &ticking);
4259
4260     if (n < 21) {
4261         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4262         DisplayError(str, 0);
4263         return;
4264     }
4265
4266     /* Convert the move number to internal form */
4267     moveNum = (moveNum - 1) * 2;
4268     if (to_play == 'B') moveNum++;
4269     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4270       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4271                         0, 1);
4272       return;
4273     }
4274
4275     switch (relation) {
4276       case RELATION_OBSERVING_PLAYED:
4277       case RELATION_OBSERVING_STATIC:
4278         if (gamenum == -1) {
4279             /* Old ICC buglet */
4280             relation = RELATION_OBSERVING_STATIC;
4281         }
4282         newGameMode = IcsObserving;
4283         break;
4284       case RELATION_PLAYING_MYMOVE:
4285       case RELATION_PLAYING_NOTMYMOVE:
4286         newGameMode =
4287           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4288             IcsPlayingWhite : IcsPlayingBlack;
4289         soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4290         break;
4291       case RELATION_EXAMINING:
4292         newGameMode = IcsExamining;
4293         break;
4294       case RELATION_ISOLATED_BOARD:
4295       default:
4296         /* Just display this board.  If user was doing something else,
4297            we will forget about it until the next board comes. */
4298         newGameMode = IcsIdle;
4299         break;
4300       case RELATION_STARTING_POSITION:
4301         newGameMode = gameMode;
4302         break;
4303     }
4304
4305     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4306         gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4307          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4308       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4309       int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4310       static int lastBgGame = -1;
4311       char *toSqr;
4312       for (k = 0; k < ranks; k++) {
4313         for (j = 0; j < files; j++)
4314           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4315         if(gameInfo.holdingsWidth > 1) {
4316              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4317              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4318         }
4319       }
4320       CopyBoard(partnerBoard, board);
4321       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4322         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4323         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4324       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4325       if(toSqr = strchr(str, '-')) {
4326         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4327         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4328       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4329       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4330       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4331       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4332       if(twoBoards) {
4333           DisplayWhiteClock(white_time*fac, to_play == 'W');
4334           DisplayBlackClock(black_time*fac, to_play != 'W');
4335           activePartner = to_play;
4336           if(gamenum != lastBgGame) {
4337               char buf[MSG_SIZ];
4338               snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4339               DisplayTitle(buf);
4340           }
4341           lastBgGame = gamenum;
4342           activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4343                       partnerUp = 0; flipView = !flipView; } // [HGM] dual
4344       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4345                  (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4346       if(!twoBoards) DisplayMessage(partnerStatus, "");
4347         partnerBoardValid = TRUE;
4348       return;
4349     }
4350
4351     if(appData.dualBoard && appData.bgObserve) {
4352         if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4353             SendToICS(ics_prefix), SendToICS("pobserve\n");
4354         else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4355             char buf[MSG_SIZ];
4356             snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4357             SendToICS(buf);
4358         }
4359     }
4360
4361     /* Modify behavior for initial board display on move listing
4362        of wild games.
4363        */
4364     switch (ics_getting_history) {
4365       case H_FALSE:
4366       case H_REQUESTED:
4367         break;
4368       case H_GOT_REQ_HEADER:
4369       case H_GOT_UNREQ_HEADER:
4370         /* This is the initial position of the current game */
4371         gamenum = ics_gamenum;
4372         moveNum = 0;            /* old ICS bug workaround */
4373         if (to_play == 'B') {
4374           startedFromSetupPosition = TRUE;
4375           blackPlaysFirst = TRUE;
4376           moveNum = 1;
4377           if (forwardMostMove == 0) forwardMostMove = 1;
4378           if (backwardMostMove == 0) backwardMostMove = 1;
4379           if (currentMove == 0) currentMove = 1;
4380         }
4381         newGameMode = gameMode;
4382         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4383         break;
4384       case H_GOT_UNWANTED_HEADER:
4385         /* This is an initial board that we don't want */
4386         return;
4387       case H_GETTING_MOVES:
4388         /* Should not happen */
4389         DisplayError(_("Error gathering move list: extra board"), 0);
4390         ics_getting_history = H_FALSE;
4391         return;
4392     }
4393
4394    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4395                                         move_str[1] == '@' && !gameInfo.holdingsWidth ||
4396                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4397      /* [HGM] We seem to have switched variant unexpectedly
4398       * Try to guess new variant from board size
4399       */
4400           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4401           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4402           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4403           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4404           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4405           if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4406           if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4407           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4408           /* Get a move list just to see the header, which
4409              will tell us whether this is really bug or zh */
4410           if (ics_getting_history == H_FALSE) {
4411             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4412             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4413             SendToICS(str);
4414           }
4415     }
4416
4417     /* Take action if this is the first board of a new game, or of a
4418        different game than is currently being displayed.  */
4419     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4420         relation == RELATION_ISOLATED_BOARD) {
4421
4422         /* Forget the old game and get the history (if any) of the new one */
4423         if (gameMode != BeginningOfGame) {
4424           Reset(TRUE, TRUE);
4425         }
4426         newGame = TRUE;
4427         if (appData.autoRaiseBoard) BoardToTop();
4428         prevMove = -3;
4429         if (gamenum == -1) {
4430             newGameMode = IcsIdle;
4431         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4432                    appData.getMoveList && !reqFlag) {
4433             /* Need to get game history */
4434             ics_getting_history = H_REQUESTED;
4435             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4436             SendToICS(str);
4437         }
4438
4439         /* Initially flip the board to have black on the bottom if playing
4440            black or if the ICS flip flag is set, but let the user change
4441            it with the Flip View button. */
4442         flipView = appData.autoFlipView ?
4443           (newGameMode == IcsPlayingBlack) || ics_flip :
4444           appData.flipView;
4445
4446         /* Done with values from previous mode; copy in new ones */
4447         gameMode = newGameMode;
4448         ModeHighlight();
4449         ics_gamenum = gamenum;
4450         if (gamenum == gs_gamenum) {
4451             int klen = strlen(gs_kind);
4452             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4453             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4454             gameInfo.event = StrSave(str);
4455         } else {
4456             gameInfo.event = StrSave("ICS game");
4457         }
4458         gameInfo.site = StrSave(appData.icsHost);
4459         gameInfo.date = PGNDate();
4460         gameInfo.round = StrSave("-");
4461         gameInfo.white = StrSave(white);
4462         gameInfo.black = StrSave(black);
4463         timeControl = basetime * 60 * 1000;
4464         timeControl_2 = 0;
4465         timeIncrement = increment * 1000;
4466         movesPerSession = 0;
4467         gameInfo.timeControl = TimeControlTagValue();
4468         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4469   if (appData.debugMode) {
4470     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4471     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4472     setbuf(debugFP, NULL);
4473   }
4474
4475         gameInfo.outOfBook = NULL;
4476
4477         /* Do we have the ratings? */
4478         if (strcmp(player1Name, white) == 0 &&
4479             strcmp(player2Name, black) == 0) {
4480             if (appData.debugMode)
4481               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4482                       player1Rating, player2Rating);
4483             gameInfo.whiteRating = player1Rating;
4484             gameInfo.blackRating = player2Rating;
4485         } else if (strcmp(player2Name, white) == 0 &&
4486                    strcmp(player1Name, black) == 0) {
4487             if (appData.debugMode)
4488               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4489                       player2Rating, player1Rating);
4490             gameInfo.whiteRating = player2Rating;
4491             gameInfo.blackRating = player1Rating;
4492         }
4493         player1Name[0] = player2Name[0] = NULLCHAR;
4494
4495         /* Silence shouts if requested */
4496         if (appData.quietPlay &&
4497             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4498             SendToICS(ics_prefix);
4499             SendToICS("set shout 0\n");
4500         }
4501     }
4502
4503     /* Deal with midgame name changes */
4504     if (!newGame) {
4505         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4506             if (gameInfo.white) free(gameInfo.white);
4507             gameInfo.white = StrSave(white);
4508         }
4509         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4510             if (gameInfo.black) free(gameInfo.black);
4511             gameInfo.black = StrSave(black);
4512         }
4513     }
4514
4515     /* Throw away game result if anything actually changes in examine mode */
4516     if (gameMode == IcsExamining && !newGame) {
4517         gameInfo.result = GameUnfinished;
4518         if (gameInfo.resultDetails != NULL) {
4519             free(gameInfo.resultDetails);
4520             gameInfo.resultDetails = NULL;
4521         }
4522     }
4523
4524     /* In pausing && IcsExamining mode, we ignore boards coming
4525        in if they are in a different variation than we are. */
4526     if (pauseExamInvalid) return;
4527     if (pausing && gameMode == IcsExamining) {
4528         if (moveNum <= pauseExamForwardMostMove) {
4529             pauseExamInvalid = TRUE;
4530             forwardMostMove = pauseExamForwardMostMove;
4531             return;
4532         }
4533     }
4534
4535   if (appData.debugMode) {
4536     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4537   }
4538     /* Parse the board */
4539     for (k = 0; k < ranks; k++) {
4540       for (j = 0; j < files; j++)
4541         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4542       if(gameInfo.holdingsWidth > 1) {
4543            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4544            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4545       }
4546     }
4547     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4548       board[5][BOARD_RGHT+1] = WhiteAngel;
4549       board[6][BOARD_RGHT+1] = WhiteMarshall;
4550       board[1][0] = BlackMarshall;
4551       board[2][0] = BlackAngel;
4552       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4553     }
4554     CopyBoard(boards[moveNum], board);
4555     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4556     if (moveNum == 0) {
4557         startedFromSetupPosition =
4558           !CompareBoards(board, initialPosition);
4559         if(startedFromSetupPosition)
4560             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4561     }
4562
4563     /* [HGM] Set castling rights. Take the outermost Rooks,
4564        to make it also work for FRC opening positions. Note that board12
4565        is really defective for later FRC positions, as it has no way to
4566        indicate which Rook can castle if they are on the same side of King.
4567        For the initial position we grant rights to the outermost Rooks,
4568        and remember thos rights, and we then copy them on positions
4569        later in an FRC game. This means WB might not recognize castlings with
4570        Rooks that have moved back to their original position as illegal,
4571        but in ICS mode that is not its job anyway.
4572     */
4573     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4574     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4575
4576         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4577             if(board[0][i] == WhiteRook) j = i;
4578         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4579         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4580             if(board[0][i] == WhiteRook) j = i;
4581         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4582         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4583             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4584         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4585         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4586             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4587         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4588
4589         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4590         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4591         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4592             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4593         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4594             if(board[BOARD_HEIGHT-1][k] == bKing)
4595                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4596         if(gameInfo.variant == VariantTwoKings) {
4597             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4598             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4599             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4600         }
4601     } else { int r;
4602         r = boards[moveNum][CASTLING][0] = initialRights[0];
4603         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4604         r = boards[moveNum][CASTLING][1] = initialRights[1];
4605         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4606         r = boards[moveNum][CASTLING][3] = initialRights[3];
4607         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4608         r = boards[moveNum][CASTLING][4] = initialRights[4];
4609         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4610         /* wildcastle kludge: always assume King has rights */
4611         r = boards[moveNum][CASTLING][2] = initialRights[2];
4612         r = boards[moveNum][CASTLING][5] = initialRights[5];
4613     }
4614     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4615     boards[moveNum][EP_STATUS] = EP_NONE;
4616     if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4617     if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4618     if(double_push !=  -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4619
4620
4621     if (ics_getting_history == H_GOT_REQ_HEADER ||
4622         ics_getting_history == H_GOT_UNREQ_HEADER) {
4623         /* This was an initial position from a move list, not
4624            the current position */
4625         return;
4626     }
4627
4628     /* Update currentMove and known move number limits */
4629     newMove = newGame || moveNum > forwardMostMove;
4630
4631     if (newGame) {
4632         forwardMostMove = backwardMostMove = currentMove = moveNum;
4633         if (gameMode == IcsExamining && moveNum == 0) {
4634           /* Workaround for ICS limitation: we are not told the wild
4635              type when starting to examine a game.  But if we ask for
4636              the move list, the move list header will tell us */
4637             ics_getting_history = H_REQUESTED;
4638             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4639             SendToICS(str);
4640         }
4641     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4642                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4643 #if ZIPPY
4644         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4645         /* [HGM] applied this also to an engine that is silently watching        */
4646         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4647             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4648             gameInfo.variant == currentlyInitializedVariant) {
4649           takeback = forwardMostMove - moveNum;
4650           for (i = 0; i < takeback; i++) {
4651             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4652             SendToProgram("undo\n", &first);
4653           }
4654         }
4655 #endif
4656
4657         forwardMostMove = moveNum;
4658         if (!pausing || currentMove > forwardMostMove)
4659           currentMove = forwardMostMove;
4660     } else {
4661         /* New part of history that is not contiguous with old part */
4662         if (pausing && gameMode == IcsExamining) {
4663             pauseExamInvalid = TRUE;
4664             forwardMostMove = pauseExamForwardMostMove;
4665             return;
4666         }
4667         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4668 #if ZIPPY
4669             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4670                 // [HGM] when we will receive the move list we now request, it will be
4671                 // fed to the engine from the first move on. So if the engine is not
4672                 // in the initial position now, bring it there.
4673                 InitChessProgram(&first, 0);
4674             }
4675 #endif
4676             ics_getting_history = H_REQUESTED;
4677             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4678             SendToICS(str);
4679         }
4680         forwardMostMove = backwardMostMove = currentMove = moveNum;
4681     }
4682
4683     /* Update the clocks */
4684     if (strchr(elapsed_time, '.')) {
4685       /* Time is in ms */
4686       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4687       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4688     } else {
4689       /* Time is in seconds */
4690       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4691       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4692     }
4693
4694
4695 #if ZIPPY
4696     if (appData.zippyPlay && newGame &&
4697         gameMode != IcsObserving && gameMode != IcsIdle &&
4698         gameMode != IcsExamining)
4699       ZippyFirstBoard(moveNum, basetime, increment);
4700 #endif
4701
4702     /* Put the move on the move list, first converting
4703        to canonical algebraic form. */
4704     if (moveNum > 0) {
4705   if (appData.debugMode) {
4706     int f = forwardMostMove;
4707     fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4708             boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4709             boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4710     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4711     fprintf(debugFP, "moveNum = %d\n", moveNum);
4712     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4713     setbuf(debugFP, NULL);
4714   }
4715         if (moveNum <= backwardMostMove) {
4716             /* We don't know what the board looked like before
4717                this move.  Punt. */
4718           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4719             strcat(parseList[moveNum - 1], " ");
4720             strcat(parseList[moveNum - 1], elapsed_time);
4721             moveList[moveNum - 1][0] = NULLCHAR;
4722         } else if (strcmp(move_str, "none") == 0) {
4723             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4724             /* Again, we don't know what the board looked like;
4725                this is really the start of the game. */
4726             parseList[moveNum - 1][0] = NULLCHAR;
4727             moveList[moveNum - 1][0] = NULLCHAR;
4728             backwardMostMove = moveNum;
4729             startedFromSetupPosition = TRUE;
4730             fromX = fromY = toX = toY = -1;
4731         } else {
4732           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4733           //                 So we parse the long-algebraic move string in stead of the SAN move
4734           int valid; char buf[MSG_SIZ], *prom;
4735
4736           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4737                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4738           // str looks something like "Q/a1-a2"; kill the slash
4739           if(str[1] == '/')
4740             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4741           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4742           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4743                 strcat(buf, prom); // long move lacks promo specification!
4744           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4745                 if(appData.debugMode)
4746                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4747                 safeStrCpy(move_str, buf, MSG_SIZ);
4748           }
4749           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4750                                 &fromX, &fromY, &toX, &toY, &promoChar)
4751                || ParseOneMove(buf, moveNum - 1, &moveType,
4752                                 &fromX, &fromY, &toX, &toY, &promoChar);
4753           // end of long SAN patch
4754           if (valid) {
4755             (void) CoordsToAlgebraic(boards[moveNum - 1],
4756                                      PosFlags(moveNum - 1),
4757                                      fromY, fromX, toY, toX, promoChar,
4758                                      parseList[moveNum-1]);
4759             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4760               case MT_NONE:
4761               case MT_STALEMATE:
4762               default:
4763                 break;
4764               case MT_CHECK:
4765                 if(gameInfo.variant != VariantShogi)
4766                     strcat(parseList[moveNum - 1], "+");
4767                 break;
4768               case MT_CHECKMATE:
4769               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4770                 strcat(parseList[moveNum - 1], "#");
4771                 break;
4772             }
4773             strcat(parseList[moveNum - 1], " ");
4774             strcat(parseList[moveNum - 1], elapsed_time);
4775             /* currentMoveString is set as a side-effect of ParseOneMove */
4776             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4777             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4778             strcat(moveList[moveNum - 1], "\n");
4779
4780             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4781                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4782               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4783                 ChessSquare old, new = boards[moveNum][k][j];
4784                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4785                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4786                   if(old == new) continue;
4787                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4788                   else if(new == WhiteWazir || new == BlackWazir) {
4789                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4790                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4791                       else boards[moveNum][k][j] = old; // preserve type of Gold
4792                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4793                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4794               }
4795           } else {
4796             /* Move from ICS was illegal!?  Punt. */
4797             if (appData.debugMode) {
4798               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4799               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4800             }
4801             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4802             strcat(parseList[moveNum - 1], " ");
4803             strcat(parseList[moveNum - 1], elapsed_time);
4804             moveList[moveNum - 1][0] = NULLCHAR;
4805             fromX = fromY = toX = toY = -1;
4806           }
4807         }
4808   if (appData.debugMode) {
4809     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4810     setbuf(debugFP, NULL);
4811   }
4812
4813 #if ZIPPY
4814         /* Send move to chess program (BEFORE animating it). */
4815         if (appData.zippyPlay && !newGame && newMove &&
4816            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4817
4818             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4819                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4820                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4821                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4822                             move_str);
4823                     DisplayError(str, 0);
4824                 } else {
4825                     if (first.sendTime) {
4826                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4827                     }
4828                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4829                     if (firstMove && !bookHit) {
4830                         firstMove = FALSE;
4831                         if (first.useColors) {
4832                           SendToProgram(gameMode == IcsPlayingWhite ?
4833                                         "white\ngo\n" :
4834                                         "black\ngo\n", &first);
4835                         } else {
4836                           SendToProgram("go\n", &first);
4837                         }
4838                         first.maybeThinking = TRUE;
4839                     }
4840                 }
4841             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4842               if (moveList[moveNum - 1][0] == NULLCHAR) {
4843                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4844                 DisplayError(str, 0);
4845               } else {
4846                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4847                 SendMoveToProgram(moveNum - 1, &first);
4848               }
4849             }
4850         }
4851 #endif
4852     }
4853
4854     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4855         /* If move comes from a remote source, animate it.  If it
4856            isn't remote, it will have already been animated. */
4857         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4858             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4859         }
4860         if (!pausing && appData.highlightLastMove) {
4861             SetHighlights(fromX, fromY, toX, toY);
4862         }
4863     }
4864
4865     /* Start the clocks */
4866     whiteFlag = blackFlag = FALSE;
4867     appData.clockMode = !(basetime == 0 && increment == 0);
4868     if (ticking == 0) {
4869       ics_clock_paused = TRUE;
4870       StopClocks();
4871     } else if (ticking == 1) {
4872       ics_clock_paused = FALSE;
4873     }
4874     if (gameMode == IcsIdle ||
4875         relation == RELATION_OBSERVING_STATIC ||
4876         relation == RELATION_EXAMINING ||
4877         ics_clock_paused)
4878       DisplayBothClocks();
4879     else
4880       StartClocks();
4881
4882     /* Display opponents and material strengths */
4883     if (gameInfo.variant != VariantBughouse &&
4884         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4885         if (tinyLayout || smallLayout) {
4886             if(gameInfo.variant == VariantNormal)
4887               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4888                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4889                     basetime, increment);
4890             else
4891               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4892                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4893                     basetime, increment, (int) gameInfo.variant);
4894         } else {
4895             if(gameInfo.variant == VariantNormal)
4896               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4897                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4898                     basetime, increment);
4899             else
4900               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
4901                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4902                     basetime, increment, VariantName(gameInfo.variant));
4903         }
4904         DisplayTitle(str);
4905   if (appData.debugMode) {
4906     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4907   }
4908     }
4909
4910
4911     /* Display the board */
4912     if (!pausing && !appData.noGUI) {
4913
4914       if (appData.premove)
4915           if (!gotPremove ||
4916              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4917              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4918               ClearPremoveHighlights();
4919
4920       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4921         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4922       DrawPosition(j, boards[currentMove]);
4923
4924       DisplayMove(moveNum - 1);
4925       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4926             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4927               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4928         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4929       }
4930     }
4931
4932     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4933 #if ZIPPY
4934     if(bookHit) { // [HGM] book: simulate book reply
4935         static char bookMove[MSG_SIZ]; // a bit generous?
4936
4937         programStats.nodes = programStats.depth = programStats.time =
4938         programStats.score = programStats.got_only_move = 0;
4939         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4940
4941         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4942         strcat(bookMove, bookHit);
4943         HandleMachineMove(bookMove, &first);
4944     }
4945 #endif
4946 }
4947
4948 void
4949 GetMoveListEvent ()
4950 {
4951     char buf[MSG_SIZ];
4952     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4953         ics_getting_history = H_REQUESTED;
4954         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4955         SendToICS(buf);
4956     }
4957 }
4958
4959 void
4960 SendToBoth (char *msg)
4961 {   // to make it easy to keep two engines in step in dual analysis
4962     SendToProgram(msg, &first);
4963     if(second.analyzing) SendToProgram(msg, &second);
4964 }
4965
4966 void
4967 AnalysisPeriodicEvent (int force)
4968 {
4969     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4970          && !force) || !appData.periodicUpdates)
4971       return;
4972
4973     /* Send . command to Crafty to collect stats */
4974     SendToBoth(".\n");
4975
4976     /* Don't send another until we get a response (this makes
4977        us stop sending to old Crafty's which don't understand
4978        the "." command (sending illegal cmds resets node count & time,
4979        which looks bad)) */
4980     programStats.ok_to_send = 0;
4981 }
4982
4983 void
4984 ics_update_width (int new_width)
4985 {
4986         ics_printf("set width %d\n", new_width);
4987 }
4988
4989 void
4990 SendMoveToProgram (int moveNum, ChessProgramState *cps)
4991 {
4992     char buf[MSG_SIZ];
4993
4994     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
4995         // null move in variant where engine does not understand it (for analysis purposes)
4996         SendBoard(cps, moveNum + 1); // send position after move in stead.
4997         return;
4998     }
4999     if (cps->useUsermove) {
5000       SendToProgram("usermove ", cps);
5001     }
5002     if (cps->useSAN) {
5003       char *space;
5004       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
5005         int len = space - parseList[moveNum];
5006         memcpy(buf, parseList[moveNum], len);
5007         buf[len++] = '\n';
5008         buf[len] = NULLCHAR;
5009       } else {
5010         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
5011       }
5012       SendToProgram(buf, cps);
5013     } else {
5014       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
5015         AlphaRank(moveList[moveNum], 4);
5016         SendToProgram(moveList[moveNum], cps);
5017         AlphaRank(moveList[moveNum], 4); // and back
5018       } else
5019       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
5020        * the engine. It would be nice to have a better way to identify castle
5021        * moves here. */
5022       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
5023                                                                          && cps->useOOCastle) {
5024         int fromX = moveList[moveNum][0] - AAA;
5025         int fromY = moveList[moveNum][1] - ONE;
5026         int toX = moveList[moveNum][2] - AAA;
5027         int toY = moveList[moveNum][3] - ONE;
5028         if((boards[moveNum][fromY][fromX] == WhiteKing
5029             && boards[moveNum][toY][toX] == WhiteRook)
5030            || (boards[moveNum][fromY][fromX] == BlackKing
5031                && boards[moveNum][toY][toX] == BlackRook)) {
5032           if(toX > fromX) SendToProgram("O-O\n", cps);
5033           else SendToProgram("O-O-O\n", cps);
5034         }
5035         else SendToProgram(moveList[moveNum], cps);
5036       } else
5037       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5038         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5039           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5040           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5041                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5042         } else
5043           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5044                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5045         SendToProgram(buf, cps);
5046       }
5047       else SendToProgram(moveList[moveNum], cps);
5048       /* End of additions by Tord */
5049     }
5050
5051     /* [HGM] setting up the opening has brought engine in force mode! */
5052     /*       Send 'go' if we are in a mode where machine should play. */
5053     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5054         (gameMode == TwoMachinesPlay   ||
5055 #if ZIPPY
5056          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
5057 #endif
5058          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5059         SendToProgram("go\n", cps);
5060   if (appData.debugMode) {
5061     fprintf(debugFP, "(extra)\n");
5062   }
5063     }
5064     setboardSpoiledMachineBlack = 0;
5065 }
5066
5067 void
5068 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5069 {
5070     char user_move[MSG_SIZ];
5071     char suffix[4];
5072
5073     if(gameInfo.variant == VariantSChess && promoChar) {
5074         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5075         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5076     } else suffix[0] = NULLCHAR;
5077
5078     switch (moveType) {
5079       default:
5080         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5081                 (int)moveType, fromX, fromY, toX, toY);
5082         DisplayError(user_move + strlen("say "), 0);
5083         break;
5084       case WhiteKingSideCastle:
5085       case BlackKingSideCastle:
5086       case WhiteQueenSideCastleWild:
5087       case BlackQueenSideCastleWild:
5088       /* PUSH Fabien */
5089       case WhiteHSideCastleFR:
5090       case BlackHSideCastleFR:
5091       /* POP Fabien */
5092         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5093         break;
5094       case WhiteQueenSideCastle:
5095       case BlackQueenSideCastle:
5096       case WhiteKingSideCastleWild:
5097       case BlackKingSideCastleWild:
5098       /* PUSH Fabien */
5099       case WhiteASideCastleFR:
5100       case BlackASideCastleFR:
5101       /* POP Fabien */
5102         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5103         break;
5104       case WhiteNonPromotion:
5105       case BlackNonPromotion:
5106         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5107         break;
5108       case WhitePromotion:
5109       case BlackPromotion:
5110         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
5111            gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
5112           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5113                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5114                 PieceToChar(WhiteFerz));
5115         else if(gameInfo.variant == VariantGreat)
5116           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5117                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5118                 PieceToChar(WhiteMan));
5119         else
5120           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5121                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5122                 promoChar);
5123         break;
5124       case WhiteDrop:
5125       case BlackDrop:
5126       drop:
5127         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5128                  ToUpper(PieceToChar((ChessSquare) fromX)),
5129                  AAA + toX, ONE + toY);
5130         break;
5131       case IllegalMove:  /* could be a variant we don't quite understand */
5132         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5133       case NormalMove:
5134       case WhiteCapturesEnPassant:
5135       case BlackCapturesEnPassant:
5136         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5137                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5138         break;
5139     }
5140     SendToICS(user_move);
5141     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5142         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5143 }
5144
5145 void
5146 UploadGameEvent ()
5147 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5148     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5149     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5150     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5151       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5152       return;
5153     }
5154     if(gameMode != IcsExamining) { // is this ever not the case?
5155         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5156
5157         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5158           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5159         } else { // on FICS we must first go to general examine mode
5160           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5161         }
5162         if(gameInfo.variant != VariantNormal) {
5163             // try figure out wild number, as xboard names are not always valid on ICS
5164             for(i=1; i<=36; i++) {
5165               snprintf(buf, MSG_SIZ, "wild/%d", i);
5166                 if(StringToVariant(buf) == gameInfo.variant) break;
5167             }
5168             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5169             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5170             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5171         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5172         SendToICS(ics_prefix);
5173         SendToICS(buf);
5174         if(startedFromSetupPosition || backwardMostMove != 0) {
5175           fen = PositionToFEN(backwardMostMove, NULL, 1);
5176           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5177             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5178             SendToICS(buf);
5179           } else { // FICS: everything has to set by separate bsetup commands
5180             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5181             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5182             SendToICS(buf);
5183             if(!WhiteOnMove(backwardMostMove)) {
5184                 SendToICS("bsetup tomove black\n");
5185             }
5186             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5187             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5188             SendToICS(buf);
5189             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5190             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5191             SendToICS(buf);
5192             i = boards[backwardMostMove][EP_STATUS];
5193             if(i >= 0) { // set e.p.
5194               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5195                 SendToICS(buf);
5196             }
5197             bsetup++;
5198           }
5199         }
5200       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5201             SendToICS("bsetup done\n"); // switch to normal examining.
5202     }
5203     for(i = backwardMostMove; i<last; i++) {
5204         char buf[20];
5205         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5206         if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5207             int len = strlen(moveList[i]);
5208             snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5209             if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5210         }
5211         SendToICS(buf);
5212     }
5213     SendToICS(ics_prefix);
5214     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5215 }
5216
5217 void
5218 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5219 {
5220     if (rf == DROP_RANK) {
5221       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5222       sprintf(move, "%c@%c%c\n",
5223                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5224     } else {
5225         if (promoChar == 'x' || promoChar == NULLCHAR) {
5226           sprintf(move, "%c%c%c%c\n",
5227                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5228         } else {
5229             sprintf(move, "%c%c%c%c%c\n",
5230                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5231         }
5232     }
5233 }
5234
5235 void
5236 ProcessICSInitScript (FILE *f)
5237 {
5238     char buf[MSG_SIZ];
5239
5240     while (fgets(buf, MSG_SIZ, f)) {
5241         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5242     }
5243
5244     fclose(f);
5245 }
5246
5247
5248 static int lastX, lastY, lastLeftX, lastLeftY, selectFlag, dragging;
5249 static ClickType lastClickType;
5250
5251 void
5252 Sweep (int step)
5253 {
5254     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5255     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5256     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5257     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5258     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5259     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5260     do {
5261         promoSweep -= step;
5262         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5263         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5264         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5265         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5266         if(!step) step = -1;
5267     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5268             appData.testLegality && (promoSweep == king ||
5269             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5270     if(toX >= 0) {
5271         int victim = boards[currentMove][toY][toX];
5272         boards[currentMove][toY][toX] = promoSweep;
5273         DrawPosition(FALSE, boards[currentMove]);
5274         boards[currentMove][toY][toX] = victim;
5275     } else
5276     ChangeDragPiece(promoSweep);
5277 }
5278
5279 int
5280 PromoScroll (int x, int y)
5281 {
5282   int step = 0;
5283
5284   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5285   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5286   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5287   if(!step) return FALSE;
5288   lastX = x; lastY = y;
5289   if((promoSweep < BlackPawn) == flipView) step = -step;
5290   if(step > 0) selectFlag = 1;
5291   if(!selectFlag) Sweep(step);
5292   return FALSE;
5293 }
5294
5295 void
5296 NextPiece (int step)
5297 {
5298     ChessSquare piece = boards[currentMove][toY][toX];
5299     do {
5300         pieceSweep -= step;
5301         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5302         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5303         if(!step) step = -1;
5304     } while(PieceToChar(pieceSweep) == '.');
5305     boards[currentMove][toY][toX] = pieceSweep;
5306     DrawPosition(FALSE, boards[currentMove]);
5307     boards[currentMove][toY][toX] = piece;
5308 }
5309 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5310 void
5311 AlphaRank (char *move, int n)
5312 {
5313 //    char *p = move, c; int x, y;
5314
5315     if (appData.debugMode) {
5316         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5317     }
5318
5319     if(move[1]=='*' &&
5320        move[2]>='0' && move[2]<='9' &&
5321        move[3]>='a' && move[3]<='x'    ) {
5322         move[1] = '@';
5323         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5324         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5325     } else
5326     if(move[0]>='0' && move[0]<='9' &&
5327        move[1]>='a' && move[1]<='x' &&
5328        move[2]>='0' && move[2]<='9' &&
5329        move[3]>='a' && move[3]<='x'    ) {
5330         /* input move, Shogi -> normal */
5331         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5332         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5333         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5334         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5335     } else
5336     if(move[1]=='@' &&
5337        move[3]>='0' && move[3]<='9' &&
5338        move[2]>='a' && move[2]<='x'    ) {
5339         move[1] = '*';
5340         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5341         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5342     } else
5343     if(
5344        move[0]>='a' && move[0]<='x' &&
5345        move[3]>='0' && move[3]<='9' &&
5346        move[2]>='a' && move[2]<='x'    ) {
5347          /* output move, normal -> Shogi */
5348         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5349         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5350         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5351         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5352         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5353     }
5354     if (appData.debugMode) {
5355         fprintf(debugFP, "   out = '%s'\n", move);
5356     }
5357 }
5358
5359 char yy_textstr[8000];
5360
5361 /* Parser for moves from gnuchess, ICS, or user typein box */
5362 Boolean
5363 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5364 {
5365     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5366
5367     switch (*moveType) {
5368       case WhitePromotion:
5369       case BlackPromotion:
5370       case WhiteNonPromotion:
5371       case BlackNonPromotion:
5372       case NormalMove:
5373       case WhiteCapturesEnPassant:
5374       case BlackCapturesEnPassant:
5375       case WhiteKingSideCastle:
5376       case WhiteQueenSideCastle:
5377       case BlackKingSideCastle:
5378       case BlackQueenSideCastle:
5379       case WhiteKingSideCastleWild:
5380       case WhiteQueenSideCastleWild:
5381       case BlackKingSideCastleWild:
5382       case BlackQueenSideCastleWild:
5383       /* Code added by Tord: */
5384       case WhiteHSideCastleFR:
5385       case WhiteASideCastleFR:
5386       case BlackHSideCastleFR:
5387       case BlackASideCastleFR:
5388       /* End of code added by Tord */
5389       case IllegalMove:         /* bug or odd chess variant */
5390         *fromX = currentMoveString[0] - AAA;
5391         *fromY = currentMoveString[1] - ONE;
5392         *toX = currentMoveString[2] - AAA;
5393         *toY = currentMoveString[3] - ONE;
5394         *promoChar = currentMoveString[4];
5395         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5396             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5397     if (appData.debugMode) {
5398         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5399     }
5400             *fromX = *fromY = *toX = *toY = 0;
5401             return FALSE;
5402         }
5403         if (appData.testLegality) {
5404           return (*moveType != IllegalMove);
5405         } else {
5406           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5407                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5408         }
5409
5410       case WhiteDrop:
5411       case BlackDrop:
5412         *fromX = *moveType == WhiteDrop ?
5413           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5414           (int) CharToPiece(ToLower(currentMoveString[0]));
5415         *fromY = DROP_RANK;
5416         *toX = currentMoveString[2] - AAA;
5417         *toY = currentMoveString[3] - ONE;
5418         *promoChar = NULLCHAR;
5419         return TRUE;
5420
5421       case AmbiguousMove:
5422       case ImpossibleMove:
5423       case EndOfFile:
5424       case ElapsedTime:
5425       case Comment:
5426       case PGNTag:
5427       case NAG:
5428       case WhiteWins:
5429       case BlackWins:
5430       case GameIsDrawn:
5431       default:
5432     if (appData.debugMode) {
5433         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5434     }
5435         /* bug? */
5436         *fromX = *fromY = *toX = *toY = 0;
5437         *promoChar = NULLCHAR;
5438         return FALSE;
5439     }
5440 }
5441
5442 Boolean pushed = FALSE;
5443 char *lastParseAttempt;
5444
5445 void
5446 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5447 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5448   int fromX, fromY, toX, toY; char promoChar;
5449   ChessMove moveType;
5450   Boolean valid;
5451   int nr = 0;
5452
5453   lastParseAttempt = pv; if(!*pv) return;    // turns out we crash when we parse an empty PV
5454   if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5455     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5456     pushed = TRUE;
5457   }
5458   endPV = forwardMostMove;
5459   do {
5460     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5461     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5462     lastParseAttempt = pv;
5463     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5464     if(!valid && nr == 0 &&
5465        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5466         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5467         // Hande case where played move is different from leading PV move
5468         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5469         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5470         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5471         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5472           endPV += 2; // if position different, keep this
5473           moveList[endPV-1][0] = fromX + AAA;
5474           moveList[endPV-1][1] = fromY + ONE;
5475           moveList[endPV-1][2] = toX + AAA;
5476           moveList[endPV-1][3] = toY + ONE;
5477           parseList[endPV-1][0] = NULLCHAR;
5478           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5479         }
5480       }
5481     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5482     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5483     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5484     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5485         valid++; // allow comments in PV
5486         continue;
5487     }
5488     nr++;
5489     if(endPV+1 > framePtr) break; // no space, truncate
5490     if(!valid) break;
5491     endPV++;
5492     CopyBoard(boards[endPV], boards[endPV-1]);
5493     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5494     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5495     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5496     CoordsToAlgebraic(boards[endPV - 1],
5497                              PosFlags(endPV - 1),
5498                              fromY, fromX, toY, toX, promoChar,
5499                              parseList[endPV - 1]);
5500   } while(valid);
5501   if(atEnd == 2) return; // used hidden, for PV conversion
5502   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5503   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5504   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5505                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5506   DrawPosition(TRUE, boards[currentMove]);
5507 }
5508
5509 int
5510 MultiPV (ChessProgramState *cps)
5511 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5512         int i;
5513         for(i=0; i<cps->nrOptions; i++)
5514             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5515                 return i;
5516         return -1;
5517 }
5518
5519 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
5520
5521 Boolean
5522 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5523 {
5524         int startPV, multi, lineStart, origIndex = index;
5525         char *p, buf2[MSG_SIZ];
5526         ChessProgramState *cps = (pane ? &second : &first);
5527
5528         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5529         lastX = x; lastY = y;
5530         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5531         lineStart = startPV = index;
5532         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5533         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5534         index = startPV;
5535         do{ while(buf[index] && buf[index] != '\n') index++;
5536         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5537         buf[index] = 0;
5538         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(cps)) >= 0) {
5539                 int n = cps->option[multi].value;
5540                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5541                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5542                 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5543                 cps->option[multi].value = n;
5544                 *start = *end = 0;
5545                 return FALSE;
5546         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5547                 ExcludeClick(origIndex - lineStart);
5548                 return FALSE;
5549         }
5550         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5551         *start = startPV; *end = index-1;
5552         extendGame = (gameMode == AnalyzeMode && appData.autoExtend);
5553         return TRUE;
5554 }
5555
5556 char *
5557 PvToSAN (char *pv)
5558 {
5559         static char buf[10*MSG_SIZ];
5560         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5561         *buf = NULLCHAR;
5562         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5563         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5564         for(i = forwardMostMove; i<endPV; i++){
5565             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5566             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5567             k += strlen(buf+k);
5568         }
5569         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5570         if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5571         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5572         endPV = savedEnd;
5573         return buf;
5574 }
5575
5576 Boolean
5577 LoadPV (int x, int y)
5578 { // called on right mouse click to load PV
5579   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5580   lastX = x; lastY = y;
5581   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5582   extendGame = FALSE;
5583   return TRUE;
5584 }
5585
5586 void
5587 UnLoadPV ()
5588 {
5589   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5590   if(endPV < 0) return;
5591   if(appData.autoCopyPV) CopyFENToClipboard();
5592   endPV = -1;
5593   if(extendGame && currentMove > forwardMostMove) {
5594         Boolean saveAnimate = appData.animate;
5595         if(pushed) {
5596             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5597                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5598             } else storedGames--; // abandon shelved tail of original game
5599         }
5600         pushed = FALSE;
5601         forwardMostMove = currentMove;
5602         currentMove = oldFMM;
5603         appData.animate = FALSE;
5604         ToNrEvent(forwardMostMove);
5605         appData.animate = saveAnimate;
5606   }
5607   currentMove = forwardMostMove;
5608   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5609   ClearPremoveHighlights();
5610   DrawPosition(TRUE, boards[currentMove]);
5611 }
5612
5613 void
5614 MovePV (int x, int y, int h)
5615 { // step through PV based on mouse coordinates (called on mouse move)
5616   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5617
5618   // we must somehow check if right button is still down (might be released off board!)
5619   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5620   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5621   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5622   if(!step) return;
5623   lastX = x; lastY = y;
5624
5625   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5626   if(endPV < 0) return;
5627   if(y < margin) step = 1; else
5628   if(y > h - margin) step = -1;
5629   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5630   currentMove += step;
5631   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5632   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5633                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5634   DrawPosition(FALSE, boards[currentMove]);
5635 }
5636
5637
5638 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5639 // All positions will have equal probability, but the current method will not provide a unique
5640 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5641 #define DARK 1
5642 #define LITE 2
5643 #define ANY 3
5644
5645 int squaresLeft[4];
5646 int piecesLeft[(int)BlackPawn];
5647 int seed, nrOfShuffles;
5648
5649 void
5650 GetPositionNumber ()
5651 {       // sets global variable seed
5652         int i;
5653
5654         seed = appData.defaultFrcPosition;
5655         if(seed < 0) { // randomize based on time for negative FRC position numbers
5656                 for(i=0; i<50; i++) seed += random();
5657                 seed = random() ^ random() >> 8 ^ random() << 8;
5658                 if(seed<0) seed = -seed;
5659         }
5660 }
5661
5662 int
5663 put (Board board, int pieceType, int rank, int n, int shade)
5664 // put the piece on the (n-1)-th empty squares of the given shade
5665 {
5666         int i;
5667
5668         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5669                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5670                         board[rank][i] = (ChessSquare) pieceType;
5671                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5672                         squaresLeft[ANY]--;
5673                         piecesLeft[pieceType]--;
5674                         return i;
5675                 }
5676         }
5677         return -1;
5678 }
5679
5680
5681 void
5682 AddOnePiece (Board board, int pieceType, int rank, int shade)
5683 // calculate where the next piece goes, (any empty square), and put it there
5684 {
5685         int i;
5686
5687         i = seed % squaresLeft[shade];
5688         nrOfShuffles *= squaresLeft[shade];
5689         seed /= squaresLeft[shade];
5690         put(board, pieceType, rank, i, shade);
5691 }
5692
5693 void
5694 AddTwoPieces (Board board, int pieceType, int rank)
5695 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5696 {
5697         int i, n=squaresLeft[ANY], j=n-1, k;
5698
5699         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5700         i = seed % k;  // pick one
5701         nrOfShuffles *= k;
5702         seed /= k;
5703         while(i >= j) i -= j--;
5704         j = n - 1 - j; i += j;
5705         put(board, pieceType, rank, j, ANY);
5706         put(board, pieceType, rank, i, ANY);
5707 }
5708
5709 void
5710 SetUpShuffle (Board board, int number)
5711 {
5712         int i, p, first=1;
5713
5714         GetPositionNumber(); nrOfShuffles = 1;
5715
5716         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5717         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5718         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5719
5720         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5721
5722         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5723             p = (int) board[0][i];
5724             if(p < (int) BlackPawn) piecesLeft[p] ++;
5725             board[0][i] = EmptySquare;
5726         }
5727
5728         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5729             // shuffles restricted to allow normal castling put KRR first
5730             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5731                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5732             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5733                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5734             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5735                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5736             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5737                 put(board, WhiteRook, 0, 0, ANY);
5738             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5739         }
5740
5741         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5742             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5743             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5744                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5745                 while(piecesLeft[p] >= 2) {
5746                     AddOnePiece(board, p, 0, LITE);
5747                     AddOnePiece(board, p, 0, DARK);
5748                 }
5749                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5750             }
5751
5752         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5753             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5754             // but we leave King and Rooks for last, to possibly obey FRC restriction
5755             if(p == (int)WhiteRook) continue;
5756             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5757             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5758         }
5759
5760         // now everything is placed, except perhaps King (Unicorn) and Rooks
5761
5762         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5763             // Last King gets castling rights
5764             while(piecesLeft[(int)WhiteUnicorn]) {
5765                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5766                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5767             }
5768
5769             while(piecesLeft[(int)WhiteKing]) {
5770                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5771                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5772             }
5773
5774
5775         } else {
5776             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5777             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5778         }
5779
5780         // Only Rooks can be left; simply place them all
5781         while(piecesLeft[(int)WhiteRook]) {
5782                 i = put(board, WhiteRook, 0, 0, ANY);
5783                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5784                         if(first) {
5785                                 first=0;
5786                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5787                         }
5788                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5789                 }
5790         }
5791         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5792             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5793         }
5794
5795         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5796 }
5797
5798 int
5799 SetCharTable (char *table, const char * map)
5800 /* [HGM] moved here from winboard.c because of its general usefulness */
5801 /*       Basically a safe strcpy that uses the last character as King */
5802 {
5803     int result = FALSE; int NrPieces;
5804
5805     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5806                     && NrPieces >= 12 && !(NrPieces&1)) {
5807         int i; /* [HGM] Accept even length from 12 to 34 */
5808
5809         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5810         for( i=0; i<NrPieces/2-1; i++ ) {
5811             table[i] = map[i];
5812             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5813         }
5814         table[(int) WhiteKing]  = map[NrPieces/2-1];
5815         table[(int) BlackKing]  = map[NrPieces-1];
5816
5817         result = TRUE;
5818     }
5819
5820     return result;
5821 }
5822
5823 void
5824 Prelude (Board board)
5825 {       // [HGM] superchess: random selection of exo-pieces
5826         int i, j, k; ChessSquare p;
5827         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5828
5829         GetPositionNumber(); // use FRC position number
5830
5831         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5832             SetCharTable(pieceToChar, appData.pieceToCharTable);
5833             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5834                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5835         }
5836
5837         j = seed%4;                 seed /= 4;
5838         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5839         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5840         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5841         j = seed%3 + (seed%3 >= j); seed /= 3;
5842         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5843         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5844         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5845         j = seed%3;                 seed /= 3;
5846         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5847         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5848         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5849         j = seed%2 + (seed%2 >= j); seed /= 2;
5850         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5851         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5852         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5853         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5854         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5855         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5856         put(board, exoPieces[0],    0, 0, ANY);
5857         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5858 }
5859
5860 void
5861 InitPosition (int redraw)
5862 {
5863     ChessSquare (* pieces)[BOARD_FILES];
5864     int i, j, pawnRow, overrule,
5865     oldx = gameInfo.boardWidth,
5866     oldy = gameInfo.boardHeight,
5867     oldh = gameInfo.holdingsWidth;
5868     static int oldv;
5869
5870     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5871
5872     /* [AS] Initialize pv info list [HGM] and game status */
5873     {
5874         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5875             pvInfoList[i].depth = 0;
5876             boards[i][EP_STATUS] = EP_NONE;
5877             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5878         }
5879
5880         initialRulePlies = 0; /* 50-move counter start */
5881
5882         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5883         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5884     }
5885
5886
5887     /* [HGM] logic here is completely changed. In stead of full positions */
5888     /* the initialized data only consist of the two backranks. The switch */
5889     /* selects which one we will use, which is than copied to the Board   */
5890     /* initialPosition, which for the rest is initialized by Pawns and    */
5891     /* empty squares. This initial position is then copied to boards[0],  */
5892     /* possibly after shuffling, so that it remains available.            */
5893
5894     gameInfo.holdingsWidth = 0; /* default board sizes */
5895     gameInfo.boardWidth    = 8;
5896     gameInfo.boardHeight   = 8;
5897     gameInfo.holdingsSize  = 0;
5898     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5899     for(i=0; i<BOARD_FILES-2; i++)
5900       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5901     initialPosition[EP_STATUS] = EP_NONE;
5902     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5903     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5904          SetCharTable(pieceNickName, appData.pieceNickNames);
5905     else SetCharTable(pieceNickName, "............");
5906     pieces = FIDEArray;
5907
5908     switch (gameInfo.variant) {
5909     case VariantFischeRandom:
5910       shuffleOpenings = TRUE;
5911     default:
5912       break;
5913     case VariantShatranj:
5914       pieces = ShatranjArray;
5915       nrCastlingRights = 0;
5916       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5917       break;
5918     case VariantMakruk:
5919       pieces = makrukArray;
5920       nrCastlingRights = 0;
5921       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5922       break;
5923     case VariantASEAN:
5924       pieces = aseanArray;
5925       nrCastlingRights = 0;
5926       SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk");
5927       break;
5928     case VariantTwoKings:
5929       pieces = twoKingsArray;
5930       break;
5931     case VariantGrand:
5932       pieces = GrandArray;
5933       nrCastlingRights = 0;
5934       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5935       gameInfo.boardWidth = 10;
5936       gameInfo.boardHeight = 10;
5937       gameInfo.holdingsSize = 7;
5938       break;
5939     case VariantCapaRandom:
5940       shuffleOpenings = TRUE;
5941     case VariantCapablanca:
5942       pieces = CapablancaArray;
5943       gameInfo.boardWidth = 10;
5944       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5945       break;
5946     case VariantGothic:
5947       pieces = GothicArray;
5948       gameInfo.boardWidth = 10;
5949       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5950       break;
5951     case VariantSChess:
5952       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5953       gameInfo.holdingsSize = 7;
5954       for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
5955       break;
5956     case VariantJanus:
5957       pieces = JanusArray;
5958       gameInfo.boardWidth = 10;
5959       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5960       nrCastlingRights = 6;
5961         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5962         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5963         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5964         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5965         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5966         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5967       break;
5968     case VariantFalcon:
5969       pieces = FalconArray;
5970       gameInfo.boardWidth = 10;
5971       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5972       break;
5973     case VariantXiangqi:
5974       pieces = XiangqiArray;
5975       gameInfo.boardWidth  = 9;
5976       gameInfo.boardHeight = 10;
5977       nrCastlingRights = 0;
5978       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5979       break;
5980     case VariantShogi:
5981       pieces = ShogiArray;
5982       gameInfo.boardWidth  = 9;
5983       gameInfo.boardHeight = 9;
5984       gameInfo.holdingsSize = 7;
5985       nrCastlingRights = 0;
5986       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5987       break;
5988     case VariantCourier:
5989       pieces = CourierArray;
5990       gameInfo.boardWidth  = 12;
5991       nrCastlingRights = 0;
5992       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5993       break;
5994     case VariantKnightmate:
5995       pieces = KnightmateArray;
5996       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5997       break;
5998     case VariantSpartan:
5999       pieces = SpartanArray;
6000       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
6001       break;
6002     case VariantFairy:
6003       pieces = fairyArray;
6004       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
6005       break;
6006     case VariantGreat:
6007       pieces = GreatArray;
6008       gameInfo.boardWidth = 10;
6009       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
6010       gameInfo.holdingsSize = 8;
6011       break;
6012     case VariantSuper:
6013       pieces = FIDEArray;
6014       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
6015       gameInfo.holdingsSize = 8;
6016       startedFromSetupPosition = TRUE;
6017       break;
6018     case VariantCrazyhouse:
6019     case VariantBughouse:
6020       pieces = FIDEArray;
6021       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
6022       gameInfo.holdingsSize = 5;
6023       break;
6024     case VariantWildCastle:
6025       pieces = FIDEArray;
6026       /* !!?shuffle with kings guaranteed to be on d or e file */
6027       shuffleOpenings = 1;
6028       break;
6029     case VariantNoCastle:
6030       pieces = FIDEArray;
6031       nrCastlingRights = 0;
6032       /* !!?unconstrained back-rank shuffle */
6033       shuffleOpenings = 1;
6034       break;
6035     }
6036
6037     overrule = 0;
6038     if(appData.NrFiles >= 0) {
6039         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6040         gameInfo.boardWidth = appData.NrFiles;
6041     }
6042     if(appData.NrRanks >= 0) {
6043         gameInfo.boardHeight = appData.NrRanks;
6044     }
6045     if(appData.holdingsSize >= 0) {
6046         i = appData.holdingsSize;
6047         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6048         gameInfo.holdingsSize = i;
6049     }
6050     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6051     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6052         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6053
6054     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6055     if(pawnRow < 1) pawnRow = 1;
6056     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN || gameInfo.variant == VariantGrand) pawnRow = 2;
6057
6058     /* User pieceToChar list overrules defaults */
6059     if(appData.pieceToCharTable != NULL)
6060         SetCharTable(pieceToChar, appData.pieceToCharTable);
6061
6062     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6063
6064         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6065             s = (ChessSquare) 0; /* account holding counts in guard band */
6066         for( i=0; i<BOARD_HEIGHT; i++ )
6067             initialPosition[i][j] = s;
6068
6069         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6070         initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
6071         initialPosition[pawnRow][j] = WhitePawn;
6072         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6073         if(gameInfo.variant == VariantXiangqi) {
6074             if(j&1) {
6075                 initialPosition[pawnRow][j] =
6076                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6077                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6078                    initialPosition[2][j] = WhiteCannon;
6079                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6080                 }
6081             }
6082         }
6083         if(gameInfo.variant == VariantGrand) {
6084             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6085                initialPosition[0][j] = WhiteRook;
6086                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6087             }
6088         }
6089         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] =  pieces[1][j-gameInfo.holdingsWidth];
6090     }
6091     if( (gameInfo.variant == VariantShogi) && !overrule ) {
6092
6093             j=BOARD_LEFT+1;
6094             initialPosition[1][j] = WhiteBishop;
6095             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6096             j=BOARD_RGHT-2;
6097             initialPosition[1][j] = WhiteRook;
6098             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6099     }
6100
6101     if( nrCastlingRights == -1) {
6102         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6103         /*       This sets default castling rights from none to normal corners   */
6104         /* Variants with other castling rights must set them themselves above    */
6105         nrCastlingRights = 6;
6106
6107         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6108         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6109         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6110         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6111         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6112         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6113      }
6114
6115      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6116      if(gameInfo.variant == VariantGreat) { // promotion commoners
6117         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6118         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6119         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6120         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6121      }
6122      if( gameInfo.variant == VariantSChess ) {
6123       initialPosition[1][0] = BlackMarshall;
6124       initialPosition[2][0] = BlackAngel;
6125       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6126       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6127       initialPosition[1][1] = initialPosition[2][1] =
6128       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6129      }
6130   if (appData.debugMode) {
6131     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6132   }
6133     if(shuffleOpenings) {
6134         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6135         startedFromSetupPosition = TRUE;
6136     }
6137     if(startedFromPositionFile) {
6138       /* [HGM] loadPos: use PositionFile for every new game */
6139       CopyBoard(initialPosition, filePosition);
6140       for(i=0; i<nrCastlingRights; i++)
6141           initialRights[i] = filePosition[CASTLING][i];
6142       startedFromSetupPosition = TRUE;
6143     }
6144
6145     CopyBoard(boards[0], initialPosition);
6146
6147     if(oldx != gameInfo.boardWidth ||
6148        oldy != gameInfo.boardHeight ||
6149        oldv != gameInfo.variant ||
6150        oldh != gameInfo.holdingsWidth
6151                                          )
6152             InitDrawingSizes(-2 ,0);
6153
6154     oldv = gameInfo.variant;
6155     if (redraw)
6156       DrawPosition(TRUE, boards[currentMove]);
6157 }
6158
6159 void
6160 SendBoard (ChessProgramState *cps, int moveNum)
6161 {
6162     char message[MSG_SIZ];
6163
6164     if (cps->useSetboard) {
6165       char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6166       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6167       SendToProgram(message, cps);
6168       free(fen);
6169
6170     } else {
6171       ChessSquare *bp;
6172       int i, j, left=0, right=BOARD_WIDTH;
6173       /* Kludge to set black to move, avoiding the troublesome and now
6174        * deprecated "black" command.
6175        */
6176       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6177         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6178
6179       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6180
6181       SendToProgram("edit\n", cps);
6182       SendToProgram("#\n", cps);
6183       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6184         bp = &boards[moveNum][i][left];
6185         for (j = left; j < right; j++, bp++) {
6186           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6187           if ((int) *bp < (int) BlackPawn) {
6188             if(j == BOARD_RGHT+1)
6189                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6190             else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6191             if(message[0] == '+' || message[0] == '~') {
6192               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6193                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6194                         AAA + j, ONE + i);
6195             }
6196             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6197                 message[1] = BOARD_RGHT   - 1 - j + '1';
6198                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6199             }
6200             SendToProgram(message, cps);
6201           }
6202         }
6203       }
6204
6205       SendToProgram("c\n", cps);
6206       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6207         bp = &boards[moveNum][i][left];
6208         for (j = left; j < right; j++, bp++) {
6209           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6210           if (((int) *bp != (int) EmptySquare)
6211               && ((int) *bp >= (int) BlackPawn)) {
6212             if(j == BOARD_LEFT-2)
6213                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6214             else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6215                     AAA + j, ONE + i);
6216             if(message[0] == '+' || message[0] == '~') {
6217               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6218                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6219                         AAA + j, ONE + i);
6220             }
6221             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6222                 message[1] = BOARD_RGHT   - 1 - j + '1';
6223                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6224             }
6225             SendToProgram(message, cps);
6226           }
6227         }
6228       }
6229
6230       SendToProgram(".\n", cps);
6231     }
6232     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6233 }
6234
6235 char exclusionHeader[MSG_SIZ];
6236 int exCnt, excludePtr;
6237 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6238 static Exclusion excluTab[200];
6239 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6240
6241 static void
6242 WriteMap (int s)
6243 {
6244     int j;
6245     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6246     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6247 }
6248
6249 static void
6250 ClearMap ()
6251 {
6252     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6253     excludePtr = 24; exCnt = 0;
6254     WriteMap(0);
6255 }
6256
6257 static void
6258 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6259 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6260     char buf[2*MOVE_LEN], *p;
6261     Exclusion *e = excluTab;
6262     int i;
6263     for(i=0; i<exCnt; i++)
6264         if(e[i].ff == fromX && e[i].fr == fromY &&
6265            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6266     if(i == exCnt) { // was not in exclude list; add it
6267         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6268         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6269             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6270             return; // abort
6271         }
6272         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6273         excludePtr++; e[i].mark = excludePtr++;
6274         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6275         exCnt++;
6276     }
6277     exclusionHeader[e[i].mark] = state;
6278 }
6279
6280 static int
6281 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6282 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6283     char buf[MSG_SIZ];
6284     int j, k;
6285     ChessMove moveType;
6286     if((signed char)promoChar == -1) { // kludge to indicate best move
6287         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6288             return 1; // if unparsable, abort
6289     }
6290     // update exclusion map (resolving toggle by consulting existing state)
6291     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6292     j = k%8; k >>= 3;
6293     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6294     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6295          excludeMap[k] |=   1<<j;
6296     else excludeMap[k] &= ~(1<<j);
6297     // update header
6298     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6299     // inform engine
6300     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6301     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6302     SendToBoth(buf);
6303     return (state == '+');
6304 }
6305
6306 static void
6307 ExcludeClick (int index)
6308 {
6309     int i, j;
6310     Exclusion *e = excluTab;
6311     if(index < 25) { // none, best or tail clicked
6312         if(index < 13) { // none: include all
6313             WriteMap(0); // clear map
6314             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6315             SendToBoth("include all\n"); // and inform engine
6316         } else if(index > 18) { // tail
6317             if(exclusionHeader[19] == '-') { // tail was excluded
6318                 SendToBoth("include all\n");
6319                 WriteMap(0); // clear map completely
6320                 // now re-exclude selected moves
6321                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6322                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6323             } else { // tail was included or in mixed state
6324                 SendToBoth("exclude all\n");
6325                 WriteMap(0xFF); // fill map completely
6326                 // now re-include selected moves
6327                 j = 0; // count them
6328                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6329                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6330                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6331             }
6332         } else { // best
6333             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6334         }
6335     } else {
6336         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6337             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6338             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6339             break;
6340         }
6341     }
6342 }
6343
6344 ChessSquare
6345 DefaultPromoChoice (int white)
6346 {
6347     ChessSquare result;
6348     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6349        gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
6350         result = WhiteFerz; // no choice
6351     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6352         result= WhiteKing; // in Suicide Q is the last thing we want
6353     else if(gameInfo.variant == VariantSpartan)
6354         result = white ? WhiteQueen : WhiteAngel;
6355     else result = WhiteQueen;
6356     if(!white) result = WHITE_TO_BLACK result;
6357     return result;
6358 }
6359
6360 static int autoQueen; // [HGM] oneclick
6361
6362 int
6363 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6364 {
6365     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6366     /* [HGM] add Shogi promotions */
6367     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6368     ChessSquare piece;
6369     ChessMove moveType;
6370     Boolean premove;
6371
6372     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6373     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6374
6375     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6376       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6377         return FALSE;
6378
6379     piece = boards[currentMove][fromY][fromX];
6380     if(gameInfo.variant == VariantShogi) {
6381         promotionZoneSize = BOARD_HEIGHT/3;
6382         highestPromotingPiece = (int)WhiteFerz;
6383     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6384         promotionZoneSize = 3;
6385     }
6386
6387     // Treat Lance as Pawn when it is not representing Amazon
6388     if(gameInfo.variant != VariantSuper) {
6389         if(piece == WhiteLance) piece = WhitePawn; else
6390         if(piece == BlackLance) piece = BlackPawn;
6391     }
6392
6393     // next weed out all moves that do not touch the promotion zone at all
6394     if((int)piece >= BlackPawn) {
6395         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6396              return FALSE;
6397         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6398     } else {
6399         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6400            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6401     }
6402
6403     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6404
6405     // weed out mandatory Shogi promotions
6406     if(gameInfo.variant == VariantShogi) {
6407         if(piece >= BlackPawn) {
6408             if(toY == 0 && piece == BlackPawn ||
6409                toY == 0 && piece == BlackQueen ||
6410                toY <= 1 && piece == BlackKnight) {
6411                 *promoChoice = '+';
6412                 return FALSE;
6413             }
6414         } else {
6415             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6416                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6417                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6418                 *promoChoice = '+';
6419                 return FALSE;
6420             }
6421         }
6422     }
6423
6424     // weed out obviously illegal Pawn moves
6425     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6426         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6427         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6428         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6429         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6430         // note we are not allowed to test for valid (non-)capture, due to premove
6431     }
6432
6433     // we either have a choice what to promote to, or (in Shogi) whether to promote
6434     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6435        gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN) {
6436         *promoChoice = PieceToChar(BlackFerz);  // no choice
6437         return FALSE;
6438     }
6439     // no sense asking what we must promote to if it is going to explode...
6440     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6441         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6442         return FALSE;
6443     }
6444     // give caller the default choice even if we will not make it
6445     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6446     if(gameInfo.variant == VariantShogi) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6447     if(        sweepSelect && gameInfo.variant != VariantGreat
6448                            && gameInfo.variant != VariantGrand
6449                            && gameInfo.variant != VariantSuper) return FALSE;
6450     if(autoQueen) return FALSE; // predetermined
6451
6452     // suppress promotion popup on illegal moves that are not premoves
6453     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6454               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6455     if(appData.testLegality && !premove) {
6456         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6457                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6458         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6459             return FALSE;
6460     }
6461
6462     return TRUE;
6463 }
6464
6465 int
6466 InPalace (int row, int column)
6467 {   /* [HGM] for Xiangqi */
6468     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6469          column < (BOARD_WIDTH + 4)/2 &&
6470          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6471     return FALSE;
6472 }
6473
6474 int
6475 PieceForSquare (int x, int y)
6476 {
6477   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6478      return -1;
6479   else
6480      return boards[currentMove][y][x];
6481 }
6482
6483 int
6484 OKToStartUserMove (int x, int y)
6485 {
6486     ChessSquare from_piece;
6487     int white_piece;
6488
6489     if (matchMode) return FALSE;
6490     if (gameMode == EditPosition) return TRUE;
6491
6492     if (x >= 0 && y >= 0)
6493       from_piece = boards[currentMove][y][x];
6494     else
6495       from_piece = EmptySquare;
6496
6497     if (from_piece == EmptySquare) return FALSE;
6498
6499     white_piece = (int)from_piece >= (int)WhitePawn &&
6500       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6501
6502     switch (gameMode) {
6503       case AnalyzeFile:
6504       case TwoMachinesPlay:
6505       case EndOfGame:
6506         return FALSE;
6507
6508       case IcsObserving:
6509       case IcsIdle:
6510         return FALSE;
6511
6512       case MachinePlaysWhite:
6513       case IcsPlayingBlack:
6514         if (appData.zippyPlay) return FALSE;
6515         if (white_piece) {
6516             DisplayMoveError(_("You are playing Black"));
6517             return FALSE;
6518         }
6519         break;
6520
6521       case MachinePlaysBlack:
6522       case IcsPlayingWhite:
6523         if (appData.zippyPlay) return FALSE;
6524         if (!white_piece) {
6525             DisplayMoveError(_("You are playing White"));
6526             return FALSE;
6527         }
6528         break;
6529
6530       case PlayFromGameFile:
6531             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6532       case EditGame:
6533         if (!white_piece && WhiteOnMove(currentMove)) {
6534             DisplayMoveError(_("It is White's turn"));
6535             return FALSE;
6536         }
6537         if (white_piece && !WhiteOnMove(currentMove)) {
6538             DisplayMoveError(_("It is Black's turn"));
6539             return FALSE;
6540         }
6541         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6542             /* Editing correspondence game history */
6543             /* Could disallow this or prompt for confirmation */
6544             cmailOldMove = -1;
6545         }
6546         break;
6547
6548       case BeginningOfGame:
6549         if (appData.icsActive) return FALSE;
6550         if (!appData.noChessProgram) {
6551             if (!white_piece) {
6552                 DisplayMoveError(_("You are playing White"));
6553                 return FALSE;
6554             }
6555         }
6556         break;
6557
6558       case Training:
6559         if (!white_piece && WhiteOnMove(currentMove)) {
6560             DisplayMoveError(_("It is White's turn"));
6561             return FALSE;
6562         }
6563         if (white_piece && !WhiteOnMove(currentMove)) {
6564             DisplayMoveError(_("It is Black's turn"));
6565             return FALSE;
6566         }
6567         break;
6568
6569       default:
6570       case IcsExamining:
6571         break;
6572     }
6573     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6574         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6575         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6576         && gameMode != AnalyzeFile && gameMode != Training) {
6577         DisplayMoveError(_("Displayed position is not current"));
6578         return FALSE;
6579     }
6580     return TRUE;
6581 }
6582
6583 Boolean
6584 OnlyMove (int *x, int *y, Boolean captures)
6585 {
6586     DisambiguateClosure cl;
6587     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6588     switch(gameMode) {
6589       case MachinePlaysBlack:
6590       case IcsPlayingWhite:
6591       case BeginningOfGame:
6592         if(!WhiteOnMove(currentMove)) return FALSE;
6593         break;
6594       case MachinePlaysWhite:
6595       case IcsPlayingBlack:
6596         if(WhiteOnMove(currentMove)) return FALSE;
6597         break;
6598       case EditGame:
6599         break;
6600       default:
6601         return FALSE;
6602     }
6603     cl.pieceIn = EmptySquare;
6604     cl.rfIn = *y;
6605     cl.ffIn = *x;
6606     cl.rtIn = -1;
6607     cl.ftIn = -1;
6608     cl.promoCharIn = NULLCHAR;
6609     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6610     if( cl.kind == NormalMove ||
6611         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6612         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6613         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6614       fromX = cl.ff;
6615       fromY = cl.rf;
6616       *x = cl.ft;
6617       *y = cl.rt;
6618       return TRUE;
6619     }
6620     if(cl.kind != ImpossibleMove) return FALSE;
6621     cl.pieceIn = EmptySquare;
6622     cl.rfIn = -1;
6623     cl.ffIn = -1;
6624     cl.rtIn = *y;
6625     cl.ftIn = *x;
6626     cl.promoCharIn = NULLCHAR;
6627     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6628     if( cl.kind == NormalMove ||
6629         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6630         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6631         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6632       fromX = cl.ff;
6633       fromY = cl.rf;
6634       *x = cl.ft;
6635       *y = cl.rt;
6636       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6637       return TRUE;
6638     }
6639     return FALSE;
6640 }
6641
6642 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6643 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6644 int lastLoadGameUseList = FALSE;
6645 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6646 ChessMove lastLoadGameStart = EndOfFile;
6647 int doubleClick;
6648
6649 void
6650 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6651 {
6652     ChessMove moveType;
6653     ChessSquare pup;
6654     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6655
6656     /* Check if the user is playing in turn.  This is complicated because we
6657        let the user "pick up" a piece before it is his turn.  So the piece he
6658        tried to pick up may have been captured by the time he puts it down!
6659        Therefore we use the color the user is supposed to be playing in this
6660        test, not the color of the piece that is currently on the starting
6661        square---except in EditGame mode, where the user is playing both
6662        sides; fortunately there the capture race can't happen.  (It can
6663        now happen in IcsExamining mode, but that's just too bad.  The user
6664        will get a somewhat confusing message in that case.)
6665        */
6666
6667     switch (gameMode) {
6668       case AnalyzeFile:
6669       case TwoMachinesPlay:
6670       case EndOfGame:
6671       case IcsObserving:
6672       case IcsIdle:
6673         /* We switched into a game mode where moves are not accepted,
6674            perhaps while the mouse button was down. */
6675         return;
6676
6677       case MachinePlaysWhite:
6678         /* User is moving for Black */
6679         if (WhiteOnMove(currentMove)) {
6680             DisplayMoveError(_("It is White's turn"));
6681             return;
6682         }
6683         break;
6684
6685       case MachinePlaysBlack:
6686         /* User is moving for White */
6687         if (!WhiteOnMove(currentMove)) {
6688             DisplayMoveError(_("It is Black's turn"));
6689             return;
6690         }
6691         break;
6692
6693       case PlayFromGameFile:
6694             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6695       case EditGame:
6696       case IcsExamining:
6697       case BeginningOfGame:
6698       case AnalyzeMode:
6699       case Training:
6700         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6701         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6702             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6703             /* User is moving for Black */
6704             if (WhiteOnMove(currentMove)) {
6705                 DisplayMoveError(_("It is White's turn"));
6706                 return;
6707             }
6708         } else {
6709             /* User is moving for White */
6710             if (!WhiteOnMove(currentMove)) {
6711                 DisplayMoveError(_("It is Black's turn"));
6712                 return;
6713             }
6714         }
6715         break;
6716
6717       case IcsPlayingBlack:
6718         /* User is moving for Black */
6719         if (WhiteOnMove(currentMove)) {
6720             if (!appData.premove) {
6721                 DisplayMoveError(_("It is White's turn"));
6722             } else if (toX >= 0 && toY >= 0) {
6723                 premoveToX = toX;
6724                 premoveToY = toY;
6725                 premoveFromX = fromX;
6726                 premoveFromY = fromY;
6727                 premovePromoChar = promoChar;
6728                 gotPremove = 1;
6729                 if (appData.debugMode)
6730                     fprintf(debugFP, "Got premove: fromX %d,"
6731                             "fromY %d, toX %d, toY %d\n",
6732                             fromX, fromY, toX, toY);
6733             }
6734             return;
6735         }
6736         break;
6737
6738       case IcsPlayingWhite:
6739         /* User is moving for White */
6740         if (!WhiteOnMove(currentMove)) {
6741             if (!appData.premove) {
6742                 DisplayMoveError(_("It is Black's turn"));
6743             } else if (toX >= 0 && toY >= 0) {
6744                 premoveToX = toX;
6745                 premoveToY = toY;
6746                 premoveFromX = fromX;
6747                 premoveFromY = fromY;
6748                 premovePromoChar = promoChar;
6749                 gotPremove = 1;
6750                 if (appData.debugMode)
6751                     fprintf(debugFP, "Got premove: fromX %d,"
6752                             "fromY %d, toX %d, toY %d\n",
6753                             fromX, fromY, toX, toY);
6754             }
6755             return;
6756         }
6757         break;
6758
6759       default:
6760         break;
6761
6762       case EditPosition:
6763         /* EditPosition, empty square, or different color piece;
6764            click-click move is possible */
6765         if (toX == -2 || toY == -2) {
6766             boards[0][fromY][fromX] = EmptySquare;
6767             DrawPosition(FALSE, boards[currentMove]);
6768             return;
6769         } else if (toX >= 0 && toY >= 0) {
6770             boards[0][toY][toX] = boards[0][fromY][fromX];
6771             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6772                 if(boards[0][fromY][0] != EmptySquare) {
6773                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6774                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6775                 }
6776             } else
6777             if(fromX == BOARD_RGHT+1) {
6778                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6779                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6780                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6781                 }
6782             } else
6783             boards[0][fromY][fromX] = gatingPiece;
6784             DrawPosition(FALSE, boards[currentMove]);
6785             return;
6786         }
6787         return;
6788     }
6789
6790     if(toX < 0 || toY < 0) return;
6791     pup = boards[currentMove][toY][toX];
6792
6793     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6794     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6795          if( pup != EmptySquare ) return;
6796          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6797            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6798                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6799            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6800            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6801            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6802            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6803          fromY = DROP_RANK;
6804     }
6805
6806     /* [HGM] always test for legality, to get promotion info */
6807     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6808                                          fromY, fromX, toY, toX, promoChar);
6809
6810     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6811
6812     /* [HGM] but possibly ignore an IllegalMove result */
6813     if (appData.testLegality) {
6814         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6815             DisplayMoveError(_("Illegal move"));
6816             return;
6817         }
6818     }
6819
6820     if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
6821         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
6822              ClearPremoveHighlights(); // was included
6823         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
6824         return;
6825     }
6826
6827     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6828 }
6829
6830 /* Common tail of UserMoveEvent and DropMenuEvent */
6831 int
6832 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
6833 {
6834     char *bookHit = 0;
6835
6836     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6837         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6838         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6839         if(WhiteOnMove(currentMove)) {
6840             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6841         } else {
6842             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6843         }
6844     }
6845
6846     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6847        move type in caller when we know the move is a legal promotion */
6848     if(moveType == NormalMove && promoChar)
6849         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6850
6851     /* [HGM] <popupFix> The following if has been moved here from
6852        UserMoveEvent(). Because it seemed to belong here (why not allow
6853        piece drops in training games?), and because it can only be
6854        performed after it is known to what we promote. */
6855     if (gameMode == Training) {
6856       /* compare the move played on the board to the next move in the
6857        * game. If they match, display the move and the opponent's response.
6858        * If they don't match, display an error message.
6859        */
6860       int saveAnimate;
6861       Board testBoard;
6862       CopyBoard(testBoard, boards[currentMove]);
6863       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6864
6865       if (CompareBoards(testBoard, boards[currentMove+1])) {
6866         ForwardInner(currentMove+1);
6867
6868         /* Autoplay the opponent's response.
6869          * if appData.animate was TRUE when Training mode was entered,
6870          * the response will be animated.
6871          */
6872         saveAnimate = appData.animate;
6873         appData.animate = animateTraining;
6874         ForwardInner(currentMove+1);
6875         appData.animate = saveAnimate;
6876
6877         /* check for the end of the game */
6878         if (currentMove >= forwardMostMove) {
6879           gameMode = PlayFromGameFile;
6880           ModeHighlight();
6881           SetTrainingModeOff();
6882           DisplayInformation(_("End of game"));
6883         }
6884       } else {
6885         DisplayError(_("Incorrect move"), 0);
6886       }
6887       return 1;
6888     }
6889
6890   /* Ok, now we know that the move is good, so we can kill
6891      the previous line in Analysis Mode */
6892   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6893                                 && currentMove < forwardMostMove) {
6894     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6895     else forwardMostMove = currentMove;
6896   }
6897
6898   ClearMap();
6899
6900   /* If we need the chess program but it's dead, restart it */
6901   ResurrectChessProgram();
6902
6903   /* A user move restarts a paused game*/
6904   if (pausing)
6905     PauseEvent();
6906
6907   thinkOutput[0] = NULLCHAR;
6908
6909   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6910
6911   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6912     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6913     return 1;
6914   }
6915
6916   if (gameMode == BeginningOfGame) {
6917     if (appData.noChessProgram) {
6918       gameMode = EditGame;
6919       SetGameInfo();
6920     } else {
6921       char buf[MSG_SIZ];
6922       gameMode = MachinePlaysBlack;
6923       StartClocks();
6924       SetGameInfo();
6925       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
6926       DisplayTitle(buf);
6927       if (first.sendName) {
6928         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6929         SendToProgram(buf, &first);
6930       }
6931       StartClocks();
6932     }
6933     ModeHighlight();
6934   }
6935
6936   /* Relay move to ICS or chess engine */
6937   if (appData.icsActive) {
6938     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6939         gameMode == IcsExamining) {
6940       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6941         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6942         SendToICS("draw ");
6943         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6944       }
6945       // also send plain move, in case ICS does not understand atomic claims
6946       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6947       ics_user_moved = 1;
6948     }
6949   } else {
6950     if (first.sendTime && (gameMode == BeginningOfGame ||
6951                            gameMode == MachinePlaysWhite ||
6952                            gameMode == MachinePlaysBlack)) {
6953       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6954     }
6955     if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
6956          // [HGM] book: if program might be playing, let it use book
6957         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6958         first.maybeThinking = TRUE;
6959     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
6960         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
6961         SendBoard(&first, currentMove+1);
6962         if(second.analyzing) {
6963             if(!second.useSetboard) SendToProgram("undo\n", &second);
6964             SendBoard(&second, currentMove+1);
6965         }
6966     } else {
6967         SendMoveToProgram(forwardMostMove-1, &first);
6968         if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
6969     }
6970     if (currentMove == cmailOldMove + 1) {
6971       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6972     }
6973   }
6974
6975   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6976
6977   switch (gameMode) {
6978   case EditGame:
6979     if(appData.testLegality)
6980     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6981     case MT_NONE:
6982     case MT_CHECK:
6983       break;
6984     case MT_CHECKMATE:
6985     case MT_STAINMATE:
6986       if (WhiteOnMove(currentMove)) {
6987         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6988       } else {
6989         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6990       }
6991       break;
6992     case MT_STALEMATE:
6993       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6994       break;
6995     }
6996     break;
6997
6998   case MachinePlaysBlack:
6999   case MachinePlaysWhite:
7000     /* disable certain menu options while machine is thinking */
7001     SetMachineThinkingEnables();
7002     break;
7003
7004   default:
7005     break;
7006   }
7007
7008   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7009   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7010
7011   if(bookHit) { // [HGM] book: simulate book reply
7012         static char bookMove[MSG_SIZ]; // a bit generous?
7013
7014         programStats.nodes = programStats.depth = programStats.time =
7015         programStats.score = programStats.got_only_move = 0;
7016         sprintf(programStats.movelist, "%s (xbook)", bookHit);
7017
7018         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7019         strcat(bookMove, bookHit);
7020         HandleMachineMove(bookMove, &first);
7021   }
7022   return 1;
7023 }
7024
7025 void
7026 MarkByFEN(char *fen)
7027 {
7028         int r, f;
7029         if(!appData.markers || !appData.highlightDragging) return;
7030         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 0;
7031         r=BOARD_HEIGHT-1; f=BOARD_LEFT;
7032         while(*fen) {
7033             int s = 0;
7034             marker[r][f] = 0;
7035             if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7036             if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 1; else
7037             if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7038             if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7039             if(*fen == 'T') marker[r][f++] = 0; else
7040             if(*fen == 'Y') marker[r][f++] = 1; else
7041             if(*fen == 'G') marker[r][f++] = 3; else
7042             if(*fen == 'B') marker[r][f++] = 4; else
7043             if(*fen == 'C') marker[r][f++] = 5; else
7044             if(*fen == 'M') marker[r][f++] = 6; else
7045             if(*fen == 'W') marker[r][f++] = 7; else
7046             if(*fen == 'D') marker[r][f++] = 8; else
7047             if(*fen == 'R') marker[r][f++] = 2; else {
7048                 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7049               f += s; fen -= s>0;
7050             }
7051             while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7052             if(r < 0) break;
7053             fen++;
7054         }
7055         DrawPosition(TRUE, NULL);
7056 }
7057
7058 void
7059 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7060 {
7061     typedef char Markers[BOARD_RANKS][BOARD_FILES];
7062     Markers *m = (Markers *) closure;
7063     if(rf == fromY && ff == fromX)
7064         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7065                          || kind == WhiteCapturesEnPassant
7066                          || kind == BlackCapturesEnPassant);
7067     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
7068 }
7069
7070 void
7071 MarkTargetSquares (int clear)
7072 {
7073   int x, y, sum=0;
7074   if(clear) { // no reason to ever suppress clearing
7075     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = 0;
7076     if(!sum) return; // nothing was cleared,no redraw needed
7077   } else {
7078     int capt = 0;
7079     if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7080        !appData.testLegality || gameMode == EditPosition) return;
7081     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7082     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7083       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7084       if(capt)
7085       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7086     }
7087   }
7088   DrawPosition(FALSE, NULL);
7089 }
7090
7091 int
7092 Explode (Board board, int fromX, int fromY, int toX, int toY)
7093 {
7094     if(gameInfo.variant == VariantAtomic &&
7095        (board[toY][toX] != EmptySquare ||                     // capture?
7096         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
7097                          board[fromY][fromX] == BlackPawn   )
7098       )) {
7099         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7100         return TRUE;
7101     }
7102     return FALSE;
7103 }
7104
7105 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7106
7107 int
7108 CanPromote (ChessSquare piece, int y)
7109 {
7110         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7111         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7112         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
7113            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7114            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7115          gameInfo.variant == VariantMakruk   || gameInfo.variant == VariantASEAN) return FALSE;
7116         return (piece == BlackPawn && y == 1 ||
7117                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
7118                 piece == BlackLance && y == 1 ||
7119                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
7120 }
7121
7122 void
7123 HoverEvent (int hiX, int hiY, int x, int y)
7124 {
7125         static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7126         int r, f;
7127         if(!first.highlight) return;
7128         if(hiX == -1 && hiY == -1 && x == fromX && y == fromY) // record markings 
7129           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7130             baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7131         else if(hiX != x || hiY != y) {
7132           // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7133           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7134             marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7135           if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7136             char buf[MSG_SIZ];
7137             snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7138             SendToProgram(buf, &first);
7139           }
7140           SetHighlights(fromX, fromY, x, y);
7141         }
7142 }
7143
7144 void ReportClick(char *action, int x, int y)
7145 {
7146         char buf[MSG_SIZ]; // Inform engine of what user does
7147         int r, f;
7148         if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7149           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 1, marker[r][f] = 0;
7150         if(!first.highlight || gameMode == EditPosition) return;
7151         snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7152         SendToProgram(buf, &first);
7153 }
7154
7155 void
7156 LeftClick (ClickType clickType, int xPix, int yPix)
7157 {
7158     int x, y;
7159     Boolean saveAnimate;
7160     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7161     char promoChoice = NULLCHAR;
7162     ChessSquare piece;
7163     static TimeMark lastClickTime, prevClickTime;
7164
7165     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7166
7167     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7168
7169     if (clickType == Press) ErrorPopDown();
7170     lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7171
7172     x = EventToSquare(xPix, BOARD_WIDTH);
7173     y = EventToSquare(yPix, BOARD_HEIGHT);
7174     if (!flipView && y >= 0) {
7175         y = BOARD_HEIGHT - 1 - y;
7176     }
7177     if (flipView && x >= 0) {
7178         x = BOARD_WIDTH - 1 - x;
7179     }
7180
7181     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7182         defaultPromoChoice = promoSweep;
7183         promoSweep = EmptySquare;   // terminate sweep
7184         promoDefaultAltered = TRUE;
7185         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7186     }
7187
7188     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7189         if(clickType == Release) return; // ignore upclick of click-click destination
7190         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7191         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7192         if(gameInfo.holdingsWidth &&
7193                 (WhiteOnMove(currentMove)
7194                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7195                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7196             // click in right holdings, for determining promotion piece
7197             ChessSquare p = boards[currentMove][y][x];
7198             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7199             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7200             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7201                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7202                 fromX = fromY = -1;
7203                 return;
7204             }
7205         }
7206         DrawPosition(FALSE, boards[currentMove]);
7207         return;
7208     }
7209
7210     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7211     if(clickType == Press
7212             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7213               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7214               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7215         return;
7216
7217     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7218         // could be static click on premove from-square: abort premove
7219         gotPremove = 0;
7220         ClearPremoveHighlights();
7221     }
7222
7223     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7224         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7225
7226     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7227         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7228                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7229         defaultPromoChoice = DefaultPromoChoice(side);
7230     }
7231
7232     autoQueen = appData.alwaysPromoteToQueen;
7233
7234     if (fromX == -1) {
7235       int originalY = y;
7236       gatingPiece = EmptySquare;
7237       if (clickType != Press) {
7238         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7239             DragPieceEnd(xPix, yPix); dragging = 0;
7240             DrawPosition(FALSE, NULL);
7241         }
7242         return;
7243       }
7244       doubleClick = FALSE;
7245       if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7246         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7247       }
7248       fromX = x; fromY = y; toX = toY = -1;
7249       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7250          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7251          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7252             /* First square */
7253             if (OKToStartUserMove(fromX, fromY)) {
7254                 second = 0;
7255                 ReportClick("lift", x, y);
7256                 MarkTargetSquares(0);
7257                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7258                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7259                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7260                     promoSweep = defaultPromoChoice;
7261                     selectFlag = 0; lastX = xPix; lastY = yPix;
7262                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7263                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7264                 }
7265                 if (appData.highlightDragging) {
7266                     SetHighlights(fromX, fromY, -1, -1);
7267                 } else {
7268                     ClearHighlights();
7269                 }
7270             } else fromX = fromY = -1;
7271             return;
7272         }
7273     }
7274
7275     /* fromX != -1 */
7276     if (clickType == Press && gameMode != EditPosition) {
7277         ChessSquare fromP;
7278         ChessSquare toP;
7279         int frc;
7280
7281         // ignore off-board to clicks
7282         if(y < 0 || x < 0) return;
7283
7284         /* Check if clicking again on the same color piece */
7285         fromP = boards[currentMove][fromY][fromX];
7286         toP = boards[currentMove][y][x];
7287         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
7288         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
7289              WhitePawn <= toP && toP <= WhiteKing &&
7290              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7291              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7292             (BlackPawn <= fromP && fromP <= BlackKing &&
7293              BlackPawn <= toP && toP <= BlackKing &&
7294              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7295              !(fromP == BlackKing && toP == BlackRook && frc))) {
7296             /* Clicked again on same color piece -- changed his mind */
7297             second = (x == fromX && y == fromY);
7298             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7299                 second = FALSE; // first double-click rather than scond click
7300                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7301             }
7302             promoDefaultAltered = FALSE;
7303             MarkTargetSquares(1);
7304            if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7305             if (appData.highlightDragging) {
7306                 SetHighlights(x, y, -1, -1);
7307             } else {
7308                 ClearHighlights();
7309             }
7310             if (OKToStartUserMove(x, y)) {
7311                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7312                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7313                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7314                  gatingPiece = boards[currentMove][fromY][fromX];
7315                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7316                 fromX = x;
7317                 fromY = y; dragging = 1;
7318                 ReportClick("lift", x, y);
7319                 MarkTargetSquares(0);
7320                 DragPieceBegin(xPix, yPix, FALSE);
7321                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7322                     promoSweep = defaultPromoChoice;
7323                     selectFlag = 0; lastX = xPix; lastY = yPix;
7324                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7325                 }
7326             }
7327            }
7328            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7329            second = FALSE;
7330         }
7331         // ignore clicks on holdings
7332         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7333     }
7334
7335     if (clickType == Release && x == fromX && y == fromY) {
7336         DragPieceEnd(xPix, yPix); dragging = 0;
7337         if(clearFlag) {
7338             // a deferred attempt to click-click move an empty square on top of a piece
7339             boards[currentMove][y][x] = EmptySquare;
7340             ClearHighlights();
7341             DrawPosition(FALSE, boards[currentMove]);
7342             fromX = fromY = -1; clearFlag = 0;
7343             return;
7344         }
7345         if (appData.animateDragging) {
7346             /* Undo animation damage if any */
7347             DrawPosition(FALSE, NULL);
7348         }
7349         if (second || sweepSelecting) {
7350             /* Second up/down in same square; just abort move */
7351             if(sweepSelecting) DrawPosition(FALSE, boards[currentMove]);
7352             second = sweepSelecting = 0;
7353             fromX = fromY = -1;
7354             gatingPiece = EmptySquare;
7355             MarkTargetSquares(1);
7356             ClearHighlights();
7357             gotPremove = 0;
7358             ClearPremoveHighlights();
7359         } else {
7360             /* First upclick in same square; start click-click mode */
7361             SetHighlights(x, y, -1, -1);
7362         }
7363         return;
7364     }
7365
7366     clearFlag = 0;
7367
7368     if(gameMode != EditPosition && !appData.testLegality && !legal[y][x]) {
7369         if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7370         DisplayMessage(_("only marked squares are legal"),"");
7371         DrawPosition(TRUE, NULL);
7372         return; // ignore to-click
7373     }
7374
7375     /* we now have a different from- and (possibly off-board) to-square */
7376     /* Completed move */
7377     if(!sweepSelecting) {
7378         toX = x;
7379         toY = y;
7380     } else sweepSelecting = 0; // this must be the up-click corresponding to the down-click that started the sweep
7381
7382     saveAnimate = appData.animate;
7383     if (clickType == Press) {
7384         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7385             // must be Edit Position mode with empty-square selected
7386             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7387             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7388             return;
7389         }
7390         if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7391           if(appData.sweepSelect) {
7392             ChessSquare piece = boards[currentMove][fromY][fromX];
7393             promoSweep = defaultPromoChoice;
7394             if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
7395             selectFlag = 0; lastX = xPix; lastY = yPix;
7396             Sweep(0); // Pawn that is going to promote: preview promotion piece
7397             sweepSelecting = 1;
7398             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7399             MarkTargetSquares(1);
7400           }
7401           return; // promo popup appears on up-click
7402         }
7403         /* Finish clickclick move */
7404         if (appData.animate || appData.highlightLastMove) {
7405             SetHighlights(fromX, fromY, toX, toY);
7406         } else {
7407             ClearHighlights();
7408         }
7409     } else {
7410 #if 0
7411 // [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
7412         /* Finish drag move */
7413         if (appData.highlightLastMove) {
7414             SetHighlights(fromX, fromY, toX, toY);
7415         } else {
7416             ClearHighlights();
7417         }
7418 #endif
7419         DragPieceEnd(xPix, yPix); dragging = 0;
7420         /* Don't animate move and drag both */
7421         appData.animate = FALSE;
7422     }
7423
7424     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7425     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7426         ChessSquare piece = boards[currentMove][fromY][fromX];
7427         if(gameMode == EditPosition && piece != EmptySquare &&
7428            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7429             int n;
7430
7431             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7432                 n = PieceToNumber(piece - (int)BlackPawn);
7433                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7434                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7435                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7436             } else
7437             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7438                 n = PieceToNumber(piece);
7439                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7440                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7441                 boards[currentMove][n][BOARD_WIDTH-2]++;
7442             }
7443             boards[currentMove][fromY][fromX] = EmptySquare;
7444         }
7445         ClearHighlights();
7446         fromX = fromY = -1;
7447         MarkTargetSquares(1);
7448         DrawPosition(TRUE, boards[currentMove]);
7449         return;
7450     }
7451
7452     // off-board moves should not be highlighted
7453     if(x < 0 || y < 0) ClearHighlights();
7454     else ReportClick("put", x, y);
7455
7456     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7457
7458     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7459         SetHighlights(fromX, fromY, toX, toY);
7460         MarkTargetSquares(1);
7461         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7462             // [HGM] super: promotion to captured piece selected from holdings
7463             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7464             promotionChoice = TRUE;
7465             // kludge follows to temporarily execute move on display, without promoting yet
7466             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7467             boards[currentMove][toY][toX] = p;
7468             DrawPosition(FALSE, boards[currentMove]);
7469             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7470             boards[currentMove][toY][toX] = q;
7471             DisplayMessage("Click in holdings to choose piece", "");
7472             return;
7473         }
7474         PromotionPopUp();
7475     } else {
7476         int oldMove = currentMove;
7477         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7478         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7479         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7480         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7481            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7482             DrawPosition(TRUE, boards[currentMove]);
7483         MarkTargetSquares(1);
7484         fromX = fromY = -1;
7485     }
7486     appData.animate = saveAnimate;
7487     if (appData.animate || appData.animateDragging) {
7488         /* Undo animation damage if needed */
7489         DrawPosition(FALSE, NULL);
7490     }
7491 }
7492
7493 int
7494 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7495 {   // front-end-free part taken out of PieceMenuPopup
7496     int whichMenu; int xSqr, ySqr;
7497
7498     if(seekGraphUp) { // [HGM] seekgraph
7499         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7500         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7501         return -2;
7502     }
7503
7504     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7505          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7506         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7507         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7508         if(action == Press)   {
7509             originalFlip = flipView;
7510             flipView = !flipView; // temporarily flip board to see game from partners perspective
7511             DrawPosition(TRUE, partnerBoard);
7512             DisplayMessage(partnerStatus, "");
7513             partnerUp = TRUE;
7514         } else if(action == Release) {
7515             flipView = originalFlip;
7516             DrawPosition(TRUE, boards[currentMove]);
7517             partnerUp = FALSE;
7518         }
7519         return -2;
7520     }
7521
7522     xSqr = EventToSquare(x, BOARD_WIDTH);
7523     ySqr = EventToSquare(y, BOARD_HEIGHT);
7524     if (action == Release) {
7525         if(pieceSweep != EmptySquare) {
7526             EditPositionMenuEvent(pieceSweep, toX, toY);
7527             pieceSweep = EmptySquare;
7528         } else UnLoadPV(); // [HGM] pv
7529     }
7530     if (action != Press) return -2; // return code to be ignored
7531     switch (gameMode) {
7532       case IcsExamining:
7533         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7534       case EditPosition:
7535         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7536         if (xSqr < 0 || ySqr < 0) return -1;
7537         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7538         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7539         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7540         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7541         NextPiece(0);
7542         return 2; // grab
7543       case IcsObserving:
7544         if(!appData.icsEngineAnalyze) return -1;
7545       case IcsPlayingWhite:
7546       case IcsPlayingBlack:
7547         if(!appData.zippyPlay) goto noZip;
7548       case AnalyzeMode:
7549       case AnalyzeFile:
7550       case MachinePlaysWhite:
7551       case MachinePlaysBlack:
7552       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7553         if (!appData.dropMenu) {
7554           LoadPV(x, y);
7555           return 2; // flag front-end to grab mouse events
7556         }
7557         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7558            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7559       case EditGame:
7560       noZip:
7561         if (xSqr < 0 || ySqr < 0) return -1;
7562         if (!appData.dropMenu || appData.testLegality &&
7563             gameInfo.variant != VariantBughouse &&
7564             gameInfo.variant != VariantCrazyhouse) return -1;
7565         whichMenu = 1; // drop menu
7566         break;
7567       default:
7568         return -1;
7569     }
7570
7571     if (((*fromX = xSqr) < 0) ||
7572         ((*fromY = ySqr) < 0)) {
7573         *fromX = *fromY = -1;
7574         return -1;
7575     }
7576     if (flipView)
7577       *fromX = BOARD_WIDTH - 1 - *fromX;
7578     else
7579       *fromY = BOARD_HEIGHT - 1 - *fromY;
7580
7581     return whichMenu;
7582 }
7583
7584 void
7585 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7586 {
7587 //    char * hint = lastHint;
7588     FrontEndProgramStats stats;
7589
7590     stats.which = cps == &first ? 0 : 1;
7591     stats.depth = cpstats->depth;
7592     stats.nodes = cpstats->nodes;
7593     stats.score = cpstats->score;
7594     stats.time = cpstats->time;
7595     stats.pv = cpstats->movelist;
7596     stats.hint = lastHint;
7597     stats.an_move_index = 0;
7598     stats.an_move_count = 0;
7599
7600     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7601         stats.hint = cpstats->move_name;
7602         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7603         stats.an_move_count = cpstats->nr_moves;
7604     }
7605
7606     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
7607
7608     SetProgramStats( &stats );
7609 }
7610
7611 void
7612 ClearEngineOutputPane (int which)
7613 {
7614     static FrontEndProgramStats dummyStats;
7615     dummyStats.which = which;
7616     dummyStats.pv = "#";
7617     SetProgramStats( &dummyStats );
7618 }
7619
7620 #define MAXPLAYERS 500
7621
7622 char *
7623 TourneyStandings (int display)
7624 {
7625     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7626     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7627     char result, *p, *names[MAXPLAYERS];
7628
7629     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7630         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7631     names[0] = p = strdup(appData.participants);
7632     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7633
7634     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7635
7636     while(result = appData.results[nr]) {
7637         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7638         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7639         wScore = bScore = 0;
7640         switch(result) {
7641           case '+': wScore = 2; break;
7642           case '-': bScore = 2; break;
7643           case '=': wScore = bScore = 1; break;
7644           case ' ':
7645           case '*': return strdup("busy"); // tourney not finished
7646         }
7647         score[w] += wScore;
7648         score[b] += bScore;
7649         games[w]++;
7650         games[b]++;
7651         nr++;
7652     }
7653     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7654     for(w=0; w<nPlayers; w++) {
7655         bScore = -1;
7656         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7657         ranking[w] = b; points[w] = bScore; score[b] = -2;
7658     }
7659     p = malloc(nPlayers*34+1);
7660     for(w=0; w<nPlayers && w<display; w++)
7661         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7662     free(names[0]);
7663     return p;
7664 }
7665
7666 void
7667 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7668 {       // count all piece types
7669         int p, f, r;
7670         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7671         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7672         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7673                 p = board[r][f];
7674                 pCnt[p]++;
7675                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7676                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7677                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7678                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7679                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7680                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7681         }
7682 }
7683
7684 int
7685 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7686 {
7687         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7688         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7689
7690         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7691         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7692         if(myPawns == 2 && nMine == 3) // KPP
7693             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7694         if(myPawns == 1 && nMine == 2) // KP
7695             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7696         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7697             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7698         if(myPawns) return FALSE;
7699         if(pCnt[WhiteRook+side])
7700             return pCnt[BlackRook-side] ||
7701                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7702                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7703                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7704         if(pCnt[WhiteCannon+side]) {
7705             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7706             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7707         }
7708         if(pCnt[WhiteKnight+side])
7709             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7710         return FALSE;
7711 }
7712
7713 int
7714 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7715 {
7716         VariantClass v = gameInfo.variant;
7717
7718         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7719         if(v == VariantShatranj) return TRUE; // always winnable through baring
7720         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7721         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7722
7723         if(v == VariantXiangqi) {
7724                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7725
7726                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7727                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7728                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7729                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7730                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7731                 if(stale) // we have at least one last-rank P plus perhaps C
7732                     return majors // KPKX
7733                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7734                 else // KCA*E*
7735                     return pCnt[WhiteFerz+side] // KCAK
7736                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7737                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7738                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7739
7740         } else if(v == VariantKnightmate) {
7741                 if(nMine == 1) return FALSE;
7742                 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
7743         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7744                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7745
7746                 if(nMine == 1) return FALSE; // bare King
7747                 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
7748                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7749                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7750                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7751                 if(pCnt[WhiteKnight+side])
7752                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7753                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7754                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7755                 if(nBishops)
7756                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7757                 if(pCnt[WhiteAlfil+side])
7758                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7759                 if(pCnt[WhiteWazir+side])
7760                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7761         }
7762
7763         return TRUE;
7764 }
7765
7766 int
7767 CompareWithRights (Board b1, Board b2)
7768 {
7769     int rights = 0;
7770     if(!CompareBoards(b1, b2)) return FALSE;
7771     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7772     /* compare castling rights */
7773     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7774            rights++; /* King lost rights, while rook still had them */
7775     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7776         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7777            rights++; /* but at least one rook lost them */
7778     }
7779     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7780            rights++;
7781     if( b1[CASTLING][5] != NoRights ) {
7782         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7783            rights++;
7784     }
7785     return rights == 0;
7786 }
7787
7788 int
7789 Adjudicate (ChessProgramState *cps)
7790 {       // [HGM] some adjudications useful with buggy engines
7791         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7792         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7793         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7794         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7795         int k, drop, count = 0; static int bare = 1;
7796         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7797         Boolean canAdjudicate = !appData.icsActive;
7798
7799         // most tests only when we understand the game, i.e. legality-checking on
7800             if( appData.testLegality )
7801             {   /* [HGM] Some more adjudications for obstinate engines */
7802                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7803                 static int moveCount = 6;
7804                 ChessMove result;
7805                 char *reason = NULL;
7806
7807                 /* Count what is on board. */
7808                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7809
7810                 /* Some material-based adjudications that have to be made before stalemate test */
7811                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7812                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7813                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7814                      if(canAdjudicate && appData.checkMates) {
7815                          if(engineOpponent)
7816                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7817                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7818                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7819                          return 1;
7820                      }
7821                 }
7822
7823                 /* Bare King in Shatranj (loses) or Losers (wins) */
7824                 if( nrW == 1 || nrB == 1) {
7825                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7826                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7827                      if(canAdjudicate && appData.checkMates) {
7828                          if(engineOpponent)
7829                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7830                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7831                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7832                          return 1;
7833                      }
7834                   } else
7835                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7836                   {    /* bare King */
7837                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7838                         if(canAdjudicate && appData.checkMates) {
7839                             /* but only adjudicate if adjudication enabled */
7840                             if(engineOpponent)
7841                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7842                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7843                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7844                             return 1;
7845                         }
7846                   }
7847                 } else bare = 1;
7848
7849
7850             // don't wait for engine to announce game end if we can judge ourselves
7851             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7852               case MT_CHECK:
7853                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7854                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7855                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7856                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7857                             checkCnt++;
7858                         if(checkCnt >= 2) {
7859                             reason = "Xboard adjudication: 3rd check";
7860                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7861                             break;
7862                         }
7863                     }
7864                 }
7865               case MT_NONE:
7866               default:
7867                 break;
7868               case MT_STALEMATE:
7869               case MT_STAINMATE:
7870                 reason = "Xboard adjudication: Stalemate";
7871                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7872                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7873                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7874                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7875                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7876                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7877                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7878                                                                         EP_CHECKMATE : EP_WINS);
7879                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
7880                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7881                 }
7882                 break;
7883               case MT_CHECKMATE:
7884                 reason = "Xboard adjudication: Checkmate";
7885                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7886                 if(gameInfo.variant == VariantShogi) {
7887                     if(forwardMostMove > backwardMostMove
7888                        && moveList[forwardMostMove-1][1] == '@'
7889                        && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
7890                         reason = "XBoard adjudication: pawn-drop mate";
7891                         boards[forwardMostMove][EP_STATUS] = EP_WINS;
7892                     }
7893                 }
7894                 break;
7895             }
7896
7897                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7898                     case EP_STALEMATE:
7899                         result = GameIsDrawn; break;
7900                     case EP_CHECKMATE:
7901                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7902                     case EP_WINS:
7903                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7904                     default:
7905                         result = EndOfFile;
7906                 }
7907                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7908                     if(engineOpponent)
7909                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7910                     GameEnds( result, reason, GE_XBOARD );
7911                     return 1;
7912                 }
7913
7914                 /* Next absolutely insufficient mating material. */
7915                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7916                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7917                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7918
7919                      /* always flag draws, for judging claims */
7920                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7921
7922                      if(canAdjudicate && appData.materialDraws) {
7923                          /* but only adjudicate them if adjudication enabled */
7924                          if(engineOpponent) {
7925                            SendToProgram("force\n", engineOpponent); // suppress reply
7926                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7927                          }
7928                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7929                          return 1;
7930                      }
7931                 }
7932
7933                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7934                 if(gameInfo.variant == VariantXiangqi ?
7935                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7936                  : nrW + nrB == 4 &&
7937                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7938                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7939                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7940                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7941                    ) ) {
7942                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7943                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7944                           if(engineOpponent) {
7945                             SendToProgram("force\n", engineOpponent); // suppress reply
7946                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7947                           }
7948                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7949                           return 1;
7950                      }
7951                 } else moveCount = 6;
7952             }
7953
7954         // Repetition draws and 50-move rule can be applied independently of legality testing
7955
7956                 /* Check for rep-draws */
7957                 count = 0;
7958                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
7959                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
7960                 for(k = forwardMostMove-2;
7961                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
7962                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7963                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
7964                     k-=2)
7965                 {   int rights=0;
7966                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7967                         /* compare castling rights */
7968                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7969                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7970                                 rights++; /* King lost rights, while rook still had them */
7971                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7972                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7973                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7974                                    rights++; /* but at least one rook lost them */
7975                         }
7976                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7977                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7978                                 rights++;
7979                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7980                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7981                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7982                                    rights++;
7983                         }
7984                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7985                             && appData.drawRepeats > 1) {
7986                              /* adjudicate after user-specified nr of repeats */
7987                              int result = GameIsDrawn;
7988                              char *details = "XBoard adjudication: repetition draw";
7989                              if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
7990                                 // [HGM] xiangqi: check for forbidden perpetuals
7991                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7992                                 for(m=forwardMostMove; m>k; m-=2) {
7993                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7994                                         ourPerpetual = 0; // the current mover did not always check
7995                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7996                                         hisPerpetual = 0; // the opponent did not always check
7997                                 }
7998                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7999                                                                         ourPerpetual, hisPerpetual);
8000                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8001                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8002                                     details = "Xboard adjudication: perpetual checking";
8003                                 } else
8004                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8005                                     break; // (or we would have caught him before). Abort repetition-checking loop.
8006                                 } else
8007                                 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8008                                     if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8009                                         result = BlackWins;
8010                                         details = "Xboard adjudication: repetition";
8011                                     }
8012                                 } else // it must be XQ
8013                                 // Now check for perpetual chases
8014                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8015                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
8016                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8017                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8018                                         static char resdet[MSG_SIZ];
8019                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8020                                         details = resdet;
8021                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8022                                     } else
8023                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
8024                                         break; // Abort repetition-checking loop.
8025                                 }
8026                                 // if neither of us is checking or chasing all the time, or both are, it is draw
8027                              }
8028                              if(engineOpponent) {
8029                                SendToProgram("force\n", engineOpponent); // suppress reply
8030                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8031                              }
8032                              GameEnds( result, details, GE_XBOARD );
8033                              return 1;
8034                         }
8035                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8036                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8037                     }
8038                 }
8039
8040                 /* Now we test for 50-move draws. Determine ply count */
8041                 count = forwardMostMove;
8042                 /* look for last irreversble move */
8043                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8044                     count--;
8045                 /* if we hit starting position, add initial plies */
8046                 if( count == backwardMostMove )
8047                     count -= initialRulePlies;
8048                 count = forwardMostMove - count;
8049                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8050                         // adjust reversible move counter for checks in Xiangqi
8051                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
8052                         if(i < backwardMostMove) i = backwardMostMove;
8053                         while(i <= forwardMostMove) {
8054                                 lastCheck = inCheck; // check evasion does not count
8055                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8056                                 if(inCheck || lastCheck) count--; // check does not count
8057                                 i++;
8058                         }
8059                 }
8060                 if( count >= 100)
8061                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8062                          /* this is used to judge if draw claims are legal */
8063                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8064                          if(engineOpponent) {
8065                            SendToProgram("force\n", engineOpponent); // suppress reply
8066                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8067                          }
8068                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8069                          return 1;
8070                 }
8071
8072                 /* if draw offer is pending, treat it as a draw claim
8073                  * when draw condition present, to allow engines a way to
8074                  * claim draws before making their move to avoid a race
8075                  * condition occurring after their move
8076                  */
8077                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8078                          char *p = NULL;
8079                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8080                              p = "Draw claim: 50-move rule";
8081                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8082                              p = "Draw claim: 3-fold repetition";
8083                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8084                              p = "Draw claim: insufficient mating material";
8085                          if( p != NULL && canAdjudicate) {
8086                              if(engineOpponent) {
8087                                SendToProgram("force\n", engineOpponent); // suppress reply
8088                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8089                              }
8090                              GameEnds( GameIsDrawn, p, GE_XBOARD );
8091                              return 1;
8092                          }
8093                 }
8094
8095                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8096                     if(engineOpponent) {
8097                       SendToProgram("force\n", engineOpponent); // suppress reply
8098                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8099                     }
8100                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8101                     return 1;
8102                 }
8103         return 0;
8104 }
8105
8106 char *
8107 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8108 {   // [HGM] book: this routine intercepts moves to simulate book replies
8109     char *bookHit = NULL;
8110
8111     //first determine if the incoming move brings opponent into his book
8112     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8113         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8114     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8115     if(bookHit != NULL && !cps->bookSuspend) {
8116         // make sure opponent is not going to reply after receiving move to book position
8117         SendToProgram("force\n", cps);
8118         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8119     }
8120     if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8121     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8122     // now arrange restart after book miss
8123     if(bookHit) {
8124         // after a book hit we never send 'go', and the code after the call to this routine
8125         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8126         char buf[MSG_SIZ], *move = bookHit;
8127         if(cps->useSAN) {
8128             int fromX, fromY, toX, toY;
8129             char promoChar;
8130             ChessMove moveType;
8131             move = buf + 30;
8132             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8133                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8134                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8135                                     PosFlags(forwardMostMove),
8136                                     fromY, fromX, toY, toX, promoChar, move);
8137             } else {
8138                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8139                 bookHit = NULL;
8140             }
8141         }
8142         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8143         SendToProgram(buf, cps);
8144         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8145     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8146         SendToProgram("go\n", cps);
8147         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8148     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8149         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8150             SendToProgram("go\n", cps);
8151         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8152     }
8153     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8154 }
8155
8156 int
8157 LoadError (char *errmess, ChessProgramState *cps)
8158 {   // unloads engine and switches back to -ncp mode if it was first
8159     if(cps->initDone) return FALSE;
8160     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8161     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8162     cps->pr = NoProc;
8163     if(cps == &first) {
8164         appData.noChessProgram = TRUE;
8165         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8166         gameMode = BeginningOfGame; ModeHighlight();
8167         SetNCPMode();
8168     }
8169     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8170     DisplayMessage("", ""); // erase waiting message
8171     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8172     return TRUE;
8173 }
8174
8175 char *savedMessage;
8176 ChessProgramState *savedState;
8177 void
8178 DeferredBookMove (void)
8179 {
8180         if(savedState->lastPing != savedState->lastPong)
8181                     ScheduleDelayedEvent(DeferredBookMove, 10);
8182         else
8183         HandleMachineMove(savedMessage, savedState);
8184 }
8185
8186 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8187 static ChessProgramState *stalledEngine;
8188 static char stashedInputMove[MSG_SIZ];
8189
8190 void
8191 HandleMachineMove (char *message, ChessProgramState *cps)
8192 {
8193     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8194     char realname[MSG_SIZ];
8195     int fromX, fromY, toX, toY;
8196     ChessMove moveType;
8197     char promoChar;
8198     char *p, *pv=buf1;
8199     int machineWhite, oldError;
8200     char *bookHit;
8201
8202     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8203         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8204         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8205             DisplayError(_("Invalid pairing from pairing engine"), 0);
8206             return;
8207         }
8208         pairingReceived = 1;
8209         NextMatchGame();
8210         return; // Skim the pairing messages here.
8211     }
8212
8213     oldError = cps->userError; cps->userError = 0;
8214
8215 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8216     /*
8217      * Kludge to ignore BEL characters
8218      */
8219     while (*message == '\007') message++;
8220
8221     /*
8222      * [HGM] engine debug message: ignore lines starting with '#' character
8223      */
8224     if(cps->debug && *message == '#') return;
8225
8226     /*
8227      * Look for book output
8228      */
8229     if (cps == &first && bookRequested) {
8230         if (message[0] == '\t' || message[0] == ' ') {
8231             /* Part of the book output is here; append it */
8232             strcat(bookOutput, message);
8233             strcat(bookOutput, "  \n");
8234             return;
8235         } else if (bookOutput[0] != NULLCHAR) {
8236             /* All of book output has arrived; display it */
8237             char *p = bookOutput;
8238             while (*p != NULLCHAR) {
8239                 if (*p == '\t') *p = ' ';
8240                 p++;
8241             }
8242             DisplayInformation(bookOutput);
8243             bookRequested = FALSE;
8244             /* Fall through to parse the current output */
8245         }
8246     }
8247
8248     /*
8249      * Look for machine move.
8250      */
8251     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8252         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8253     {
8254         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8255             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8256             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8257             stalledEngine = cps;
8258             if(appData.ponderNextMove) { // bring opponent out of ponder
8259                 if(gameMode == TwoMachinesPlay) {
8260                     if(cps->other->pause)
8261                         PauseEngine(cps->other);
8262                     else
8263                         SendToProgram("easy\n", cps->other);
8264                 }
8265             }
8266             StopClocks();
8267             return;
8268         }
8269
8270         /* This method is only useful on engines that support ping */
8271         if (cps->lastPing != cps->lastPong) {
8272           if (gameMode == BeginningOfGame) {
8273             /* Extra move from before last new; ignore */
8274             if (appData.debugMode) {
8275                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8276             }
8277           } else {
8278             if (appData.debugMode) {
8279                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8280                         cps->which, gameMode);
8281             }
8282
8283             SendToProgram("undo\n", cps);
8284           }
8285           return;
8286         }
8287
8288         switch (gameMode) {
8289           case BeginningOfGame:
8290             /* Extra move from before last reset; ignore */
8291             if (appData.debugMode) {
8292                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8293             }
8294             return;
8295
8296           case EndOfGame:
8297           case IcsIdle:
8298           default:
8299             /* Extra move after we tried to stop.  The mode test is
8300                not a reliable way of detecting this problem, but it's
8301                the best we can do on engines that don't support ping.
8302             */
8303             if (appData.debugMode) {
8304                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8305                         cps->which, gameMode);
8306             }
8307             SendToProgram("undo\n", cps);
8308             return;
8309
8310           case MachinePlaysWhite:
8311           case IcsPlayingWhite:
8312             machineWhite = TRUE;
8313             break;
8314
8315           case MachinePlaysBlack:
8316           case IcsPlayingBlack:
8317             machineWhite = FALSE;
8318             break;
8319
8320           case TwoMachinesPlay:
8321             machineWhite = (cps->twoMachinesColor[0] == 'w');
8322             break;
8323         }
8324         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8325             if (appData.debugMode) {
8326                 fprintf(debugFP,
8327                         "Ignoring move out of turn by %s, gameMode %d"
8328                         ", forwardMost %d\n",
8329                         cps->which, gameMode, forwardMostMove);
8330             }
8331             return;
8332         }
8333
8334         if(cps->alphaRank) AlphaRank(machineMove, 4);
8335         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8336                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8337             /* Machine move could not be parsed; ignore it. */
8338           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8339                     machineMove, _(cps->which));
8340             DisplayMoveError(buf1);
8341             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
8342                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
8343             if (gameMode == TwoMachinesPlay) {
8344               GameEnds(machineWhite ? BlackWins : WhiteWins,
8345                        buf1, GE_XBOARD);
8346             }
8347             return;
8348         }
8349
8350         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8351         /* So we have to redo legality test with true e.p. status here,  */
8352         /* to make sure an illegal e.p. capture does not slip through,   */
8353         /* to cause a forfeit on a justified illegal-move complaint      */
8354         /* of the opponent.                                              */
8355         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8356            ChessMove moveType;
8357            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8358                              fromY, fromX, toY, toX, promoChar);
8359             if(moveType == IllegalMove) {
8360               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8361                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8362                 GameEnds(machineWhite ? BlackWins : WhiteWins,
8363                            buf1, GE_XBOARD);
8364                 return;
8365            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
8366            /* [HGM] Kludge to handle engines that send FRC-style castling
8367               when they shouldn't (like TSCP-Gothic) */
8368            switch(moveType) {
8369              case WhiteASideCastleFR:
8370              case BlackASideCastleFR:
8371                toX+=2;
8372                currentMoveString[2]++;
8373                break;
8374              case WhiteHSideCastleFR:
8375              case BlackHSideCastleFR:
8376                toX--;
8377                currentMoveString[2]--;
8378                break;
8379              default: ; // nothing to do, but suppresses warning of pedantic compilers
8380            }
8381         }
8382         hintRequested = FALSE;
8383         lastHint[0] = NULLCHAR;
8384         bookRequested = FALSE;
8385         /* Program may be pondering now */
8386         cps->maybeThinking = TRUE;
8387         if (cps->sendTime == 2) cps->sendTime = 1;
8388         if (cps->offeredDraw) cps->offeredDraw--;
8389
8390         /* [AS] Save move info*/
8391         pvInfoList[ forwardMostMove ].score = programStats.score;
8392         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8393         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8394
8395         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8396
8397         /* Test suites abort the 'game' after one move */
8398         if(*appData.finger) {
8399            static FILE *f;
8400            char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8401            if(!f) f = fopen(appData.finger, "w");
8402            if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8403            else { DisplayFatalError("Bad output file", errno, 0); return; }
8404            free(fen);
8405            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8406         }
8407
8408         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8409         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8410             int count = 0;
8411
8412             while( count < adjudicateLossPlies ) {
8413                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8414
8415                 if( count & 1 ) {
8416                     score = -score; /* Flip score for winning side */
8417                 }
8418
8419                 if( score > adjudicateLossThreshold ) {
8420                     break;
8421                 }
8422
8423                 count++;
8424             }
8425
8426             if( count >= adjudicateLossPlies ) {
8427                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8428
8429                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8430                     "Xboard adjudication",
8431                     GE_XBOARD );
8432
8433                 return;
8434             }
8435         }
8436
8437         if(Adjudicate(cps)) {
8438             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8439             return; // [HGM] adjudicate: for all automatic game ends
8440         }
8441
8442 #if ZIPPY
8443         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8444             first.initDone) {
8445           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8446                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8447                 SendToICS("draw ");
8448                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8449           }
8450           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8451           ics_user_moved = 1;
8452           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8453                 char buf[3*MSG_SIZ];
8454
8455                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8456                         programStats.score / 100.,
8457                         programStats.depth,
8458                         programStats.time / 100.,
8459                         (unsigned int)programStats.nodes,
8460                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8461                         programStats.movelist);
8462                 SendToICS(buf);
8463 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8464           }
8465         }
8466 #endif
8467
8468         /* [AS] Clear stats for next move */
8469         ClearProgramStats();
8470         thinkOutput[0] = NULLCHAR;
8471         hiddenThinkOutputState = 0;
8472
8473         bookHit = NULL;
8474         if (gameMode == TwoMachinesPlay) {
8475             /* [HGM] relaying draw offers moved to after reception of move */
8476             /* and interpreting offer as claim if it brings draw condition */
8477             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8478                 SendToProgram("draw\n", cps->other);
8479             }
8480             if (cps->other->sendTime) {
8481                 SendTimeRemaining(cps->other,
8482                                   cps->other->twoMachinesColor[0] == 'w');
8483             }
8484             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8485             if (firstMove && !bookHit) {
8486                 firstMove = FALSE;
8487                 if (cps->other->useColors) {
8488                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8489                 }
8490                 SendToProgram("go\n", cps->other);
8491             }
8492             cps->other->maybeThinking = TRUE;
8493         }
8494
8495         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8496
8497         if (!pausing && appData.ringBellAfterMoves) {
8498             RingBell();
8499         }
8500
8501         /*
8502          * Reenable menu items that were disabled while
8503          * machine was thinking
8504          */
8505         if (gameMode != TwoMachinesPlay)
8506             SetUserThinkingEnables();
8507
8508         // [HGM] book: after book hit opponent has received move and is now in force mode
8509         // force the book reply into it, and then fake that it outputted this move by jumping
8510         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8511         if(bookHit) {
8512                 static char bookMove[MSG_SIZ]; // a bit generous?
8513
8514                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8515                 strcat(bookMove, bookHit);
8516                 message = bookMove;
8517                 cps = cps->other;
8518                 programStats.nodes = programStats.depth = programStats.time =
8519                 programStats.score = programStats.got_only_move = 0;
8520                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8521
8522                 if(cps->lastPing != cps->lastPong) {
8523                     savedMessage = message; // args for deferred call
8524                     savedState = cps;
8525                     ScheduleDelayedEvent(DeferredBookMove, 10);
8526                     return;
8527                 }
8528                 goto FakeBookMove;
8529         }
8530
8531         return;
8532     }
8533
8534     /* Set special modes for chess engines.  Later something general
8535      *  could be added here; for now there is just one kludge feature,
8536      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8537      *  when "xboard" is given as an interactive command.
8538      */
8539     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8540         cps->useSigint = FALSE;
8541         cps->useSigterm = FALSE;
8542     }
8543     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8544       ParseFeatures(message+8, cps);
8545       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8546     }
8547
8548     if (!strncmp(message, "setup ", 6) && 
8549         (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown || NonStandardBoardSize())
8550                                         ) { // [HGM] allow first engine to define opening position
8551       int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
8552       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8553       *buf = NULLCHAR;
8554       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8555       if(startedFromSetupPosition) return;
8556       dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
8557       if(dummy >= 3) {
8558         while(message[s] && message[s++] != ' ');
8559         if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
8560            dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
8561             appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
8562             if(dummy == 4) gameInfo.variant = StringToVariant(varName);     // parent variant
8563           InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
8564           if(*buf) SetCharTable(pieceToChar, buf); // do again, for it was spoiled by InitPosition
8565         }
8566       }
8567       ParseFEN(boards[0], &dummy, message+s, FALSE);
8568       DrawPosition(TRUE, boards[0]);
8569       startedFromSetupPosition = TRUE;
8570       return;
8571     }
8572     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8573      * want this, I was asked to put it in, and obliged.
8574      */
8575     if (!strncmp(message, "setboard ", 9)) {
8576         Board initial_position;
8577
8578         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8579
8580         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
8581             DisplayError(_("Bad FEN received from engine"), 0);
8582             return ;
8583         } else {
8584            Reset(TRUE, FALSE);
8585            CopyBoard(boards[0], initial_position);
8586            initialRulePlies = FENrulePlies;
8587            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8588            else gameMode = MachinePlaysBlack;
8589            DrawPosition(FALSE, boards[currentMove]);
8590         }
8591         return;
8592     }
8593
8594     /*
8595      * Look for communication commands
8596      */
8597     if (!strncmp(message, "telluser ", 9)) {
8598         if(message[9] == '\\' && message[10] == '\\')
8599             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8600         PlayTellSound();
8601         DisplayNote(message + 9);
8602         return;
8603     }
8604     if (!strncmp(message, "tellusererror ", 14)) {
8605         cps->userError = 1;
8606         if(message[14] == '\\' && message[15] == '\\')
8607             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8608         PlayTellSound();
8609         DisplayError(message + 14, 0);
8610         return;
8611     }
8612     if (!strncmp(message, "tellopponent ", 13)) {
8613       if (appData.icsActive) {
8614         if (loggedOn) {
8615           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8616           SendToICS(buf1);
8617         }
8618       } else {
8619         DisplayNote(message + 13);
8620       }
8621       return;
8622     }
8623     if (!strncmp(message, "tellothers ", 11)) {
8624       if (appData.icsActive) {
8625         if (loggedOn) {
8626           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8627           SendToICS(buf1);
8628         }
8629       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
8630       return;
8631     }
8632     if (!strncmp(message, "tellall ", 8)) {
8633       if (appData.icsActive) {
8634         if (loggedOn) {
8635           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8636           SendToICS(buf1);
8637         }
8638       } else {
8639         DisplayNote(message + 8);
8640       }
8641       return;
8642     }
8643     if (strncmp(message, "warning", 7) == 0) {
8644         /* Undocumented feature, use tellusererror in new code */
8645         DisplayError(message, 0);
8646         return;
8647     }
8648     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8649         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8650         strcat(realname, " query");
8651         AskQuestion(realname, buf2, buf1, cps->pr);
8652         return;
8653     }
8654     /* Commands from the engine directly to ICS.  We don't allow these to be
8655      *  sent until we are logged on. Crafty kibitzes have been known to
8656      *  interfere with the login process.
8657      */
8658     if (loggedOn) {
8659         if (!strncmp(message, "tellics ", 8)) {
8660             SendToICS(message + 8);
8661             SendToICS("\n");
8662             return;
8663         }
8664         if (!strncmp(message, "tellicsnoalias ", 15)) {
8665             SendToICS(ics_prefix);
8666             SendToICS(message + 15);
8667             SendToICS("\n");
8668             return;
8669         }
8670         /* The following are for backward compatibility only */
8671         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8672             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8673             SendToICS(ics_prefix);
8674             SendToICS(message);
8675             SendToICS("\n");
8676             return;
8677         }
8678     }
8679     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8680         return;
8681     }
8682     if(!strncmp(message, "highlight ", 10)) {
8683         if(appData.testLegality && appData.markers) return;
8684         MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
8685         return;
8686     }
8687     if(!strncmp(message, "click ", 6)) {
8688         char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
8689         if(appData.testLegality || !appData.oneClick) return;
8690         sscanf(message+6, "%c%d%c", &f, &y, &c);
8691         x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
8692         if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
8693         x = x*squareSize + (x+1)*lineGap + squareSize/2;
8694         y = y*squareSize + (y+1)*lineGap + squareSize/2;
8695         f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
8696         if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
8697             LeftClick(Release, lastLeftX, lastLeftY);
8698         controlKey  = (c == ',');
8699         LeftClick(Press, x, y);
8700         LeftClick(Release, x, y);
8701         first.highlight = f;
8702         return;
8703     }
8704     /*
8705      * If the move is illegal, cancel it and redraw the board.
8706      * Also deal with other error cases.  Matching is rather loose
8707      * here to accommodate engines written before the spec.
8708      */
8709     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8710         strncmp(message, "Error", 5) == 0) {
8711         if (StrStr(message, "name") ||
8712             StrStr(message, "rating") || StrStr(message, "?") ||
8713             StrStr(message, "result") || StrStr(message, "board") ||
8714             StrStr(message, "bk") || StrStr(message, "computer") ||
8715             StrStr(message, "variant") || StrStr(message, "hint") ||
8716             StrStr(message, "random") || StrStr(message, "depth") ||
8717             StrStr(message, "accepted")) {
8718             return;
8719         }
8720         if (StrStr(message, "protover")) {
8721           /* Program is responding to input, so it's apparently done
8722              initializing, and this error message indicates it is
8723              protocol version 1.  So we don't need to wait any longer
8724              for it to initialize and send feature commands. */
8725           FeatureDone(cps, 1);
8726           cps->protocolVersion = 1;
8727           return;
8728         }
8729         cps->maybeThinking = FALSE;
8730
8731         if (StrStr(message, "draw")) {
8732             /* Program doesn't have "draw" command */
8733             cps->sendDrawOffers = 0;
8734             return;
8735         }
8736         if (cps->sendTime != 1 &&
8737             (StrStr(message, "time") || StrStr(message, "otim"))) {
8738           /* Program apparently doesn't have "time" or "otim" command */
8739           cps->sendTime = 0;
8740           return;
8741         }
8742         if (StrStr(message, "analyze")) {
8743             cps->analysisSupport = FALSE;
8744             cps->analyzing = FALSE;
8745 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8746             EditGameEvent(); // [HGM] try to preserve loaded game
8747             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8748             DisplayError(buf2, 0);
8749             return;
8750         }
8751         if (StrStr(message, "(no matching move)st")) {
8752           /* Special kludge for GNU Chess 4 only */
8753           cps->stKludge = TRUE;
8754           SendTimeControl(cps, movesPerSession, timeControl,
8755                           timeIncrement, appData.searchDepth,
8756                           searchTime);
8757           return;
8758         }
8759         if (StrStr(message, "(no matching move)sd")) {
8760           /* Special kludge for GNU Chess 4 only */
8761           cps->sdKludge = TRUE;
8762           SendTimeControl(cps, movesPerSession, timeControl,
8763                           timeIncrement, appData.searchDepth,
8764                           searchTime);
8765           return;
8766         }
8767         if (!StrStr(message, "llegal")) {
8768             return;
8769         }
8770         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8771             gameMode == IcsIdle) return;
8772         if (forwardMostMove <= backwardMostMove) return;
8773         if (pausing) PauseEvent();
8774       if(appData.forceIllegal) {
8775             // [HGM] illegal: machine refused move; force position after move into it
8776           SendToProgram("force\n", cps);
8777           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8778                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8779                 // when black is to move, while there might be nothing on a2 or black
8780                 // might already have the move. So send the board as if white has the move.
8781                 // But first we must change the stm of the engine, as it refused the last move
8782                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8783                 if(WhiteOnMove(forwardMostMove)) {
8784                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8785                     SendBoard(cps, forwardMostMove); // kludgeless board
8786                 } else {
8787                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8788                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8789                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8790                 }
8791           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8792             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8793                  gameMode == TwoMachinesPlay)
8794               SendToProgram("go\n", cps);
8795             return;
8796       } else
8797         if (gameMode == PlayFromGameFile) {
8798             /* Stop reading this game file */
8799             gameMode = EditGame;
8800             ModeHighlight();
8801         }
8802         /* [HGM] illegal-move claim should forfeit game when Xboard */
8803         /* only passes fully legal moves                            */
8804         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8805             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8806                                 "False illegal-move claim", GE_XBOARD );
8807             return; // do not take back move we tested as valid
8808         }
8809         currentMove = forwardMostMove-1;
8810         DisplayMove(currentMove-1); /* before DisplayMoveError */
8811         SwitchClocks(forwardMostMove-1); // [HGM] race
8812         DisplayBothClocks();
8813         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8814                 parseList[currentMove], _(cps->which));
8815         DisplayMoveError(buf1);
8816         DrawPosition(FALSE, boards[currentMove]);
8817
8818         SetUserThinkingEnables();
8819         return;
8820     }
8821     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8822         /* Program has a broken "time" command that
8823            outputs a string not ending in newline.
8824            Don't use it. */
8825         cps->sendTime = 0;
8826     }
8827
8828     /*
8829      * If chess program startup fails, exit with an error message.
8830      * Attempts to recover here are futile. [HGM] Well, we try anyway
8831      */
8832     if ((StrStr(message, "unknown host") != NULL)
8833         || (StrStr(message, "No remote directory") != NULL)
8834         || (StrStr(message, "not found") != NULL)
8835         || (StrStr(message, "No such file") != NULL)
8836         || (StrStr(message, "can't alloc") != NULL)
8837         || (StrStr(message, "Permission denied") != NULL)) {
8838
8839         cps->maybeThinking = FALSE;
8840         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8841                 _(cps->which), cps->program, cps->host, message);
8842         RemoveInputSource(cps->isr);
8843         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8844             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
8845             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
8846         }
8847         return;
8848     }
8849
8850     /*
8851      * Look for hint output
8852      */
8853     if (sscanf(message, "Hint: %s", buf1) == 1) {
8854         if (cps == &first && hintRequested) {
8855             hintRequested = FALSE;
8856             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8857                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8858                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8859                                     PosFlags(forwardMostMove),
8860                                     fromY, fromX, toY, toX, promoChar, buf1);
8861                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8862                 DisplayInformation(buf2);
8863             } else {
8864                 /* Hint move could not be parsed!? */
8865               snprintf(buf2, sizeof(buf2),
8866                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8867                         buf1, _(cps->which));
8868                 DisplayError(buf2, 0);
8869             }
8870         } else {
8871           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8872         }
8873         return;
8874     }
8875
8876     /*
8877      * Ignore other messages if game is not in progress
8878      */
8879     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8880         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8881
8882     /*
8883      * look for win, lose, draw, or draw offer
8884      */
8885     if (strncmp(message, "1-0", 3) == 0) {
8886         char *p, *q, *r = "";
8887         p = strchr(message, '{');
8888         if (p) {
8889             q = strchr(p, '}');
8890             if (q) {
8891                 *q = NULLCHAR;
8892                 r = p + 1;
8893             }
8894         }
8895         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8896         return;
8897     } else if (strncmp(message, "0-1", 3) == 0) {
8898         char *p, *q, *r = "";
8899         p = strchr(message, '{');
8900         if (p) {
8901             q = strchr(p, '}');
8902             if (q) {
8903                 *q = NULLCHAR;
8904                 r = p + 1;
8905             }
8906         }
8907         /* Kludge for Arasan 4.1 bug */
8908         if (strcmp(r, "Black resigns") == 0) {
8909             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8910             return;
8911         }
8912         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8913         return;
8914     } else if (strncmp(message, "1/2", 3) == 0) {
8915         char *p, *q, *r = "";
8916         p = strchr(message, '{');
8917         if (p) {
8918             q = strchr(p, '}');
8919             if (q) {
8920                 *q = NULLCHAR;
8921                 r = p + 1;
8922             }
8923         }
8924
8925         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8926         return;
8927
8928     } else if (strncmp(message, "White resign", 12) == 0) {
8929         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8930         return;
8931     } else if (strncmp(message, "Black resign", 12) == 0) {
8932         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8933         return;
8934     } else if (strncmp(message, "White matches", 13) == 0 ||
8935                strncmp(message, "Black matches", 13) == 0   ) {
8936         /* [HGM] ignore GNUShogi noises */
8937         return;
8938     } else if (strncmp(message, "White", 5) == 0 &&
8939                message[5] != '(' &&
8940                StrStr(message, "Black") == NULL) {
8941         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8942         return;
8943     } else if (strncmp(message, "Black", 5) == 0 &&
8944                message[5] != '(') {
8945         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8946         return;
8947     } else if (strcmp(message, "resign") == 0 ||
8948                strcmp(message, "computer resigns") == 0) {
8949         switch (gameMode) {
8950           case MachinePlaysBlack:
8951           case IcsPlayingBlack:
8952             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8953             break;
8954           case MachinePlaysWhite:
8955           case IcsPlayingWhite:
8956             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8957             break;
8958           case TwoMachinesPlay:
8959             if (cps->twoMachinesColor[0] == 'w')
8960               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8961             else
8962               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8963             break;
8964           default:
8965             /* can't happen */
8966             break;
8967         }
8968         return;
8969     } else if (strncmp(message, "opponent mates", 14) == 0) {
8970         switch (gameMode) {
8971           case MachinePlaysBlack:
8972           case IcsPlayingBlack:
8973             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8974             break;
8975           case MachinePlaysWhite:
8976           case IcsPlayingWhite:
8977             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8978             break;
8979           case TwoMachinesPlay:
8980             if (cps->twoMachinesColor[0] == 'w')
8981               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8982             else
8983               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8984             break;
8985           default:
8986             /* can't happen */
8987             break;
8988         }
8989         return;
8990     } else if (strncmp(message, "computer mates", 14) == 0) {
8991         switch (gameMode) {
8992           case MachinePlaysBlack:
8993           case IcsPlayingBlack:
8994             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8995             break;
8996           case MachinePlaysWhite:
8997           case IcsPlayingWhite:
8998             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8999             break;
9000           case TwoMachinesPlay:
9001             if (cps->twoMachinesColor[0] == 'w')
9002               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9003             else
9004               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9005             break;
9006           default:
9007             /* can't happen */
9008             break;
9009         }
9010         return;
9011     } else if (strncmp(message, "checkmate", 9) == 0) {
9012         if (WhiteOnMove(forwardMostMove)) {
9013             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9014         } else {
9015             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9016         }
9017         return;
9018     } else if (strstr(message, "Draw") != NULL ||
9019                strstr(message, "game is a draw") != NULL) {
9020         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9021         return;
9022     } else if (strstr(message, "offer") != NULL &&
9023                strstr(message, "draw") != NULL) {
9024 #if ZIPPY
9025         if (appData.zippyPlay && first.initDone) {
9026             /* Relay offer to ICS */
9027             SendToICS(ics_prefix);
9028             SendToICS("draw\n");
9029         }
9030 #endif
9031         cps->offeredDraw = 2; /* valid until this engine moves twice */
9032         if (gameMode == TwoMachinesPlay) {
9033             if (cps->other->offeredDraw) {
9034                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9035             /* [HGM] in two-machine mode we delay relaying draw offer      */
9036             /* until after we also have move, to see if it is really claim */
9037             }
9038         } else if (gameMode == MachinePlaysWhite ||
9039                    gameMode == MachinePlaysBlack) {
9040           if (userOfferedDraw) {
9041             DisplayInformation(_("Machine accepts your draw offer"));
9042             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9043           } else {
9044             DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9045           }
9046         }
9047     }
9048
9049
9050     /*
9051      * Look for thinking output
9052      */
9053     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9054           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9055                                 ) {
9056         int plylev, mvleft, mvtot, curscore, time;
9057         char mvname[MOVE_LEN];
9058         u64 nodes; // [DM]
9059         char plyext;
9060         int ignore = FALSE;
9061         int prefixHint = FALSE;
9062         mvname[0] = NULLCHAR;
9063
9064         switch (gameMode) {
9065           case MachinePlaysBlack:
9066           case IcsPlayingBlack:
9067             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9068             break;
9069           case MachinePlaysWhite:
9070           case IcsPlayingWhite:
9071             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9072             break;
9073           case AnalyzeMode:
9074           case AnalyzeFile:
9075             break;
9076           case IcsObserving: /* [DM] icsEngineAnalyze */
9077             if (!appData.icsEngineAnalyze) ignore = TRUE;
9078             break;
9079           case TwoMachinesPlay:
9080             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9081                 ignore = TRUE;
9082             }
9083             break;
9084           default:
9085             ignore = TRUE;
9086             break;
9087         }
9088
9089         if (!ignore) {
9090             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9091             buf1[0] = NULLCHAR;
9092             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9093                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9094
9095                 if (plyext != ' ' && plyext != '\t') {
9096                     time *= 100;
9097                 }
9098
9099                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9100                 if( cps->scoreIsAbsolute &&
9101                     ( gameMode == MachinePlaysBlack ||
9102                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9103                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
9104                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9105                      !WhiteOnMove(currentMove)
9106                     ) )
9107                 {
9108                     curscore = -curscore;
9109                 }
9110
9111                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9112
9113                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9114                         char buf[MSG_SIZ];
9115                         FILE *f;
9116                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9117                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9118                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9119                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9120                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
9121                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9122                                 fclose(f);
9123                         }
9124                         else
9125                           /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9126                           DisplayError(_("failed writing PV"), 0);
9127                 }
9128
9129                 tempStats.depth = plylev;
9130                 tempStats.nodes = nodes;
9131                 tempStats.time = time;
9132                 tempStats.score = curscore;
9133                 tempStats.got_only_move = 0;
9134
9135                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9136                         int ticklen;
9137
9138                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
9139                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9140                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9141                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9142                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9143                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9144                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9145                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9146                 }
9147
9148                 /* Buffer overflow protection */
9149                 if (pv[0] != NULLCHAR) {
9150                     if (strlen(pv) >= sizeof(tempStats.movelist)
9151                         && appData.debugMode) {
9152                         fprintf(debugFP,
9153                                 "PV is too long; using the first %u bytes.\n",
9154                                 (unsigned) sizeof(tempStats.movelist) - 1);
9155                     }
9156
9157                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9158                 } else {
9159                     sprintf(tempStats.movelist, " no PV\n");
9160                 }
9161
9162                 if (tempStats.seen_stat) {
9163                     tempStats.ok_to_send = 1;
9164                 }
9165
9166                 if (strchr(tempStats.movelist, '(') != NULL) {
9167                     tempStats.line_is_book = 1;
9168                     tempStats.nr_moves = 0;
9169                     tempStats.moves_left = 0;
9170                 } else {
9171                     tempStats.line_is_book = 0;
9172                 }
9173
9174                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9175                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9176
9177                 SendProgramStatsToFrontend( cps, &tempStats );
9178
9179                 /*
9180                     [AS] Protect the thinkOutput buffer from overflow... this
9181                     is only useful if buf1 hasn't overflowed first!
9182                 */
9183                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
9184                          plylev,
9185                          (gameMode == TwoMachinesPlay ?
9186                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9187                          ((double) curscore) / 100.0,
9188                          prefixHint ? lastHint : "",
9189                          prefixHint ? " " : "" );
9190
9191                 if( buf1[0] != NULLCHAR ) {
9192                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9193
9194                     if( strlen(pv) > max_len ) {
9195                         if( appData.debugMode) {
9196                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9197                         }
9198                         pv[max_len+1] = '\0';
9199                     }
9200
9201                     strcat( thinkOutput, pv);
9202                 }
9203
9204                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9205                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9206                     DisplayMove(currentMove - 1);
9207                 }
9208                 return;
9209
9210             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9211                 /* crafty (9.25+) says "(only move) <move>"
9212                  * if there is only 1 legal move
9213                  */
9214                 sscanf(p, "(only move) %s", buf1);
9215                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9216                 sprintf(programStats.movelist, "%s (only move)", buf1);
9217                 programStats.depth = 1;
9218                 programStats.nr_moves = 1;
9219                 programStats.moves_left = 1;
9220                 programStats.nodes = 1;
9221                 programStats.time = 1;
9222                 programStats.got_only_move = 1;
9223
9224                 /* Not really, but we also use this member to
9225                    mean "line isn't going to change" (Crafty
9226                    isn't searching, so stats won't change) */
9227                 programStats.line_is_book = 1;
9228
9229                 SendProgramStatsToFrontend( cps, &programStats );
9230
9231                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9232                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9233                     DisplayMove(currentMove - 1);
9234                 }
9235                 return;
9236             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9237                               &time, &nodes, &plylev, &mvleft,
9238                               &mvtot, mvname) >= 5) {
9239                 /* The stat01: line is from Crafty (9.29+) in response
9240                    to the "." command */
9241                 programStats.seen_stat = 1;
9242                 cps->maybeThinking = TRUE;
9243
9244                 if (programStats.got_only_move || !appData.periodicUpdates)
9245                   return;
9246
9247                 programStats.depth = plylev;
9248                 programStats.time = time;
9249                 programStats.nodes = nodes;
9250                 programStats.moves_left = mvleft;
9251                 programStats.nr_moves = mvtot;
9252                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9253                 programStats.ok_to_send = 1;
9254                 programStats.movelist[0] = '\0';
9255
9256                 SendProgramStatsToFrontend( cps, &programStats );
9257
9258                 return;
9259
9260             } else if (strncmp(message,"++",2) == 0) {
9261                 /* Crafty 9.29+ outputs this */
9262                 programStats.got_fail = 2;
9263                 return;
9264
9265             } else if (strncmp(message,"--",2) == 0) {
9266                 /* Crafty 9.29+ outputs this */
9267                 programStats.got_fail = 1;
9268                 return;
9269
9270             } else if (thinkOutput[0] != NULLCHAR &&
9271                        strncmp(message, "    ", 4) == 0) {
9272                 unsigned message_len;
9273
9274                 p = message;
9275                 while (*p && *p == ' ') p++;
9276
9277                 message_len = strlen( p );
9278
9279                 /* [AS] Avoid buffer overflow */
9280                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9281                     strcat(thinkOutput, " ");
9282                     strcat(thinkOutput, p);
9283                 }
9284
9285                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9286                     strcat(programStats.movelist, " ");
9287                     strcat(programStats.movelist, p);
9288                 }
9289
9290                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9291                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9292                     DisplayMove(currentMove - 1);
9293                 }
9294                 return;
9295             }
9296         }
9297         else {
9298             buf1[0] = NULLCHAR;
9299
9300             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9301                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9302             {
9303                 ChessProgramStats cpstats;
9304
9305                 if (plyext != ' ' && plyext != '\t') {
9306                     time *= 100;
9307                 }
9308
9309                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9310                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9311                     curscore = -curscore;
9312                 }
9313
9314                 cpstats.depth = plylev;
9315                 cpstats.nodes = nodes;
9316                 cpstats.time = time;
9317                 cpstats.score = curscore;
9318                 cpstats.got_only_move = 0;
9319                 cpstats.movelist[0] = '\0';
9320
9321                 if (buf1[0] != NULLCHAR) {
9322                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9323                 }
9324
9325                 cpstats.ok_to_send = 0;
9326                 cpstats.line_is_book = 0;
9327                 cpstats.nr_moves = 0;
9328                 cpstats.moves_left = 0;
9329
9330                 SendProgramStatsToFrontend( cps, &cpstats );
9331             }
9332         }
9333     }
9334 }
9335
9336
9337 /* Parse a game score from the character string "game", and
9338    record it as the history of the current game.  The game
9339    score is NOT assumed to start from the standard position.
9340    The display is not updated in any way.
9341    */
9342 void
9343 ParseGameHistory (char *game)
9344 {
9345     ChessMove moveType;
9346     int fromX, fromY, toX, toY, boardIndex;
9347     char promoChar;
9348     char *p, *q;
9349     char buf[MSG_SIZ];
9350
9351     if (appData.debugMode)
9352       fprintf(debugFP, "Parsing game history: %s\n", game);
9353
9354     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9355     gameInfo.site = StrSave(appData.icsHost);
9356     gameInfo.date = PGNDate();
9357     gameInfo.round = StrSave("-");
9358
9359     /* Parse out names of players */
9360     while (*game == ' ') game++;
9361     p = buf;
9362     while (*game != ' ') *p++ = *game++;
9363     *p = NULLCHAR;
9364     gameInfo.white = StrSave(buf);
9365     while (*game == ' ') game++;
9366     p = buf;
9367     while (*game != ' ' && *game != '\n') *p++ = *game++;
9368     *p = NULLCHAR;
9369     gameInfo.black = StrSave(buf);
9370
9371     /* Parse moves */
9372     boardIndex = blackPlaysFirst ? 1 : 0;
9373     yynewstr(game);
9374     for (;;) {
9375         yyboardindex = boardIndex;
9376         moveType = (ChessMove) Myylex();
9377         switch (moveType) {
9378           case IllegalMove:             /* maybe suicide chess, etc. */
9379   if (appData.debugMode) {
9380     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9381     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9382     setbuf(debugFP, NULL);
9383   }
9384           case WhitePromotion:
9385           case BlackPromotion:
9386           case WhiteNonPromotion:
9387           case BlackNonPromotion:
9388           case NormalMove:
9389           case WhiteCapturesEnPassant:
9390           case BlackCapturesEnPassant:
9391           case WhiteKingSideCastle:
9392           case WhiteQueenSideCastle:
9393           case BlackKingSideCastle:
9394           case BlackQueenSideCastle:
9395           case WhiteKingSideCastleWild:
9396           case WhiteQueenSideCastleWild:
9397           case BlackKingSideCastleWild:
9398           case BlackQueenSideCastleWild:
9399           /* PUSH Fabien */
9400           case WhiteHSideCastleFR:
9401           case WhiteASideCastleFR:
9402           case BlackHSideCastleFR:
9403           case BlackASideCastleFR:
9404           /* POP Fabien */
9405             fromX = currentMoveString[0] - AAA;
9406             fromY = currentMoveString[1] - ONE;
9407             toX = currentMoveString[2] - AAA;
9408             toY = currentMoveString[3] - ONE;
9409             promoChar = currentMoveString[4];
9410             break;
9411           case WhiteDrop:
9412           case BlackDrop:
9413             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9414             fromX = moveType == WhiteDrop ?
9415               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9416             (int) CharToPiece(ToLower(currentMoveString[0]));
9417             fromY = DROP_RANK;
9418             toX = currentMoveString[2] - AAA;
9419             toY = currentMoveString[3] - ONE;
9420             promoChar = NULLCHAR;
9421             break;
9422           case AmbiguousMove:
9423             /* bug? */
9424             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9425   if (appData.debugMode) {
9426     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9427     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9428     setbuf(debugFP, NULL);
9429   }
9430             DisplayError(buf, 0);
9431             return;
9432           case ImpossibleMove:
9433             /* bug? */
9434             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9435   if (appData.debugMode) {
9436     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9437     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9438     setbuf(debugFP, NULL);
9439   }
9440             DisplayError(buf, 0);
9441             return;
9442           case EndOfFile:
9443             if (boardIndex < backwardMostMove) {
9444                 /* Oops, gap.  How did that happen? */
9445                 DisplayError(_("Gap in move list"), 0);
9446                 return;
9447             }
9448             backwardMostMove =  blackPlaysFirst ? 1 : 0;
9449             if (boardIndex > forwardMostMove) {
9450                 forwardMostMove = boardIndex;
9451             }
9452             return;
9453           case ElapsedTime:
9454             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9455                 strcat(parseList[boardIndex-1], " ");
9456                 strcat(parseList[boardIndex-1], yy_text);
9457             }
9458             continue;
9459           case Comment:
9460           case PGNTag:
9461           case NAG:
9462           default:
9463             /* ignore */
9464             continue;
9465           case WhiteWins:
9466           case BlackWins:
9467           case GameIsDrawn:
9468           case GameUnfinished:
9469             if (gameMode == IcsExamining) {
9470                 if (boardIndex < backwardMostMove) {
9471                     /* Oops, gap.  How did that happen? */
9472                     return;
9473                 }
9474                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9475                 return;
9476             }
9477             gameInfo.result = moveType;
9478             p = strchr(yy_text, '{');
9479             if (p == NULL) p = strchr(yy_text, '(');
9480             if (p == NULL) {
9481                 p = yy_text;
9482                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9483             } else {
9484                 q = strchr(p, *p == '{' ? '}' : ')');
9485                 if (q != NULL) *q = NULLCHAR;
9486                 p++;
9487             }
9488             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9489             gameInfo.resultDetails = StrSave(p);
9490             continue;
9491         }
9492         if (boardIndex >= forwardMostMove &&
9493             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9494             backwardMostMove = blackPlaysFirst ? 1 : 0;
9495             return;
9496         }
9497         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9498                                  fromY, fromX, toY, toX, promoChar,
9499                                  parseList[boardIndex]);
9500         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9501         /* currentMoveString is set as a side-effect of yylex */
9502         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9503         strcat(moveList[boardIndex], "\n");
9504         boardIndex++;
9505         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9506         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9507           case MT_NONE:
9508           case MT_STALEMATE:
9509           default:
9510             break;
9511           case MT_CHECK:
9512             if(gameInfo.variant != VariantShogi)
9513                 strcat(parseList[boardIndex - 1], "+");
9514             break;
9515           case MT_CHECKMATE:
9516           case MT_STAINMATE:
9517             strcat(parseList[boardIndex - 1], "#");
9518             break;
9519         }
9520     }
9521 }
9522
9523
9524 /* Apply a move to the given board  */
9525 void
9526 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9527 {
9528   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9529   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9530
9531     /* [HGM] compute & store e.p. status and castling rights for new position */
9532     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9533
9534       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9535       oldEP = (signed char)board[EP_STATUS];
9536       board[EP_STATUS] = EP_NONE;
9537
9538   if (fromY == DROP_RANK) {
9539         /* must be first */
9540         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9541             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9542             return;
9543         }
9544         piece = board[toY][toX] = (ChessSquare) fromX;
9545   } else {
9546       int i;
9547
9548       if( board[toY][toX] != EmptySquare )
9549            board[EP_STATUS] = EP_CAPTURE;
9550
9551       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9552            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9553                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9554       } else
9555       if( board[fromY][fromX] == WhitePawn ) {
9556            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9557                board[EP_STATUS] = EP_PAWN_MOVE;
9558            if( toY-fromY==2) {
9559                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9560                         gameInfo.variant != VariantBerolina || toX < fromX)
9561                       board[EP_STATUS] = toX | berolina;
9562                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9563                         gameInfo.variant != VariantBerolina || toX > fromX)
9564                       board[EP_STATUS] = toX;
9565            }
9566       } else
9567       if( board[fromY][fromX] == BlackPawn ) {
9568            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9569                board[EP_STATUS] = EP_PAWN_MOVE;
9570            if( toY-fromY== -2) {
9571                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9572                         gameInfo.variant != VariantBerolina || toX < fromX)
9573                       board[EP_STATUS] = toX | berolina;
9574                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9575                         gameInfo.variant != VariantBerolina || toX > fromX)
9576                       board[EP_STATUS] = toX;
9577            }
9578        }
9579
9580        for(i=0; i<nrCastlingRights; i++) {
9581            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9582               board[CASTLING][i] == toX   && castlingRank[i] == toY
9583              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9584        }
9585
9586        if(gameInfo.variant == VariantSChess) { // update virginity
9587            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
9588            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
9589            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
9590            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
9591        }
9592
9593      if (fromX == toX && fromY == toY) return;
9594
9595      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9596      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9597      if(gameInfo.variant == VariantKnightmate)
9598          king += (int) WhiteUnicorn - (int) WhiteKing;
9599
9600     /* Code added by Tord: */
9601     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9602     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9603         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9604       board[fromY][fromX] = EmptySquare;
9605       board[toY][toX] = EmptySquare;
9606       if((toX > fromX) != (piece == WhiteRook)) {
9607         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9608       } else {
9609         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9610       }
9611     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9612                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9613       board[fromY][fromX] = EmptySquare;
9614       board[toY][toX] = EmptySquare;
9615       if((toX > fromX) != (piece == BlackRook)) {
9616         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9617       } else {
9618         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9619       }
9620     /* End of code added by Tord */
9621
9622     } else if (board[fromY][fromX] == king
9623         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9624         && toY == fromY && toX > fromX+1) {
9625         board[fromY][fromX] = EmptySquare;
9626         board[toY][toX] = king;
9627         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9628         board[fromY][BOARD_RGHT-1] = EmptySquare;
9629     } else if (board[fromY][fromX] == king
9630         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9631                && toY == fromY && toX < fromX-1) {
9632         board[fromY][fromX] = EmptySquare;
9633         board[toY][toX] = king;
9634         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9635         board[fromY][BOARD_LEFT] = EmptySquare;
9636     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9637                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9638                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9639                ) {
9640         /* white pawn promotion */
9641         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9642         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9643             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9644         board[fromY][fromX] = EmptySquare;
9645     } else if ((fromY >= BOARD_HEIGHT>>1)
9646                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9647                && (toX != fromX)
9648                && gameInfo.variant != VariantXiangqi
9649                && gameInfo.variant != VariantBerolina
9650                && (board[fromY][fromX] == WhitePawn)
9651                && (board[toY][toX] == EmptySquare)) {
9652         board[fromY][fromX] = EmptySquare;
9653         board[toY][toX] = WhitePawn;
9654         captured = board[toY - 1][toX];
9655         board[toY - 1][toX] = EmptySquare;
9656     } else if ((fromY == BOARD_HEIGHT-4)
9657                && (toX == fromX)
9658                && gameInfo.variant == VariantBerolina
9659                && (board[fromY][fromX] == WhitePawn)
9660                && (board[toY][toX] == EmptySquare)) {
9661         board[fromY][fromX] = EmptySquare;
9662         board[toY][toX] = WhitePawn;
9663         if(oldEP & EP_BEROLIN_A) {
9664                 captured = board[fromY][fromX-1];
9665                 board[fromY][fromX-1] = EmptySquare;
9666         }else{  captured = board[fromY][fromX+1];
9667                 board[fromY][fromX+1] = EmptySquare;
9668         }
9669     } else if (board[fromY][fromX] == king
9670         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9671                && toY == fromY && toX > fromX+1) {
9672         board[fromY][fromX] = EmptySquare;
9673         board[toY][toX] = king;
9674         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9675         board[fromY][BOARD_RGHT-1] = EmptySquare;
9676     } else if (board[fromY][fromX] == king
9677         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9678                && toY == fromY && toX < fromX-1) {
9679         board[fromY][fromX] = EmptySquare;
9680         board[toY][toX] = king;
9681         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9682         board[fromY][BOARD_LEFT] = EmptySquare;
9683     } else if (fromY == 7 && fromX == 3
9684                && board[fromY][fromX] == BlackKing
9685                && toY == 7 && toX == 5) {
9686         board[fromY][fromX] = EmptySquare;
9687         board[toY][toX] = BlackKing;
9688         board[fromY][7] = EmptySquare;
9689         board[toY][4] = BlackRook;
9690     } else if (fromY == 7 && fromX == 3
9691                && board[fromY][fromX] == BlackKing
9692                && toY == 7 && toX == 1) {
9693         board[fromY][fromX] = EmptySquare;
9694         board[toY][toX] = BlackKing;
9695         board[fromY][0] = EmptySquare;
9696         board[toY][2] = BlackRook;
9697     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9698                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9699                && toY < promoRank && promoChar
9700                ) {
9701         /* black pawn promotion */
9702         board[toY][toX] = CharToPiece(ToLower(promoChar));
9703         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9704             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9705         board[fromY][fromX] = EmptySquare;
9706     } else if ((fromY < BOARD_HEIGHT>>1)
9707                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9708                && (toX != fromX)
9709                && gameInfo.variant != VariantXiangqi
9710                && gameInfo.variant != VariantBerolina
9711                && (board[fromY][fromX] == BlackPawn)
9712                && (board[toY][toX] == EmptySquare)) {
9713         board[fromY][fromX] = EmptySquare;
9714         board[toY][toX] = BlackPawn;
9715         captured = board[toY + 1][toX];
9716         board[toY + 1][toX] = EmptySquare;
9717     } else if ((fromY == 3)
9718                && (toX == fromX)
9719                && gameInfo.variant == VariantBerolina
9720                && (board[fromY][fromX] == BlackPawn)
9721                && (board[toY][toX] == EmptySquare)) {
9722         board[fromY][fromX] = EmptySquare;
9723         board[toY][toX] = BlackPawn;
9724         if(oldEP & EP_BEROLIN_A) {
9725                 captured = board[fromY][fromX-1];
9726                 board[fromY][fromX-1] = EmptySquare;
9727         }else{  captured = board[fromY][fromX+1];
9728                 board[fromY][fromX+1] = EmptySquare;
9729         }
9730     } else {
9731         board[toY][toX] = board[fromY][fromX];
9732         board[fromY][fromX] = EmptySquare;
9733     }
9734   }
9735
9736     if (gameInfo.holdingsWidth != 0) {
9737
9738       /* !!A lot more code needs to be written to support holdings  */
9739       /* [HGM] OK, so I have written it. Holdings are stored in the */
9740       /* penultimate board files, so they are automaticlly stored   */
9741       /* in the game history.                                       */
9742       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9743                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9744         /* Delete from holdings, by decreasing count */
9745         /* and erasing image if necessary            */
9746         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9747         if(p < (int) BlackPawn) { /* white drop */
9748              p -= (int)WhitePawn;
9749                  p = PieceToNumber((ChessSquare)p);
9750              if(p >= gameInfo.holdingsSize) p = 0;
9751              if(--board[p][BOARD_WIDTH-2] <= 0)
9752                   board[p][BOARD_WIDTH-1] = EmptySquare;
9753              if((int)board[p][BOARD_WIDTH-2] < 0)
9754                         board[p][BOARD_WIDTH-2] = 0;
9755         } else {                  /* black drop */
9756              p -= (int)BlackPawn;
9757                  p = PieceToNumber((ChessSquare)p);
9758              if(p >= gameInfo.holdingsSize) p = 0;
9759              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9760                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9761              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9762                         board[BOARD_HEIGHT-1-p][1] = 0;
9763         }
9764       }
9765       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9766           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9767         /* [HGM] holdings: Add to holdings, if holdings exist */
9768         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9769                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9770                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9771         }
9772         p = (int) captured;
9773         if (p >= (int) BlackPawn) {
9774           p -= (int)BlackPawn;
9775           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9776                   /* in Shogi restore piece to its original  first */
9777                   captured = (ChessSquare) (DEMOTED captured);
9778                   p = DEMOTED p;
9779           }
9780           p = PieceToNumber((ChessSquare)p);
9781           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9782           board[p][BOARD_WIDTH-2]++;
9783           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9784         } else {
9785           p -= (int)WhitePawn;
9786           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9787                   captured = (ChessSquare) (DEMOTED captured);
9788                   p = DEMOTED p;
9789           }
9790           p = PieceToNumber((ChessSquare)p);
9791           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9792           board[BOARD_HEIGHT-1-p][1]++;
9793           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9794         }
9795       }
9796     } else if (gameInfo.variant == VariantAtomic) {
9797       if (captured != EmptySquare) {
9798         int y, x;
9799         for (y = toY-1; y <= toY+1; y++) {
9800           for (x = toX-1; x <= toX+1; x++) {
9801             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9802                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9803               board[y][x] = EmptySquare;
9804             }
9805           }
9806         }
9807         board[toY][toX] = EmptySquare;
9808       }
9809     }
9810     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9811         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9812     } else
9813     if(promoChar == '+') {
9814         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
9815         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9816     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9817         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9818         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
9819            && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
9820         board[toY][toX] = newPiece;
9821     }
9822     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9823                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9824         // [HGM] superchess: take promotion piece out of holdings
9825         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9826         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9827             if(!--board[k][BOARD_WIDTH-2])
9828                 board[k][BOARD_WIDTH-1] = EmptySquare;
9829         } else {
9830             if(!--board[BOARD_HEIGHT-1-k][1])
9831                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9832         }
9833     }
9834
9835 }
9836
9837 /* Updates forwardMostMove */
9838 void
9839 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
9840 {
9841 //    forwardMostMove++; // [HGM] bare: moved downstream
9842
9843     (void) CoordsToAlgebraic(boards[forwardMostMove],
9844                              PosFlags(forwardMostMove),
9845                              fromY, fromX, toY, toX, promoChar,
9846                              parseList[forwardMostMove]);
9847
9848     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9849         int timeLeft; static int lastLoadFlag=0; int king, piece;
9850         piece = boards[forwardMostMove][fromY][fromX];
9851         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9852         if(gameInfo.variant == VariantKnightmate)
9853             king += (int) WhiteUnicorn - (int) WhiteKing;
9854         if(forwardMostMove == 0) {
9855             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9856                 fprintf(serverMoves, "%s;", UserName());
9857             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9858                 fprintf(serverMoves, "%s;", second.tidy);
9859             fprintf(serverMoves, "%s;", first.tidy);
9860             if(gameMode == MachinePlaysWhite)
9861                 fprintf(serverMoves, "%s;", UserName());
9862             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9863                 fprintf(serverMoves, "%s;", second.tidy);
9864         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9865         lastLoadFlag = loadFlag;
9866         // print base move
9867         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9868         // print castling suffix
9869         if( toY == fromY && piece == king ) {
9870             if(toX-fromX > 1)
9871                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9872             if(fromX-toX >1)
9873                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9874         }
9875         // e.p. suffix
9876         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9877              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9878              boards[forwardMostMove][toY][toX] == EmptySquare
9879              && fromX != toX && fromY != toY)
9880                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9881         // promotion suffix
9882         if(promoChar != NULLCHAR) {
9883             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
9884                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
9885                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
9886             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
9887         }
9888         if(!loadFlag) {
9889                 char buf[MOVE_LEN*2], *p; int len;
9890             fprintf(serverMoves, "/%d/%d",
9891                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9892             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9893             else                      timeLeft = blackTimeRemaining/1000;
9894             fprintf(serverMoves, "/%d", timeLeft);
9895                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
9896                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
9897                 if(p = strchr(buf, '=')) *p = NULLCHAR;
9898                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
9899             fprintf(serverMoves, "/%s", buf);
9900         }
9901         fflush(serverMoves);
9902     }
9903
9904     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
9905         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
9906       return;
9907     }
9908     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9909     if (commentList[forwardMostMove+1] != NULL) {
9910         free(commentList[forwardMostMove+1]);
9911         commentList[forwardMostMove+1] = NULL;
9912     }
9913     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9914     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9915     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9916     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9917     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9918     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9919     adjustedClock = FALSE;
9920     gameInfo.result = GameUnfinished;
9921     if (gameInfo.resultDetails != NULL) {
9922         free(gameInfo.resultDetails);
9923         gameInfo.resultDetails = NULL;
9924     }
9925     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9926                               moveList[forwardMostMove - 1]);
9927     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9928       case MT_NONE:
9929       case MT_STALEMATE:
9930       default:
9931         break;
9932       case MT_CHECK:
9933         if(gameInfo.variant != VariantShogi)
9934             strcat(parseList[forwardMostMove - 1], "+");
9935         break;
9936       case MT_CHECKMATE:
9937       case MT_STAINMATE:
9938         strcat(parseList[forwardMostMove - 1], "#");
9939         break;
9940     }
9941
9942 }
9943
9944 /* Updates currentMove if not pausing */
9945 void
9946 ShowMove (int fromX, int fromY, int toX, int toY)
9947 {
9948     int instant = (gameMode == PlayFromGameFile) ?
9949         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9950     if(appData.noGUI) return;
9951     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9952         if (!instant) {
9953             if (forwardMostMove == currentMove + 1) {
9954                 AnimateMove(boards[forwardMostMove - 1],
9955                             fromX, fromY, toX, toY);
9956             }
9957         }
9958         currentMove = forwardMostMove;
9959     }
9960
9961     if (instant) return;
9962
9963     DisplayMove(currentMove - 1);
9964     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9965             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
9966                 SetHighlights(fromX, fromY, toX, toY);
9967             }
9968     }
9969     DrawPosition(FALSE, boards[currentMove]);
9970     DisplayBothClocks();
9971     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9972 }
9973
9974 void
9975 SendEgtPath (ChessProgramState *cps)
9976 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9977         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9978
9979         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9980
9981         while(*p) {
9982             char c, *q = name+1, *r, *s;
9983
9984             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9985             while(*p && *p != ',') *q++ = *p++;
9986             *q++ = ':'; *q = 0;
9987             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9988                 strcmp(name, ",nalimov:") == 0 ) {
9989                 // take nalimov path from the menu-changeable option first, if it is defined
9990               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9991                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9992             } else
9993             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9994                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9995                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9996                 s = r = StrStr(s, ":") + 1; // beginning of path info
9997                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9998                 c = *r; *r = 0;             // temporarily null-terminate path info
9999                     *--q = 0;               // strip of trailig ':' from name
10000                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10001                 *r = c;
10002                 SendToProgram(buf,cps);     // send egtbpath command for this format
10003             }
10004             if(*p == ',') p++; // read away comma to position for next format name
10005         }
10006 }
10007
10008 static int
10009 NonStandardBoardSize ()
10010 {
10011       /* [HGM] Awkward testing. Should really be a table */
10012       int overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
10013       if( gameInfo.variant == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10014       if( gameInfo.variant == VariantXiangqi )
10015            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
10016       if( gameInfo.variant == VariantShogi )
10017            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
10018       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
10019            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
10020       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
10021           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
10022            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
10023       if( gameInfo.variant == VariantCourier )
10024            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
10025       if( gameInfo.variant == VariantSuper )
10026            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
10027       if( gameInfo.variant == VariantGreat )
10028            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
10029       if( gameInfo.variant == VariantSChess )
10030            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
10031       if( gameInfo.variant == VariantGrand )
10032            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
10033       return overruled;
10034 }
10035
10036 void
10037 InitChessProgram (ChessProgramState *cps, int setup)
10038 /* setup needed to setup FRC opening position */
10039 {
10040     char buf[MSG_SIZ], b[MSG_SIZ];
10041     if (appData.noChessProgram) return;
10042     hintRequested = FALSE;
10043     bookRequested = FALSE;
10044
10045     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10046     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10047     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10048     if(cps->memSize) { /* [HGM] memory */
10049       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10050         SendToProgram(buf, cps);
10051     }
10052     SendEgtPath(cps); /* [HGM] EGT */
10053     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10054       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10055         SendToProgram(buf, cps);
10056     }
10057
10058     SendToProgram(cps->initString, cps);
10059     if (gameInfo.variant != VariantNormal &&
10060         gameInfo.variant != VariantLoadable
10061         /* [HGM] also send variant if board size non-standard */
10062         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
10063                                             ) {
10064       char *v = VariantName(gameInfo.variant);
10065       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
10066         /* [HGM] in protocol 1 we have to assume all variants valid */
10067         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
10068         DisplayFatalError(buf, 0, 1);
10069         return;
10070       }
10071
10072       if(NonStandardBoardSize()) { /* [HGM] make prefix for non-standard board size. */
10073         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
10074                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
10075            /* [HGM] varsize: try first if this defiant size variant is specifically known */
10076            if(StrStr(cps->variants, b) == NULL) {
10077                // specific sized variant not known, check if general sizing allowed
10078                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
10079                    if(StrStr(cps->variants, "boardsize") == NULL) {
10080                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10081                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
10082                        DisplayFatalError(buf, 0, 1);
10083                        return;
10084                    }
10085                    /* [HGM] here we really should compare with the maximum supported board size */
10086                }
10087            }
10088       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
10089       snprintf(buf, MSG_SIZ, "variant %s\n", b);
10090       SendToProgram(buf, cps);
10091     }
10092     currentlyInitializedVariant = gameInfo.variant;
10093
10094     /* [HGM] send opening position in FRC to first engine */
10095     if(setup) {
10096           SendToProgram("force\n", cps);
10097           SendBoard(cps, 0);
10098           /* engine is now in force mode! Set flag to wake it up after first move. */
10099           setboardSpoiledMachineBlack = 1;
10100     }
10101
10102     if (cps->sendICS) {
10103       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10104       SendToProgram(buf, cps);
10105     }
10106     cps->maybeThinking = FALSE;
10107     cps->offeredDraw = 0;
10108     if (!appData.icsActive) {
10109         SendTimeControl(cps, movesPerSession, timeControl,
10110                         timeIncrement, appData.searchDepth,
10111                         searchTime);
10112     }
10113     if (appData.showThinking
10114         // [HGM] thinking: four options require thinking output to be sent
10115         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10116                                 ) {
10117         SendToProgram("post\n", cps);
10118     }
10119     SendToProgram("hard\n", cps);
10120     if (!appData.ponderNextMove) {
10121         /* Warning: "easy" is a toggle in GNU Chess, so don't send
10122            it without being sure what state we are in first.  "hard"
10123            is not a toggle, so that one is OK.
10124          */
10125         SendToProgram("easy\n", cps);
10126     }
10127     if (cps->usePing) {
10128       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
10129       SendToProgram(buf, cps);
10130     }
10131     cps->initDone = TRUE;
10132     ClearEngineOutputPane(cps == &second);
10133 }
10134
10135
10136 void
10137 ResendOptions (ChessProgramState *cps)
10138 { // send the stored value of the options
10139   int i;
10140   char buf[MSG_SIZ];
10141   Option *opt = cps->option;
10142   for(i=0; i<cps->nrOptions; i++, opt++) {
10143       switch(opt->type) {
10144         case Spin:
10145         case Slider:
10146         case CheckBox:
10147             snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10148           break;
10149         case ComboBox:
10150           snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10151           break;
10152         default:
10153             snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10154           break;
10155         case Button:
10156         case SaveButton:
10157           continue;
10158       }
10159       SendToProgram(buf, cps);
10160   }
10161 }
10162
10163 void
10164 StartChessProgram (ChessProgramState *cps)
10165 {
10166     char buf[MSG_SIZ];
10167     int err;
10168
10169     if (appData.noChessProgram) return;
10170     cps->initDone = FALSE;
10171
10172     if (strcmp(cps->host, "localhost") == 0) {
10173         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10174     } else if (*appData.remoteShell == NULLCHAR) {
10175         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10176     } else {
10177         if (*appData.remoteUser == NULLCHAR) {
10178           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10179                     cps->program);
10180         } else {
10181           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10182                     cps->host, appData.remoteUser, cps->program);
10183         }
10184         err = StartChildProcess(buf, "", &cps->pr);
10185     }
10186
10187     if (err != 0) {
10188       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10189         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10190         if(cps != &first) return;
10191         appData.noChessProgram = TRUE;
10192         ThawUI();
10193         SetNCPMode();
10194 //      DisplayFatalError(buf, err, 1);
10195 //      cps->pr = NoProc;
10196 //      cps->isr = NULL;
10197         return;
10198     }
10199
10200     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10201     if (cps->protocolVersion > 1) {
10202       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10203       if(!cps->reload) { // do not clear options when reloading because of -xreuse
10204         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10205         cps->comboCnt = 0;  //                and values of combo boxes
10206       }
10207       SendToProgram(buf, cps);
10208       if(cps->reload) ResendOptions(cps);
10209     } else {
10210       SendToProgram("xboard\n", cps);
10211     }
10212 }
10213
10214 void
10215 TwoMachinesEventIfReady P((void))
10216 {
10217   static int curMess = 0;
10218   if (first.lastPing != first.lastPong) {
10219     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10220     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10221     return;
10222   }
10223   if (second.lastPing != second.lastPong) {
10224     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10225     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10226     return;
10227   }
10228   DisplayMessage("", ""); curMess = 0;
10229   TwoMachinesEvent();
10230 }
10231
10232 char *
10233 MakeName (char *template)
10234 {
10235     time_t clock;
10236     struct tm *tm;
10237     static char buf[MSG_SIZ];
10238     char *p = buf;
10239     int i;
10240
10241     clock = time((time_t *)NULL);
10242     tm = localtime(&clock);
10243
10244     while(*p++ = *template++) if(p[-1] == '%') {
10245         switch(*template++) {
10246           case 0:   *p = 0; return buf;
10247           case 'Y': i = tm->tm_year+1900; break;
10248           case 'y': i = tm->tm_year-100; break;
10249           case 'M': i = tm->tm_mon+1; break;
10250           case 'd': i = tm->tm_mday; break;
10251           case 'h': i = tm->tm_hour; break;
10252           case 'm': i = tm->tm_min; break;
10253           case 's': i = tm->tm_sec; break;
10254           default:  i = 0;
10255         }
10256         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10257     }
10258     return buf;
10259 }
10260
10261 int
10262 CountPlayers (char *p)
10263 {
10264     int n = 0;
10265     while(p = strchr(p, '\n')) p++, n++; // count participants
10266     return n;
10267 }
10268
10269 FILE *
10270 WriteTourneyFile (char *results, FILE *f)
10271 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10272     if(f == NULL) f = fopen(appData.tourneyFile, "w");
10273     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10274         // create a file with tournament description
10275         fprintf(f, "-participants {%s}\n", appData.participants);
10276         fprintf(f, "-seedBase %d\n", appData.seedBase);
10277         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10278         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10279         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10280         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10281         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10282         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10283         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10284         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10285         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10286         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10287         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10288         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10289         fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
10290         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10291         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10292         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10293         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10294         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10295         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10296         fprintf(f, "-smpCores %d\n", appData.smpCores);
10297         if(searchTime > 0)
10298                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10299         else {
10300                 fprintf(f, "-mps %d\n", appData.movesPerSession);
10301                 fprintf(f, "-tc %s\n", appData.timeControl);
10302                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10303         }
10304         fprintf(f, "-results \"%s\"\n", results);
10305     }
10306     return f;
10307 }
10308
10309 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10310
10311 void
10312 Substitute (char *participants, int expunge)
10313 {
10314     int i, changed, changes=0, nPlayers=0;
10315     char *p, *q, *r, buf[MSG_SIZ];
10316     if(participants == NULL) return;
10317     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10318     r = p = participants; q = appData.participants;
10319     while(*p && *p == *q) {
10320         if(*p == '\n') r = p+1, nPlayers++;
10321         p++; q++;
10322     }
10323     if(*p) { // difference
10324         while(*p && *p++ != '\n');
10325         while(*q && *q++ != '\n');
10326       changed = nPlayers;
10327         changes = 1 + (strcmp(p, q) != 0);
10328     }
10329     if(changes == 1) { // a single engine mnemonic was changed
10330         q = r; while(*q) nPlayers += (*q++ == '\n');
10331         p = buf; while(*r && (*p = *r++) != '\n') p++;
10332         *p = NULLCHAR;
10333         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10334         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10335         if(mnemonic[i]) { // The substitute is valid
10336             FILE *f;
10337             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10338                 flock(fileno(f), LOCK_EX);
10339                 ParseArgsFromFile(f);
10340                 fseek(f, 0, SEEK_SET);
10341                 FREE(appData.participants); appData.participants = participants;
10342                 if(expunge) { // erase results of replaced engine
10343                     int len = strlen(appData.results), w, b, dummy;
10344                     for(i=0; i<len; i++) {
10345                         Pairing(i, nPlayers, &w, &b, &dummy);
10346                         if((w == changed || b == changed) && appData.results[i] == '*') {
10347                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10348                             fclose(f);
10349                             return;
10350                         }
10351                     }
10352                     for(i=0; i<len; i++) {
10353                         Pairing(i, nPlayers, &w, &b, &dummy);
10354                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10355                     }
10356                 }
10357                 WriteTourneyFile(appData.results, f);
10358                 fclose(f); // release lock
10359                 return;
10360             }
10361         } else DisplayError(_("No engine with the name you gave is installed"), 0);
10362     }
10363     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10364     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
10365     free(participants);
10366     return;
10367 }
10368
10369 int
10370 CheckPlayers (char *participants)
10371 {
10372         int i;
10373         char buf[MSG_SIZ], *p;
10374         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10375         while(p = strchr(participants, '\n')) {
10376             *p = NULLCHAR;
10377             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10378             if(!mnemonic[i]) {
10379                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10380                 *p = '\n';
10381                 DisplayError(buf, 0);
10382                 return 1;
10383             }
10384             *p = '\n';
10385             participants = p + 1;
10386         }
10387         return 0;
10388 }
10389
10390 int
10391 CreateTourney (char *name)
10392 {
10393         FILE *f;
10394         if(matchMode && strcmp(name, appData.tourneyFile)) {
10395              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10396         }
10397         if(name[0] == NULLCHAR) {
10398             if(appData.participants[0])
10399                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10400             return 0;
10401         }
10402         f = fopen(name, "r");
10403         if(f) { // file exists
10404             ASSIGN(appData.tourneyFile, name);
10405             ParseArgsFromFile(f); // parse it
10406         } else {
10407             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10408             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10409                 DisplayError(_("Not enough participants"), 0);
10410                 return 0;
10411             }
10412             if(CheckPlayers(appData.participants)) return 0;
10413             ASSIGN(appData.tourneyFile, name);
10414             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10415             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10416         }
10417         fclose(f);
10418         appData.noChessProgram = FALSE;
10419         appData.clockMode = TRUE;
10420         SetGNUMode();
10421         return 1;
10422 }
10423
10424 int
10425 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10426 {
10427     char buf[MSG_SIZ], *p, *q;
10428     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10429     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10430     skip = !all && group[0]; // if group requested, we start in skip mode
10431     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10432         p = names; q = buf; header = 0;
10433         while(*p && *p != '\n') *q++ = *p++;
10434         *q = 0;
10435         if(*p == '\n') p++;
10436         if(buf[0] == '#') {
10437             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10438             depth++; // we must be entering a new group
10439             if(all) continue; // suppress printing group headers when complete list requested
10440             header = 1;
10441             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10442         }
10443         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10444         if(engineList[i]) free(engineList[i]);
10445         engineList[i] = strdup(buf);
10446         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10447         if(engineMnemonic[i]) free(engineMnemonic[i]);
10448         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10449             strcat(buf, " (");
10450             sscanf(q + 8, "%s", buf + strlen(buf));
10451             strcat(buf, ")");
10452         }
10453         engineMnemonic[i] = strdup(buf);
10454         i++;
10455     }
10456     engineList[i] = engineMnemonic[i] = NULL;
10457     return i;
10458 }
10459
10460 // following implemented as macro to avoid type limitations
10461 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10462
10463 void
10464 SwapEngines (int n)
10465 {   // swap settings for first engine and other engine (so far only some selected options)
10466     int h;
10467     char *p;
10468     if(n == 0) return;
10469     SWAP(directory, p)
10470     SWAP(chessProgram, p)
10471     SWAP(isUCI, h)
10472     SWAP(hasOwnBookUCI, h)
10473     SWAP(protocolVersion, h)
10474     SWAP(reuse, h)
10475     SWAP(scoreIsAbsolute, h)
10476     SWAP(timeOdds, h)
10477     SWAP(logo, p)
10478     SWAP(pgnName, p)
10479     SWAP(pvSAN, h)
10480     SWAP(engOptions, p)
10481     SWAP(engInitString, p)
10482     SWAP(computerString, p)
10483     SWAP(features, p)
10484     SWAP(fenOverride, p)
10485     SWAP(NPS, h)
10486     SWAP(accumulateTC, h)
10487     SWAP(host, p)
10488 }
10489
10490 int
10491 GetEngineLine (char *s, int n)
10492 {
10493     int i;
10494     char buf[MSG_SIZ];
10495     extern char *icsNames;
10496     if(!s || !*s) return 0;
10497     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
10498     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
10499     if(!mnemonic[i]) return 0;
10500     if(n == 11) return 1; // just testing if there was a match
10501     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
10502     if(n == 1) SwapEngines(n);
10503     ParseArgsFromString(buf);
10504     if(n == 1) SwapEngines(n);
10505     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
10506         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
10507         ParseArgsFromString(buf);
10508     }
10509     return 1;
10510 }
10511
10512 int
10513 SetPlayer (int player, char *p)
10514 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
10515     int i;
10516     char buf[MSG_SIZ], *engineName;
10517     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10518     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10519     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10520     if(mnemonic[i]) {
10521         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10522         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10523         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10524         ParseArgsFromString(buf);
10525     } else { // no engine with this nickname is installed!
10526         snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
10527         ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
10528         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10529         ModeHighlight();
10530         DisplayError(buf, 0);
10531         return 0;
10532     }
10533     free(engineName);
10534     return i;
10535 }
10536
10537 char *recentEngines;
10538
10539 void
10540 RecentEngineEvent (int nr)
10541 {
10542     int n;
10543 //    SwapEngines(1); // bump first to second
10544 //    ReplaceEngine(&second, 1); // and load it there
10545     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10546     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10547     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10548         ReplaceEngine(&first, 0);
10549         FloatToFront(&appData.recentEngineList, command[n]);
10550     }
10551 }
10552
10553 int
10554 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10555 {   // determine players from game number
10556     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10557
10558     if(appData.tourneyType == 0) {
10559         roundsPerCycle = (nPlayers - 1) | 1;
10560         pairingsPerRound = nPlayers / 2;
10561     } else if(appData.tourneyType > 0) {
10562         roundsPerCycle = nPlayers - appData.tourneyType;
10563         pairingsPerRound = appData.tourneyType;
10564     }
10565     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10566     gamesPerCycle = gamesPerRound * roundsPerCycle;
10567     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10568     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10569     curRound = nr / gamesPerRound; nr %= gamesPerRound;
10570     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10571     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10572     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10573
10574     if(appData.cycleSync) *syncInterval = gamesPerCycle;
10575     if(appData.roundSync) *syncInterval = gamesPerRound;
10576
10577     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10578
10579     if(appData.tourneyType == 0) {
10580         if(curPairing == (nPlayers-1)/2 ) {
10581             *whitePlayer = curRound;
10582             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10583         } else {
10584             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10585             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10586             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10587             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10588         }
10589     } else if(appData.tourneyType > 1) {
10590         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10591         *whitePlayer = curRound + appData.tourneyType;
10592     } else if(appData.tourneyType > 0) {
10593         *whitePlayer = curPairing;
10594         *blackPlayer = curRound + appData.tourneyType;
10595     }
10596
10597     // take care of white/black alternation per round.
10598     // For cycles and games this is already taken care of by default, derived from matchGame!
10599     return curRound & 1;
10600 }
10601
10602 int
10603 NextTourneyGame (int nr, int *swapColors)
10604 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10605     char *p, *q;
10606     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
10607     FILE *tf;
10608     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10609     tf = fopen(appData.tourneyFile, "r");
10610     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10611     ParseArgsFromFile(tf); fclose(tf);
10612     InitTimeControls(); // TC might be altered from tourney file
10613
10614     nPlayers = CountPlayers(appData.participants); // count participants
10615     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10616     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10617
10618     if(syncInterval) {
10619         p = q = appData.results;
10620         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10621         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10622             DisplayMessage(_("Waiting for other game(s)"),"");
10623             waitingForGame = TRUE;
10624             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10625             return 0;
10626         }
10627         waitingForGame = FALSE;
10628     }
10629
10630     if(appData.tourneyType < 0) {
10631         if(nr>=0 && !pairingReceived) {
10632             char buf[1<<16];
10633             if(pairing.pr == NoProc) {
10634                 if(!appData.pairingEngine[0]) {
10635                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10636                     return 0;
10637                 }
10638                 StartChessProgram(&pairing); // starts the pairing engine
10639             }
10640             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10641             SendToProgram(buf, &pairing);
10642             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10643             SendToProgram(buf, &pairing);
10644             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10645         }
10646         pairingReceived = 0;                              // ... so we continue here
10647         *swapColors = 0;
10648         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10649         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10650         matchGame = 1; roundNr = nr / syncInterval + 1;
10651     }
10652
10653     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10654
10655     // redefine engines, engine dir, etc.
10656     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10657     if(first.pr == NoProc) {
10658       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
10659       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10660     }
10661     if(second.pr == NoProc) {
10662       SwapEngines(1);
10663       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
10664       SwapEngines(1);         // and make that valid for second engine by swapping
10665       InitEngine(&second, 1);
10666     }
10667     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10668     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10669     return OK;
10670 }
10671
10672 void
10673 NextMatchGame ()
10674 {   // performs game initialization that does not invoke engines, and then tries to start the game
10675     int res, firstWhite, swapColors = 0;
10676     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10677     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
10678         char buf[MSG_SIZ];
10679         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
10680         if(strcmp(buf, currentDebugFile)) { // name has changed
10681             FILE *f = fopen(buf, "w");
10682             if(f) { // if opening the new file failed, just keep using the old one
10683                 ASSIGN(currentDebugFile, buf);
10684                 fclose(debugFP);
10685                 debugFP = f;
10686             }
10687             if(appData.serverFileName) {
10688                 if(serverFP) fclose(serverFP);
10689                 serverFP = fopen(appData.serverFileName, "w");
10690                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
10691                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
10692             }
10693         }
10694     }
10695     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10696     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10697     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
10698     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10699     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10700     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10701     Reset(FALSE, first.pr != NoProc);
10702     res = LoadGameOrPosition(matchGame); // setup game
10703     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10704     if(!res) return; // abort when bad game/pos file
10705     TwoMachinesEvent();
10706 }
10707
10708 void
10709 UserAdjudicationEvent (int result)
10710 {
10711     ChessMove gameResult = GameIsDrawn;
10712
10713     if( result > 0 ) {
10714         gameResult = WhiteWins;
10715     }
10716     else if( result < 0 ) {
10717         gameResult = BlackWins;
10718     }
10719
10720     if( gameMode == TwoMachinesPlay ) {
10721         GameEnds( gameResult, "User adjudication", GE_XBOARD );
10722     }
10723 }
10724
10725
10726 // [HGM] save: calculate checksum of game to make games easily identifiable
10727 int
10728 StringCheckSum (char *s)
10729 {
10730         int i = 0;
10731         if(s==NULL) return 0;
10732         while(*s) i = i*259 + *s++;
10733         return i;
10734 }
10735
10736 int
10737 GameCheckSum ()
10738 {
10739         int i, sum=0;
10740         for(i=backwardMostMove; i<forwardMostMove; i++) {
10741                 sum += pvInfoList[i].depth;
10742                 sum += StringCheckSum(parseList[i]);
10743                 sum += StringCheckSum(commentList[i]);
10744                 sum *= 261;
10745         }
10746         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10747         return sum + StringCheckSum(commentList[i]);
10748 } // end of save patch
10749
10750 void
10751 GameEnds (ChessMove result, char *resultDetails, int whosays)
10752 {
10753     GameMode nextGameMode;
10754     int isIcsGame;
10755     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10756
10757     if(endingGame) return; /* [HGM] crash: forbid recursion */
10758     endingGame = 1;
10759     if(twoBoards) { // [HGM] dual: switch back to one board
10760         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10761         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10762     }
10763     if (appData.debugMode) {
10764       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10765               result, resultDetails ? resultDetails : "(null)", whosays);
10766     }
10767
10768     fromX = fromY = -1; // [HGM] abort any move the user is entering.
10769
10770     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
10771
10772     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10773         /* If we are playing on ICS, the server decides when the
10774            game is over, but the engine can offer to draw, claim
10775            a draw, or resign.
10776          */
10777 #if ZIPPY
10778         if (appData.zippyPlay && first.initDone) {
10779             if (result == GameIsDrawn) {
10780                 /* In case draw still needs to be claimed */
10781                 SendToICS(ics_prefix);
10782                 SendToICS("draw\n");
10783             } else if (StrCaseStr(resultDetails, "resign")) {
10784                 SendToICS(ics_prefix);
10785                 SendToICS("resign\n");
10786             }
10787         }
10788 #endif
10789         endingGame = 0; /* [HGM] crash */
10790         return;
10791     }
10792
10793     /* If we're loading the game from a file, stop */
10794     if (whosays == GE_FILE) {
10795       (void) StopLoadGameTimer();
10796       gameFileFP = NULL;
10797     }
10798
10799     /* Cancel draw offers */
10800     first.offeredDraw = second.offeredDraw = 0;
10801
10802     /* If this is an ICS game, only ICS can really say it's done;
10803        if not, anyone can. */
10804     isIcsGame = (gameMode == IcsPlayingWhite ||
10805                  gameMode == IcsPlayingBlack ||
10806                  gameMode == IcsObserving    ||
10807                  gameMode == IcsExamining);
10808
10809     if (!isIcsGame || whosays == GE_ICS) {
10810         /* OK -- not an ICS game, or ICS said it was done */
10811         StopClocks();
10812         if (!isIcsGame && !appData.noChessProgram)
10813           SetUserThinkingEnables();
10814
10815         /* [HGM] if a machine claims the game end we verify this claim */
10816         if(gameMode == TwoMachinesPlay && appData.testClaims) {
10817             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10818                 char claimer;
10819                 ChessMove trueResult = (ChessMove) -1;
10820
10821                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
10822                                             first.twoMachinesColor[0] :
10823                                             second.twoMachinesColor[0] ;
10824
10825                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10826                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10827                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10828                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10829                 } else
10830                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10831                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10832                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10833                 } else
10834                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10835                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10836                 }
10837
10838                 // now verify win claims, but not in drop games, as we don't understand those yet
10839                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10840                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10841                     (result == WhiteWins && claimer == 'w' ||
10842                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
10843                       if (appData.debugMode) {
10844                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
10845                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10846                       }
10847                       if(result != trueResult) {
10848                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10849                               result = claimer == 'w' ? BlackWins : WhiteWins;
10850                               resultDetails = buf;
10851                       }
10852                 } else
10853                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10854                     && (forwardMostMove <= backwardMostMove ||
10855                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10856                         (claimer=='b')==(forwardMostMove&1))
10857                                                                                   ) {
10858                       /* [HGM] verify: draws that were not flagged are false claims */
10859                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10860                       result = claimer == 'w' ? BlackWins : WhiteWins;
10861                       resultDetails = buf;
10862                 }
10863                 /* (Claiming a loss is accepted no questions asked!) */
10864             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
10865                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
10866                 result = GameUnfinished;
10867                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
10868             }
10869             /* [HGM] bare: don't allow bare King to win */
10870             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10871                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10872                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10873                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10874                && result != GameIsDrawn)
10875             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10876                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10877                         int p = (signed char)boards[forwardMostMove][i][j] - color;
10878                         if(p >= 0 && p <= (int)WhiteKing) k++;
10879                 }
10880                 if (appData.debugMode) {
10881                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10882                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10883                 }
10884                 if(k <= 1) {
10885                         result = GameIsDrawn;
10886                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10887                         resultDetails = buf;
10888                 }
10889             }
10890         }
10891
10892
10893         if(serverMoves != NULL && !loadFlag) { char c = '=';
10894             if(result==WhiteWins) c = '+';
10895             if(result==BlackWins) c = '-';
10896             if(resultDetails != NULL)
10897                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
10898         }
10899         if (resultDetails != NULL) {
10900             gameInfo.result = result;
10901             gameInfo.resultDetails = StrSave(resultDetails);
10902
10903             /* display last move only if game was not loaded from file */
10904             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10905                 DisplayMove(currentMove - 1);
10906
10907             if (forwardMostMove != 0) {
10908                 if (gameMode != PlayFromGameFile && gameMode != EditGame
10909                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10910                                                                 ) {
10911                     if (*appData.saveGameFile != NULLCHAR) {
10912                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
10913                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
10914                         else
10915                         SaveGameToFile(appData.saveGameFile, TRUE);
10916                     } else if (appData.autoSaveGames) {
10917                         if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
10918                     }
10919                     if (*appData.savePositionFile != NULLCHAR) {
10920                         SavePositionToFile(appData.savePositionFile);
10921                     }
10922                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
10923                 }
10924             }
10925
10926             /* Tell program how game ended in case it is learning */
10927             /* [HGM] Moved this to after saving the PGN, just in case */
10928             /* engine died and we got here through time loss. In that */
10929             /* case we will get a fatal error writing the pipe, which */
10930             /* would otherwise lose us the PGN.                       */
10931             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
10932             /* output during GameEnds should never be fatal anymore   */
10933             if (gameMode == MachinePlaysWhite ||
10934                 gameMode == MachinePlaysBlack ||
10935                 gameMode == TwoMachinesPlay ||
10936                 gameMode == IcsPlayingWhite ||
10937                 gameMode == IcsPlayingBlack ||
10938                 gameMode == BeginningOfGame) {
10939                 char buf[MSG_SIZ];
10940                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10941                         resultDetails);
10942                 if (first.pr != NoProc) {
10943                     SendToProgram(buf, &first);
10944                 }
10945                 if (second.pr != NoProc &&
10946                     gameMode == TwoMachinesPlay) {
10947                     SendToProgram(buf, &second);
10948                 }
10949             }
10950         }
10951
10952         if (appData.icsActive) {
10953             if (appData.quietPlay &&
10954                 (gameMode == IcsPlayingWhite ||
10955                  gameMode == IcsPlayingBlack)) {
10956                 SendToICS(ics_prefix);
10957                 SendToICS("set shout 1\n");
10958             }
10959             nextGameMode = IcsIdle;
10960             ics_user_moved = FALSE;
10961             /* clean up premove.  It's ugly when the game has ended and the
10962              * premove highlights are still on the board.
10963              */
10964             if (gotPremove) {
10965               gotPremove = FALSE;
10966               ClearPremoveHighlights();
10967               DrawPosition(FALSE, boards[currentMove]);
10968             }
10969             if (whosays == GE_ICS) {
10970                 switch (result) {
10971                 case WhiteWins:
10972                     if (gameMode == IcsPlayingWhite)
10973                         PlayIcsWinSound();
10974                     else if(gameMode == IcsPlayingBlack)
10975                         PlayIcsLossSound();
10976                     break;
10977                 case BlackWins:
10978                     if (gameMode == IcsPlayingBlack)
10979                         PlayIcsWinSound();
10980                     else if(gameMode == IcsPlayingWhite)
10981                         PlayIcsLossSound();
10982                     break;
10983                 case GameIsDrawn:
10984                     PlayIcsDrawSound();
10985                     break;
10986                 default:
10987                     PlayIcsUnfinishedSound();
10988                 }
10989             }
10990             if(appData.quitNext) { ExitEvent(0); return; }
10991         } else if (gameMode == EditGame ||
10992                    gameMode == PlayFromGameFile ||
10993                    gameMode == AnalyzeMode ||
10994                    gameMode == AnalyzeFile) {
10995             nextGameMode = gameMode;
10996         } else {
10997             nextGameMode = EndOfGame;
10998         }
10999         pausing = FALSE;
11000         ModeHighlight();
11001     } else {
11002         nextGameMode = gameMode;
11003     }
11004
11005     if (appData.noChessProgram) {
11006         gameMode = nextGameMode;
11007         ModeHighlight();
11008         endingGame = 0; /* [HGM] crash */
11009         return;
11010     }
11011
11012     if (first.reuse) {
11013         /* Put first chess program into idle state */
11014         if (first.pr != NoProc &&
11015             (gameMode == MachinePlaysWhite ||
11016              gameMode == MachinePlaysBlack ||
11017              gameMode == TwoMachinesPlay ||
11018              gameMode == IcsPlayingWhite ||
11019              gameMode == IcsPlayingBlack ||
11020              gameMode == BeginningOfGame)) {
11021             SendToProgram("force\n", &first);
11022             if (first.usePing) {
11023               char buf[MSG_SIZ];
11024               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11025               SendToProgram(buf, &first);
11026             }
11027         }
11028     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11029         /* Kill off first chess program */
11030         if (first.isr != NULL)
11031           RemoveInputSource(first.isr);
11032         first.isr = NULL;
11033
11034         if (first.pr != NoProc) {
11035             ExitAnalyzeMode();
11036             DoSleep( appData.delayBeforeQuit );
11037             SendToProgram("quit\n", &first);
11038             DoSleep( appData.delayAfterQuit );
11039             DestroyChildProcess(first.pr, first.useSigterm);
11040             first.reload = TRUE;
11041         }
11042         first.pr = NoProc;
11043     }
11044     if (second.reuse) {
11045         /* Put second chess program into idle state */
11046         if (second.pr != NoProc &&
11047             gameMode == TwoMachinesPlay) {
11048             SendToProgram("force\n", &second);
11049             if (second.usePing) {
11050               char buf[MSG_SIZ];
11051               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11052               SendToProgram(buf, &second);
11053             }
11054         }
11055     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11056         /* Kill off second chess program */
11057         if (second.isr != NULL)
11058           RemoveInputSource(second.isr);
11059         second.isr = NULL;
11060
11061         if (second.pr != NoProc) {
11062             DoSleep( appData.delayBeforeQuit );
11063             SendToProgram("quit\n", &second);
11064             DoSleep( appData.delayAfterQuit );
11065             DestroyChildProcess(second.pr, second.useSigterm);
11066             second.reload = TRUE;
11067         }
11068         second.pr = NoProc;
11069     }
11070
11071     if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11072         char resChar = '=';
11073         switch (result) {
11074         case WhiteWins:
11075           resChar = '+';
11076           if (first.twoMachinesColor[0] == 'w') {
11077             first.matchWins++;
11078           } else {
11079             second.matchWins++;
11080           }
11081           break;
11082         case BlackWins:
11083           resChar = '-';
11084           if (first.twoMachinesColor[0] == 'b') {
11085             first.matchWins++;
11086           } else {
11087             second.matchWins++;
11088           }
11089           break;
11090         case GameUnfinished:
11091           resChar = ' ';
11092         default:
11093           break;
11094         }
11095
11096         if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11097         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11098             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11099             ReserveGame(nextGame, resChar); // sets nextGame
11100             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11101             else ranking = strdup("busy"); //suppress popup when aborted but not finished
11102         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11103
11104         if (nextGame <= appData.matchGames && !abortMatch) {
11105             gameMode = nextGameMode;
11106             matchGame = nextGame; // this will be overruled in tourney mode!
11107             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11108             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11109             endingGame = 0; /* [HGM] crash */
11110             return;
11111         } else {
11112             gameMode = nextGameMode;
11113             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11114                      first.tidy, second.tidy,
11115                      first.matchWins, second.matchWins,
11116                      appData.matchGames - (first.matchWins + second.matchWins));
11117             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11118             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11119             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11120             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11121                 first.twoMachinesColor = "black\n";
11122                 second.twoMachinesColor = "white\n";
11123             } else {
11124                 first.twoMachinesColor = "white\n";
11125                 second.twoMachinesColor = "black\n";
11126             }
11127         }
11128     }
11129     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11130         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11131       ExitAnalyzeMode();
11132     gameMode = nextGameMode;
11133     ModeHighlight();
11134     endingGame = 0;  /* [HGM] crash */
11135     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11136         if(matchMode == TRUE) { // match through command line: exit with or without popup
11137             if(ranking) {
11138                 ToNrEvent(forwardMostMove);
11139                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11140                 else ExitEvent(0);
11141             } else DisplayFatalError(buf, 0, 0);
11142         } else { // match through menu; just stop, with or without popup
11143             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11144             ModeHighlight();
11145             if(ranking){
11146                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11147             } else DisplayNote(buf);
11148       }
11149       if(ranking) free(ranking);
11150     }
11151 }
11152
11153 /* Assumes program was just initialized (initString sent).
11154    Leaves program in force mode. */
11155 void
11156 FeedMovesToProgram (ChessProgramState *cps, int upto)
11157 {
11158     int i;
11159
11160     if (appData.debugMode)
11161       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11162               startedFromSetupPosition ? "position and " : "",
11163               backwardMostMove, upto, cps->which);
11164     if(currentlyInitializedVariant != gameInfo.variant) {
11165       char buf[MSG_SIZ];
11166         // [HGM] variantswitch: make engine aware of new variant
11167         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
11168                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11169         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11170         SendToProgram(buf, cps);
11171         currentlyInitializedVariant = gameInfo.variant;
11172     }
11173     SendToProgram("force\n", cps);
11174     if (startedFromSetupPosition) {
11175         SendBoard(cps, backwardMostMove);
11176     if (appData.debugMode) {
11177         fprintf(debugFP, "feedMoves\n");
11178     }
11179     }
11180     for (i = backwardMostMove; i < upto; i++) {
11181         SendMoveToProgram(i, cps);
11182     }
11183 }
11184
11185
11186 int
11187 ResurrectChessProgram ()
11188 {
11189      /* The chess program may have exited.
11190         If so, restart it and feed it all the moves made so far. */
11191     static int doInit = 0;
11192
11193     if (appData.noChessProgram) return 1;
11194
11195     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11196         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11197         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11198         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11199     } else {
11200         if (first.pr != NoProc) return 1;
11201         StartChessProgram(&first);
11202     }
11203     InitChessProgram(&first, FALSE);
11204     FeedMovesToProgram(&first, currentMove);
11205
11206     if (!first.sendTime) {
11207         /* can't tell gnuchess what its clock should read,
11208            so we bow to its notion. */
11209         ResetClocks();
11210         timeRemaining[0][currentMove] = whiteTimeRemaining;
11211         timeRemaining[1][currentMove] = blackTimeRemaining;
11212     }
11213
11214     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11215                 appData.icsEngineAnalyze) && first.analysisSupport) {
11216       SendToProgram("analyze\n", &first);
11217       first.analyzing = TRUE;
11218     }
11219     return 1;
11220 }
11221
11222 /*
11223  * Button procedures
11224  */
11225 void
11226 Reset (int redraw, int init)
11227 {
11228     int i;
11229
11230     if (appData.debugMode) {
11231         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11232                 redraw, init, gameMode);
11233     }
11234     CleanupTail(); // [HGM] vari: delete any stored variations
11235     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11236     pausing = pauseExamInvalid = FALSE;
11237     startedFromSetupPosition = blackPlaysFirst = FALSE;
11238     firstMove = TRUE;
11239     whiteFlag = blackFlag = FALSE;
11240     userOfferedDraw = FALSE;
11241     hintRequested = bookRequested = FALSE;
11242     first.maybeThinking = FALSE;
11243     second.maybeThinking = FALSE;
11244     first.bookSuspend = FALSE; // [HGM] book
11245     second.bookSuspend = FALSE;
11246     thinkOutput[0] = NULLCHAR;
11247     lastHint[0] = NULLCHAR;
11248     ClearGameInfo(&gameInfo);
11249     gameInfo.variant = StringToVariant(appData.variant);
11250     if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) gameInfo.variant = VariantUnknown;
11251     ics_user_moved = ics_clock_paused = FALSE;
11252     ics_getting_history = H_FALSE;
11253     ics_gamenum = -1;
11254     white_holding[0] = black_holding[0] = NULLCHAR;
11255     ClearProgramStats();
11256     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11257
11258     ResetFrontEnd();
11259     ClearHighlights();
11260     flipView = appData.flipView;
11261     ClearPremoveHighlights();
11262     gotPremove = FALSE;
11263     alarmSounded = FALSE;
11264
11265     GameEnds(EndOfFile, NULL, GE_PLAYER);
11266     if(appData.serverMovesName != NULL) {
11267         /* [HGM] prepare to make moves file for broadcasting */
11268         clock_t t = clock();
11269         if(serverMoves != NULL) fclose(serverMoves);
11270         serverMoves = fopen(appData.serverMovesName, "r");
11271         if(serverMoves != NULL) {
11272             fclose(serverMoves);
11273             /* delay 15 sec before overwriting, so all clients can see end */
11274             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11275         }
11276         serverMoves = fopen(appData.serverMovesName, "w");
11277     }
11278
11279     ExitAnalyzeMode();
11280     gameMode = BeginningOfGame;
11281     ModeHighlight();
11282     if(appData.icsActive) gameInfo.variant = VariantNormal;
11283     currentMove = forwardMostMove = backwardMostMove = 0;
11284     MarkTargetSquares(1);
11285     InitPosition(redraw);
11286     for (i = 0; i < MAX_MOVES; i++) {
11287         if (commentList[i] != NULL) {
11288             free(commentList[i]);
11289             commentList[i] = NULL;
11290         }
11291     }
11292     ResetClocks();
11293     timeRemaining[0][0] = whiteTimeRemaining;
11294     timeRemaining[1][0] = blackTimeRemaining;
11295
11296     if (first.pr == NoProc) {
11297         StartChessProgram(&first);
11298     }
11299     if (init) {
11300             InitChessProgram(&first, startedFromSetupPosition);
11301     }
11302     DisplayTitle("");
11303     DisplayMessage("", "");
11304     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11305     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11306     ClearMap();        // [HGM] exclude: invalidate map
11307 }
11308
11309 void
11310 AutoPlayGameLoop ()
11311 {
11312     for (;;) {
11313         if (!AutoPlayOneMove())
11314           return;
11315         if (matchMode || appData.timeDelay == 0)
11316           continue;
11317         if (appData.timeDelay < 0)
11318           return;
11319         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11320         break;
11321     }
11322 }
11323
11324 void
11325 AnalyzeNextGame()
11326 {
11327     ReloadGame(1); // next game
11328 }
11329
11330 int
11331 AutoPlayOneMove ()
11332 {
11333     int fromX, fromY, toX, toY;
11334
11335     if (appData.debugMode) {
11336       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11337     }
11338
11339     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11340       return FALSE;
11341
11342     if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
11343       pvInfoList[currentMove].depth = programStats.depth;
11344       pvInfoList[currentMove].score = programStats.score;
11345       pvInfoList[currentMove].time  = 0;
11346       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11347       else { // append analysis of final position as comment
11348         char buf[MSG_SIZ];
11349         snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
11350         AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
11351       }
11352       programStats.depth = 0;
11353     }
11354
11355     if (currentMove >= forwardMostMove) {
11356       if(gameMode == AnalyzeFile) {
11357           if(appData.loadGameIndex == -1) {
11358             GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
11359           ScheduleDelayedEvent(AnalyzeNextGame, 10);
11360           } else {
11361           ExitAnalyzeMode(); SendToProgram("force\n", &first);
11362         }
11363       }
11364 //      gameMode = EndOfGame;
11365 //      ModeHighlight();
11366
11367       /* [AS] Clear current move marker at the end of a game */
11368       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11369
11370       return FALSE;
11371     }
11372
11373     toX = moveList[currentMove][2] - AAA;
11374     toY = moveList[currentMove][3] - ONE;
11375
11376     if (moveList[currentMove][1] == '@') {
11377         if (appData.highlightLastMove) {
11378             SetHighlights(-1, -1, toX, toY);
11379         }
11380     } else {
11381         fromX = moveList[currentMove][0] - AAA;
11382         fromY = moveList[currentMove][1] - ONE;
11383
11384         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11385
11386         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11387
11388         if (appData.highlightLastMove) {
11389             SetHighlights(fromX, fromY, toX, toY);
11390         }
11391     }
11392     DisplayMove(currentMove);
11393     SendMoveToProgram(currentMove++, &first);
11394     DisplayBothClocks();
11395     DrawPosition(FALSE, boards[currentMove]);
11396     // [HGM] PV info: always display, routine tests if empty
11397     DisplayComment(currentMove - 1, commentList[currentMove]);
11398     return TRUE;
11399 }
11400
11401
11402 int
11403 LoadGameOneMove (ChessMove readAhead)
11404 {
11405     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11406     char promoChar = NULLCHAR;
11407     ChessMove moveType;
11408     char move[MSG_SIZ];
11409     char *p, *q;
11410
11411     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11412         gameMode != AnalyzeMode && gameMode != Training) {
11413         gameFileFP = NULL;
11414         return FALSE;
11415     }
11416
11417     yyboardindex = forwardMostMove;
11418     if (readAhead != EndOfFile) {
11419       moveType = readAhead;
11420     } else {
11421       if (gameFileFP == NULL)
11422           return FALSE;
11423       moveType = (ChessMove) Myylex();
11424     }
11425
11426     done = FALSE;
11427     switch (moveType) {
11428       case Comment:
11429         if (appData.debugMode)
11430           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11431         p = yy_text;
11432
11433         /* append the comment but don't display it */
11434         AppendComment(currentMove, p, FALSE);
11435         return TRUE;
11436
11437       case WhiteCapturesEnPassant:
11438       case BlackCapturesEnPassant:
11439       case WhitePromotion:
11440       case BlackPromotion:
11441       case WhiteNonPromotion:
11442       case BlackNonPromotion:
11443       case NormalMove:
11444       case WhiteKingSideCastle:
11445       case WhiteQueenSideCastle:
11446       case BlackKingSideCastle:
11447       case BlackQueenSideCastle:
11448       case WhiteKingSideCastleWild:
11449       case WhiteQueenSideCastleWild:
11450       case BlackKingSideCastleWild:
11451       case BlackQueenSideCastleWild:
11452       /* PUSH Fabien */
11453       case WhiteHSideCastleFR:
11454       case WhiteASideCastleFR:
11455       case BlackHSideCastleFR:
11456       case BlackASideCastleFR:
11457       /* POP Fabien */
11458         if (appData.debugMode)
11459           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11460         fromX = currentMoveString[0] - AAA;
11461         fromY = currentMoveString[1] - ONE;
11462         toX = currentMoveString[2] - AAA;
11463         toY = currentMoveString[3] - ONE;
11464         promoChar = currentMoveString[4];
11465         break;
11466
11467       case WhiteDrop:
11468       case BlackDrop:
11469         if (appData.debugMode)
11470           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11471         fromX = moveType == WhiteDrop ?
11472           (int) CharToPiece(ToUpper(currentMoveString[0])) :
11473         (int) CharToPiece(ToLower(currentMoveString[0]));
11474         fromY = DROP_RANK;
11475         toX = currentMoveString[2] - AAA;
11476         toY = currentMoveString[3] - ONE;
11477         break;
11478
11479       case WhiteWins:
11480       case BlackWins:
11481       case GameIsDrawn:
11482       case GameUnfinished:
11483         if (appData.debugMode)
11484           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11485         p = strchr(yy_text, '{');
11486         if (p == NULL) p = strchr(yy_text, '(');
11487         if (p == NULL) {
11488             p = yy_text;
11489             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11490         } else {
11491             q = strchr(p, *p == '{' ? '}' : ')');
11492             if (q != NULL) *q = NULLCHAR;
11493             p++;
11494         }
11495         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11496         GameEnds(moveType, p, GE_FILE);
11497         done = TRUE;
11498         if (cmailMsgLoaded) {
11499             ClearHighlights();
11500             flipView = WhiteOnMove(currentMove);
11501             if (moveType == GameUnfinished) flipView = !flipView;
11502             if (appData.debugMode)
11503               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11504         }
11505         break;
11506
11507       case EndOfFile:
11508         if (appData.debugMode)
11509           fprintf(debugFP, "Parser hit end of file\n");
11510         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11511           case MT_NONE:
11512           case MT_CHECK:
11513             break;
11514           case MT_CHECKMATE:
11515           case MT_STAINMATE:
11516             if (WhiteOnMove(currentMove)) {
11517                 GameEnds(BlackWins, "Black mates", GE_FILE);
11518             } else {
11519                 GameEnds(WhiteWins, "White mates", GE_FILE);
11520             }
11521             break;
11522           case MT_STALEMATE:
11523             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11524             break;
11525         }
11526         done = TRUE;
11527         break;
11528
11529       case MoveNumberOne:
11530         if (lastLoadGameStart == GNUChessGame) {
11531             /* GNUChessGames have numbers, but they aren't move numbers */
11532             if (appData.debugMode)
11533               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11534                       yy_text, (int) moveType);
11535             return LoadGameOneMove(EndOfFile); /* tail recursion */
11536         }
11537         /* else fall thru */
11538
11539       case XBoardGame:
11540       case GNUChessGame:
11541       case PGNTag:
11542         /* Reached start of next game in file */
11543         if (appData.debugMode)
11544           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11545         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11546           case MT_NONE:
11547           case MT_CHECK:
11548             break;
11549           case MT_CHECKMATE:
11550           case MT_STAINMATE:
11551             if (WhiteOnMove(currentMove)) {
11552                 GameEnds(BlackWins, "Black mates", GE_FILE);
11553             } else {
11554                 GameEnds(WhiteWins, "White mates", GE_FILE);
11555             }
11556             break;
11557           case MT_STALEMATE:
11558             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11559             break;
11560         }
11561         done = TRUE;
11562         break;
11563
11564       case PositionDiagram:     /* should not happen; ignore */
11565       case ElapsedTime:         /* ignore */
11566       case NAG:                 /* ignore */
11567         if (appData.debugMode)
11568           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11569                   yy_text, (int) moveType);
11570         return LoadGameOneMove(EndOfFile); /* tail recursion */
11571
11572       case IllegalMove:
11573         if (appData.testLegality) {
11574             if (appData.debugMode)
11575               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11576             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11577                     (forwardMostMove / 2) + 1,
11578                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11579             DisplayError(move, 0);
11580             done = TRUE;
11581         } else {
11582             if (appData.debugMode)
11583               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11584                       yy_text, currentMoveString);
11585             fromX = currentMoveString[0] - AAA;
11586             fromY = currentMoveString[1] - ONE;
11587             toX = currentMoveString[2] - AAA;
11588             toY = currentMoveString[3] - ONE;
11589             promoChar = currentMoveString[4];
11590         }
11591         break;
11592
11593       case AmbiguousMove:
11594         if (appData.debugMode)
11595           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11596         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11597                 (forwardMostMove / 2) + 1,
11598                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11599         DisplayError(move, 0);
11600         done = TRUE;
11601         break;
11602
11603       default:
11604       case ImpossibleMove:
11605         if (appData.debugMode)
11606           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11607         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11608                 (forwardMostMove / 2) + 1,
11609                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11610         DisplayError(move, 0);
11611         done = TRUE;
11612         break;
11613     }
11614
11615     if (done) {
11616         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11617             DrawPosition(FALSE, boards[currentMove]);
11618             DisplayBothClocks();
11619             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11620               DisplayComment(currentMove - 1, commentList[currentMove]);
11621         }
11622         (void) StopLoadGameTimer();
11623         gameFileFP = NULL;
11624         cmailOldMove = forwardMostMove;
11625         return FALSE;
11626     } else {
11627         /* currentMoveString is set as a side-effect of yylex */
11628
11629         thinkOutput[0] = NULLCHAR;
11630         MakeMove(fromX, fromY, toX, toY, promoChar);
11631         currentMove = forwardMostMove;
11632         return TRUE;
11633     }
11634 }
11635
11636 /* Load the nth game from the given file */
11637 int
11638 LoadGameFromFile (char *filename, int n, char *title, int useList)
11639 {
11640     FILE *f;
11641     char buf[MSG_SIZ];
11642
11643     if (strcmp(filename, "-") == 0) {
11644         f = stdin;
11645         title = "stdin";
11646     } else {
11647         f = fopen(filename, "rb");
11648         if (f == NULL) {
11649           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
11650             DisplayError(buf, errno);
11651             return FALSE;
11652         }
11653     }
11654     if (fseek(f, 0, 0) == -1) {
11655         /* f is not seekable; probably a pipe */
11656         useList = FALSE;
11657     }
11658     if (useList && n == 0) {
11659         int error = GameListBuild(f);
11660         if (error) {
11661             DisplayError(_("Cannot build game list"), error);
11662         } else if (!ListEmpty(&gameList) &&
11663                    ((ListGame *) gameList.tailPred)->number > 1) {
11664             GameListPopUp(f, title);
11665             return TRUE;
11666         }
11667         GameListDestroy();
11668         n = 1;
11669     }
11670     if (n == 0) n = 1;
11671     return LoadGame(f, n, title, FALSE);
11672 }
11673
11674
11675 void
11676 MakeRegisteredMove ()
11677 {
11678     int fromX, fromY, toX, toY;
11679     char promoChar;
11680     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11681         switch (cmailMoveType[lastLoadGameNumber - 1]) {
11682           case CMAIL_MOVE:
11683           case CMAIL_DRAW:
11684             if (appData.debugMode)
11685               fprintf(debugFP, "Restoring %s for game %d\n",
11686                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11687
11688             thinkOutput[0] = NULLCHAR;
11689             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11690             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11691             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11692             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11693             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11694             promoChar = cmailMove[lastLoadGameNumber - 1][4];
11695             MakeMove(fromX, fromY, toX, toY, promoChar);
11696             ShowMove(fromX, fromY, toX, toY);
11697
11698             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11699               case MT_NONE:
11700               case MT_CHECK:
11701                 break;
11702
11703               case MT_CHECKMATE:
11704               case MT_STAINMATE:
11705                 if (WhiteOnMove(currentMove)) {
11706                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
11707                 } else {
11708                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
11709                 }
11710                 break;
11711
11712               case MT_STALEMATE:
11713                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11714                 break;
11715             }
11716
11717             break;
11718
11719           case CMAIL_RESIGN:
11720             if (WhiteOnMove(currentMove)) {
11721                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11722             } else {
11723                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11724             }
11725             break;
11726
11727           case CMAIL_ACCEPT:
11728             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11729             break;
11730
11731           default:
11732             break;
11733         }
11734     }
11735
11736     return;
11737 }
11738
11739 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11740 int
11741 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11742 {
11743     int retVal;
11744
11745     if (gameNumber > nCmailGames) {
11746         DisplayError(_("No more games in this message"), 0);
11747         return FALSE;
11748     }
11749     if (f == lastLoadGameFP) {
11750         int offset = gameNumber - lastLoadGameNumber;
11751         if (offset == 0) {
11752             cmailMsg[0] = NULLCHAR;
11753             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11754                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11755                 nCmailMovesRegistered--;
11756             }
11757             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11758             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11759                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11760             }
11761         } else {
11762             if (! RegisterMove()) return FALSE;
11763         }
11764     }
11765
11766     retVal = LoadGame(f, gameNumber, title, useList);
11767
11768     /* Make move registered during previous look at this game, if any */
11769     MakeRegisteredMove();
11770
11771     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11772         commentList[currentMove]
11773           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11774         DisplayComment(currentMove - 1, commentList[currentMove]);
11775     }
11776
11777     return retVal;
11778 }
11779
11780 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11781 int
11782 ReloadGame (int offset)
11783 {
11784     int gameNumber = lastLoadGameNumber + offset;
11785     if (lastLoadGameFP == NULL) {
11786         DisplayError(_("No game has been loaded yet"), 0);
11787         return FALSE;
11788     }
11789     if (gameNumber <= 0) {
11790         DisplayError(_("Can't back up any further"), 0);
11791         return FALSE;
11792     }
11793     if (cmailMsgLoaded) {
11794         return CmailLoadGame(lastLoadGameFP, gameNumber,
11795                              lastLoadGameTitle, lastLoadGameUseList);
11796     } else {
11797         return LoadGame(lastLoadGameFP, gameNumber,
11798                         lastLoadGameTitle, lastLoadGameUseList);
11799     }
11800 }
11801
11802 int keys[EmptySquare+1];
11803
11804 int
11805 PositionMatches (Board b1, Board b2)
11806 {
11807     int r, f, sum=0;
11808     switch(appData.searchMode) {
11809         case 1: return CompareWithRights(b1, b2);
11810         case 2:
11811             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11812                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11813             }
11814             return TRUE;
11815         case 3:
11816             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11817               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11818                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11819             }
11820             return sum==0;
11821         case 4:
11822             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11823                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11824             }
11825             return sum==0;
11826     }
11827     return TRUE;
11828 }
11829
11830 #define Q_PROMO  4
11831 #define Q_EP     3
11832 #define Q_BCASTL 2
11833 #define Q_WCASTL 1
11834
11835 int pieceList[256], quickBoard[256];
11836 ChessSquare pieceType[256] = { EmptySquare };
11837 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11838 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11839 int soughtTotal, turn;
11840 Boolean epOK, flipSearch;
11841
11842 typedef struct {
11843     unsigned char piece, to;
11844 } Move;
11845
11846 #define DSIZE (250000)
11847
11848 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11849 Move *moveDatabase = initialSpace;
11850 unsigned int movePtr, dataSize = DSIZE;
11851
11852 int
11853 MakePieceList (Board board, int *counts)
11854 {
11855     int r, f, n=Q_PROMO, total=0;
11856     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11857     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11858         int sq = f + (r<<4);
11859         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
11860             quickBoard[sq] = ++n;
11861             pieceList[n] = sq;
11862             pieceType[n] = board[r][f];
11863             counts[board[r][f]]++;
11864             if(board[r][f] == WhiteKing) pieceList[1] = n; else
11865             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
11866             total++;
11867         }
11868     }
11869     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
11870     return total;
11871 }
11872
11873 void
11874 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
11875 {
11876     int sq = fromX + (fromY<<4);
11877     int piece = quickBoard[sq];
11878     quickBoard[sq] = 0;
11879     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
11880     if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11881         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
11882         moveDatabase[movePtr++].piece = Q_WCASTL;
11883         quickBoard[sq] = piece;
11884         piece = quickBoard[from]; quickBoard[from] = 0;
11885         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11886     } else
11887     if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11888         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
11889         moveDatabase[movePtr++].piece = Q_BCASTL;
11890         quickBoard[sq] = piece;
11891         piece = quickBoard[from]; quickBoard[from] = 0;
11892         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11893     } else
11894     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
11895         quickBoard[(fromY<<4)+toX] = 0;
11896         moveDatabase[movePtr].piece = Q_EP;
11897         moveDatabase[movePtr++].to = (fromY<<4)+toX;
11898         moveDatabase[movePtr].to = sq;
11899     } else
11900     if(promoPiece != pieceType[piece]) {
11901         moveDatabase[movePtr++].piece = Q_PROMO;
11902         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
11903     }
11904     moveDatabase[movePtr].piece = piece;
11905     quickBoard[sq] = piece;
11906     movePtr++;
11907 }
11908
11909 int
11910 PackGame (Board board)
11911 {
11912     Move *newSpace = NULL;
11913     moveDatabase[movePtr].piece = 0; // terminate previous game
11914     if(movePtr > dataSize) {
11915         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
11916         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
11917         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
11918         if(newSpace) {
11919             int i;
11920             Move *p = moveDatabase, *q = newSpace;
11921             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
11922             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
11923             moveDatabase = newSpace;
11924         } else { // calloc failed, we must be out of memory. Too bad...
11925             dataSize = 0; // prevent calloc events for all subsequent games
11926             return 0;     // and signal this one isn't cached
11927         }
11928     }
11929     movePtr++;
11930     MakePieceList(board, counts);
11931     return movePtr;
11932 }
11933
11934 int
11935 QuickCompare (Board board, int *minCounts, int *maxCounts)
11936 {   // compare according to search mode
11937     int r, f;
11938     switch(appData.searchMode)
11939     {
11940       case 1: // exact position match
11941         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
11942         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11943             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11944         }
11945         break;
11946       case 2: // can have extra material on empty squares
11947         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11948             if(board[r][f] == EmptySquare) continue;
11949             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11950         }
11951         break;
11952       case 3: // material with exact Pawn structure
11953         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11954             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
11955             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11956         } // fall through to material comparison
11957       case 4: // exact material
11958         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
11959         break;
11960       case 6: // material range with given imbalance
11961         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
11962         // fall through to range comparison
11963       case 5: // material range
11964         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
11965     }
11966     return TRUE;
11967 }
11968
11969 int
11970 QuickScan (Board board, Move *move)
11971 {   // reconstruct game,and compare all positions in it
11972     int cnt=0, stretch=0, total = MakePieceList(board, counts);
11973     do {
11974         int piece = move->piece;
11975         int to = move->to, from = pieceList[piece];
11976         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
11977           if(!piece) return -1;
11978           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
11979             piece = (++move)->piece;
11980             from = pieceList[piece];
11981             counts[pieceType[piece]]--;
11982             pieceType[piece] = (ChessSquare) move->to;
11983             counts[move->to]++;
11984           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
11985             counts[pieceType[quickBoard[to]]]--;
11986             quickBoard[to] = 0; total--;
11987             move++;
11988             continue;
11989           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
11990             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
11991             from  = pieceList[piece]; // so this must be King
11992             quickBoard[from] = 0;
11993             pieceList[piece] = to;
11994             from = pieceList[(++move)->piece]; // for FRC this has to be done here
11995             quickBoard[from] = 0; // rook
11996             quickBoard[to] = piece;
11997             to = move->to; piece = move->piece;
11998             goto aftercastle;
11999           }
12000         }
12001         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12002         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
12003         quickBoard[from] = 0;
12004       aftercastle:
12005         quickBoard[to] = piece;
12006         pieceList[piece] = to;
12007         cnt++; turn ^= 3;
12008         if(QuickCompare(soughtBoard, minSought, maxSought) ||
12009            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12010            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12011                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12012           ) {
12013             static int lastCounts[EmptySquare+1];
12014             int i;
12015             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12016             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12017         } else stretch = 0;
12018         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
12019         move++;
12020     } while(1);
12021 }
12022
12023 void
12024 InitSearch ()
12025 {
12026     int r, f;
12027     flipSearch = FALSE;
12028     CopyBoard(soughtBoard, boards[currentMove]);
12029     soughtTotal = MakePieceList(soughtBoard, maxSought);
12030     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12031     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12032     CopyBoard(reverseBoard, boards[currentMove]);
12033     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12034         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12035         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12036         reverseBoard[r][f] = piece;
12037     }
12038     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12039     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12040     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12041                  || (boards[currentMove][CASTLING][2] == NoRights ||
12042                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12043                  && (boards[currentMove][CASTLING][5] == NoRights ||
12044                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12045       ) {
12046         flipSearch = TRUE;
12047         CopyBoard(flipBoard, soughtBoard);
12048         CopyBoard(rotateBoard, reverseBoard);
12049         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12050             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
12051             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12052         }
12053     }
12054     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12055     if(appData.searchMode >= 5) {
12056         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12057         MakePieceList(soughtBoard, minSought);
12058         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12059     }
12060     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12061         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12062 }
12063
12064 GameInfo dummyInfo;
12065 static int creatingBook;
12066
12067 int
12068 GameContainsPosition (FILE *f, ListGame *lg)
12069 {
12070     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12071     int fromX, fromY, toX, toY;
12072     char promoChar;
12073     static int initDone=FALSE;
12074
12075     // weed out games based on numerical tag comparison
12076     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12077     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12078     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12079     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12080     if(!initDone) {
12081         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12082         initDone = TRUE;
12083     }
12084     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
12085     else CopyBoard(boards[scratch], initialPosition); // default start position
12086     if(lg->moves) {
12087         turn = btm + 1;
12088         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12089         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12090     }
12091     if(btm) plyNr++;
12092     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12093     fseek(f, lg->offset, 0);
12094     yynewfile(f);
12095     while(1) {
12096         yyboardindex = scratch;
12097         quickFlag = plyNr+1;
12098         next = Myylex();
12099         quickFlag = 0;
12100         switch(next) {
12101             case PGNTag:
12102                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12103             default:
12104                 continue;
12105
12106             case XBoardGame:
12107             case GNUChessGame:
12108                 if(plyNr) return -1; // after we have seen moves, this is for new game
12109               continue;
12110
12111             case AmbiguousMove: // we cannot reconstruct the game beyond these two
12112             case ImpossibleMove:
12113             case WhiteWins: // game ends here with these four
12114             case BlackWins:
12115             case GameIsDrawn:
12116             case GameUnfinished:
12117                 return -1;
12118
12119             case IllegalMove:
12120                 if(appData.testLegality) return -1;
12121             case WhiteCapturesEnPassant:
12122             case BlackCapturesEnPassant:
12123             case WhitePromotion:
12124             case BlackPromotion:
12125             case WhiteNonPromotion:
12126             case BlackNonPromotion:
12127             case NormalMove:
12128             case WhiteKingSideCastle:
12129             case WhiteQueenSideCastle:
12130             case BlackKingSideCastle:
12131             case BlackQueenSideCastle:
12132             case WhiteKingSideCastleWild:
12133             case WhiteQueenSideCastleWild:
12134             case BlackKingSideCastleWild:
12135             case BlackQueenSideCastleWild:
12136             case WhiteHSideCastleFR:
12137             case WhiteASideCastleFR:
12138             case BlackHSideCastleFR:
12139             case BlackASideCastleFR:
12140                 fromX = currentMoveString[0] - AAA;
12141                 fromY = currentMoveString[1] - ONE;
12142                 toX = currentMoveString[2] - AAA;
12143                 toY = currentMoveString[3] - ONE;
12144                 promoChar = currentMoveString[4];
12145                 break;
12146             case WhiteDrop:
12147             case BlackDrop:
12148                 fromX = next == WhiteDrop ?
12149                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
12150                   (int) CharToPiece(ToLower(currentMoveString[0]));
12151                 fromY = DROP_RANK;
12152                 toX = currentMoveString[2] - AAA;
12153                 toY = currentMoveString[3] - ONE;
12154                 promoChar = 0;
12155                 break;
12156         }
12157         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12158         plyNr++;
12159         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12160         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12161         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12162         if(appData.findMirror) {
12163             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12164             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12165         }
12166     }
12167 }
12168
12169 /* Load the nth game from open file f */
12170 int
12171 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12172 {
12173     ChessMove cm;
12174     char buf[MSG_SIZ];
12175     int gn = gameNumber;
12176     ListGame *lg = NULL;
12177     int numPGNTags = 0;
12178     int err, pos = -1;
12179     GameMode oldGameMode;
12180     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12181
12182     if (appData.debugMode)
12183         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12184
12185     if (gameMode == Training )
12186         SetTrainingModeOff();
12187
12188     oldGameMode = gameMode;
12189     if (gameMode != BeginningOfGame) {
12190       Reset(FALSE, TRUE);
12191     }
12192
12193     gameFileFP = f;
12194     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12195         fclose(lastLoadGameFP);
12196     }
12197
12198     if (useList) {
12199         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12200
12201         if (lg) {
12202             fseek(f, lg->offset, 0);
12203             GameListHighlight(gameNumber);
12204             pos = lg->position;
12205             gn = 1;
12206         }
12207         else {
12208             if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
12209               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12210             else
12211             DisplayError(_("Game number out of range"), 0);
12212             return FALSE;
12213         }
12214     } else {
12215         GameListDestroy();
12216         if (fseek(f, 0, 0) == -1) {
12217             if (f == lastLoadGameFP ?
12218                 gameNumber == lastLoadGameNumber + 1 :
12219                 gameNumber == 1) {
12220                 gn = 1;
12221             } else {
12222                 DisplayError(_("Can't seek on game file"), 0);
12223                 return FALSE;
12224             }
12225         }
12226     }
12227     lastLoadGameFP = f;
12228     lastLoadGameNumber = gameNumber;
12229     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12230     lastLoadGameUseList = useList;
12231
12232     yynewfile(f);
12233
12234     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12235       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12236                 lg->gameInfo.black);
12237             DisplayTitle(buf);
12238     } else if (*title != NULLCHAR) {
12239         if (gameNumber > 1) {
12240           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12241             DisplayTitle(buf);
12242         } else {
12243             DisplayTitle(title);
12244         }
12245     }
12246
12247     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12248         gameMode = PlayFromGameFile;
12249         ModeHighlight();
12250     }
12251
12252     currentMove = forwardMostMove = backwardMostMove = 0;
12253     CopyBoard(boards[0], initialPosition);
12254     StopClocks();
12255
12256     /*
12257      * Skip the first gn-1 games in the file.
12258      * Also skip over anything that precedes an identifiable
12259      * start of game marker, to avoid being confused by
12260      * garbage at the start of the file.  Currently
12261      * recognized start of game markers are the move number "1",
12262      * the pattern "gnuchess .* game", the pattern
12263      * "^[#;%] [^ ]* game file", and a PGN tag block.
12264      * A game that starts with one of the latter two patterns
12265      * will also have a move number 1, possibly
12266      * following a position diagram.
12267      * 5-4-02: Let's try being more lenient and allowing a game to
12268      * start with an unnumbered move.  Does that break anything?
12269      */
12270     cm = lastLoadGameStart = EndOfFile;
12271     while (gn > 0) {
12272         yyboardindex = forwardMostMove;
12273         cm = (ChessMove) Myylex();
12274         switch (cm) {
12275           case EndOfFile:
12276             if (cmailMsgLoaded) {
12277                 nCmailGames = CMAIL_MAX_GAMES - gn;
12278             } else {
12279                 Reset(TRUE, TRUE);
12280                 DisplayError(_("Game not found in file"), 0);
12281             }
12282             return FALSE;
12283
12284           case GNUChessGame:
12285           case XBoardGame:
12286             gn--;
12287             lastLoadGameStart = cm;
12288             break;
12289
12290           case MoveNumberOne:
12291             switch (lastLoadGameStart) {
12292               case GNUChessGame:
12293               case XBoardGame:
12294               case PGNTag:
12295                 break;
12296               case MoveNumberOne:
12297               case EndOfFile:
12298                 gn--;           /* count this game */
12299                 lastLoadGameStart = cm;
12300                 break;
12301               default:
12302                 /* impossible */
12303                 break;
12304             }
12305             break;
12306
12307           case PGNTag:
12308             switch (lastLoadGameStart) {
12309               case GNUChessGame:
12310               case PGNTag:
12311               case MoveNumberOne:
12312               case EndOfFile:
12313                 gn--;           /* count this game */
12314                 lastLoadGameStart = cm;
12315                 break;
12316               case XBoardGame:
12317                 lastLoadGameStart = cm; /* game counted already */
12318                 break;
12319               default:
12320                 /* impossible */
12321                 break;
12322             }
12323             if (gn > 0) {
12324                 do {
12325                     yyboardindex = forwardMostMove;
12326                     cm = (ChessMove) Myylex();
12327                 } while (cm == PGNTag || cm == Comment);
12328             }
12329             break;
12330
12331           case WhiteWins:
12332           case BlackWins:
12333           case GameIsDrawn:
12334             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
12335                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
12336                     != CMAIL_OLD_RESULT) {
12337                     nCmailResults ++ ;
12338                     cmailResult[  CMAIL_MAX_GAMES
12339                                 - gn - 1] = CMAIL_OLD_RESULT;
12340                 }
12341             }
12342             break;
12343
12344           case NormalMove:
12345             /* Only a NormalMove can be at the start of a game
12346              * without a position diagram. */
12347             if (lastLoadGameStart == EndOfFile ) {
12348               gn--;
12349               lastLoadGameStart = MoveNumberOne;
12350             }
12351             break;
12352
12353           default:
12354             break;
12355         }
12356     }
12357
12358     if (appData.debugMode)
12359       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
12360
12361     if (cm == XBoardGame) {
12362         /* Skip any header junk before position diagram and/or move 1 */
12363         for (;;) {
12364             yyboardindex = forwardMostMove;
12365             cm = (ChessMove) Myylex();
12366
12367             if (cm == EndOfFile ||
12368                 cm == GNUChessGame || cm == XBoardGame) {
12369                 /* Empty game; pretend end-of-file and handle later */
12370                 cm = EndOfFile;
12371                 break;
12372             }
12373
12374             if (cm == MoveNumberOne || cm == PositionDiagram ||
12375                 cm == PGNTag || cm == Comment)
12376               break;
12377         }
12378     } else if (cm == GNUChessGame) {
12379         if (gameInfo.event != NULL) {
12380             free(gameInfo.event);
12381         }
12382         gameInfo.event = StrSave(yy_text);
12383     }
12384
12385     startedFromSetupPosition = FALSE;
12386     while (cm == PGNTag) {
12387         if (appData.debugMode)
12388           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
12389         err = ParsePGNTag(yy_text, &gameInfo);
12390         if (!err) numPGNTags++;
12391
12392         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
12393         if(gameInfo.variant != oldVariant) {
12394             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
12395             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
12396             InitPosition(TRUE);
12397             oldVariant = gameInfo.variant;
12398             if (appData.debugMode)
12399               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
12400         }
12401
12402
12403         if (gameInfo.fen != NULL) {
12404           Board initial_position;
12405           startedFromSetupPosition = TRUE;
12406           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
12407             Reset(TRUE, TRUE);
12408             DisplayError(_("Bad FEN position in file"), 0);
12409             return FALSE;
12410           }
12411           CopyBoard(boards[0], initial_position);
12412           if (blackPlaysFirst) {
12413             currentMove = forwardMostMove = backwardMostMove = 1;
12414             CopyBoard(boards[1], initial_position);
12415             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12416             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12417             timeRemaining[0][1] = whiteTimeRemaining;
12418             timeRemaining[1][1] = blackTimeRemaining;
12419             if (commentList[0] != NULL) {
12420               commentList[1] = commentList[0];
12421               commentList[0] = NULL;
12422             }
12423           } else {
12424             currentMove = forwardMostMove = backwardMostMove = 0;
12425           }
12426           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12427           {   int i;
12428               initialRulePlies = FENrulePlies;
12429               for( i=0; i< nrCastlingRights; i++ )
12430                   initialRights[i] = initial_position[CASTLING][i];
12431           }
12432           yyboardindex = forwardMostMove;
12433           free(gameInfo.fen);
12434           gameInfo.fen = NULL;
12435         }
12436
12437         yyboardindex = forwardMostMove;
12438         cm = (ChessMove) Myylex();
12439
12440         /* Handle comments interspersed among the tags */
12441         while (cm == Comment) {
12442             char *p;
12443             if (appData.debugMode)
12444               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12445             p = yy_text;
12446             AppendComment(currentMove, p, FALSE);
12447             yyboardindex = forwardMostMove;
12448             cm = (ChessMove) Myylex();
12449         }
12450     }
12451
12452     /* don't rely on existence of Event tag since if game was
12453      * pasted from clipboard the Event tag may not exist
12454      */
12455     if (numPGNTags > 0){
12456         char *tags;
12457         if (gameInfo.variant == VariantNormal) {
12458           VariantClass v = StringToVariant(gameInfo.event);
12459           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12460           if(v < VariantShogi) gameInfo.variant = v;
12461         }
12462         if (!matchMode) {
12463           if( appData.autoDisplayTags ) {
12464             tags = PGNTags(&gameInfo);
12465             TagsPopUp(tags, CmailMsg());
12466             free(tags);
12467           }
12468         }
12469     } else {
12470         /* Make something up, but don't display it now */
12471         SetGameInfo();
12472         TagsPopDown();
12473     }
12474
12475     if (cm == PositionDiagram) {
12476         int i, j;
12477         char *p;
12478         Board initial_position;
12479
12480         if (appData.debugMode)
12481           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12482
12483         if (!startedFromSetupPosition) {
12484             p = yy_text;
12485             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12486               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12487                 switch (*p) {
12488                   case '{':
12489                   case '[':
12490                   case '-':
12491                   case ' ':
12492                   case '\t':
12493                   case '\n':
12494                   case '\r':
12495                     break;
12496                   default:
12497                     initial_position[i][j++] = CharToPiece(*p);
12498                     break;
12499                 }
12500             while (*p == ' ' || *p == '\t' ||
12501                    *p == '\n' || *p == '\r') p++;
12502
12503             if (strncmp(p, "black", strlen("black"))==0)
12504               blackPlaysFirst = TRUE;
12505             else
12506               blackPlaysFirst = FALSE;
12507             startedFromSetupPosition = TRUE;
12508
12509             CopyBoard(boards[0], initial_position);
12510             if (blackPlaysFirst) {
12511                 currentMove = forwardMostMove = backwardMostMove = 1;
12512                 CopyBoard(boards[1], initial_position);
12513                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12514                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12515                 timeRemaining[0][1] = whiteTimeRemaining;
12516                 timeRemaining[1][1] = blackTimeRemaining;
12517                 if (commentList[0] != NULL) {
12518                     commentList[1] = commentList[0];
12519                     commentList[0] = NULL;
12520                 }
12521             } else {
12522                 currentMove = forwardMostMove = backwardMostMove = 0;
12523             }
12524         }
12525         yyboardindex = forwardMostMove;
12526         cm = (ChessMove) Myylex();
12527     }
12528
12529   if(!creatingBook) {
12530     if (first.pr == NoProc) {
12531         StartChessProgram(&first);
12532     }
12533     InitChessProgram(&first, FALSE);
12534     SendToProgram("force\n", &first);
12535     if (startedFromSetupPosition) {
12536         SendBoard(&first, forwardMostMove);
12537     if (appData.debugMode) {
12538         fprintf(debugFP, "Load Game\n");
12539     }
12540         DisplayBothClocks();
12541     }
12542   }
12543
12544     /* [HGM] server: flag to write setup moves in broadcast file as one */
12545     loadFlag = appData.suppressLoadMoves;
12546
12547     while (cm == Comment) {
12548         char *p;
12549         if (appData.debugMode)
12550           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12551         p = yy_text;
12552         AppendComment(currentMove, p, FALSE);
12553         yyboardindex = forwardMostMove;
12554         cm = (ChessMove) Myylex();
12555     }
12556
12557     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12558         cm == WhiteWins || cm == BlackWins ||
12559         cm == GameIsDrawn || cm == GameUnfinished) {
12560         DisplayMessage("", _("No moves in game"));
12561         if (cmailMsgLoaded) {
12562             if (appData.debugMode)
12563               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12564             ClearHighlights();
12565             flipView = FALSE;
12566         }
12567         DrawPosition(FALSE, boards[currentMove]);
12568         DisplayBothClocks();
12569         gameMode = EditGame;
12570         ModeHighlight();
12571         gameFileFP = NULL;
12572         cmailOldMove = 0;
12573         return TRUE;
12574     }
12575
12576     // [HGM] PV info: routine tests if comment empty
12577     if (!matchMode && (pausing || appData.timeDelay != 0)) {
12578         DisplayComment(currentMove - 1, commentList[currentMove]);
12579     }
12580     if (!matchMode && appData.timeDelay != 0)
12581       DrawPosition(FALSE, boards[currentMove]);
12582
12583     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12584       programStats.ok_to_send = 1;
12585     }
12586
12587     /* if the first token after the PGN tags is a move
12588      * and not move number 1, retrieve it from the parser
12589      */
12590     if (cm != MoveNumberOne)
12591         LoadGameOneMove(cm);
12592
12593     /* load the remaining moves from the file */
12594     while (LoadGameOneMove(EndOfFile)) {
12595       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12596       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12597     }
12598
12599     /* rewind to the start of the game */
12600     currentMove = backwardMostMove;
12601
12602     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12603
12604     if (oldGameMode == AnalyzeFile) {
12605       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
12606       AnalyzeFileEvent();
12607     } else
12608     if (oldGameMode == AnalyzeMode) {
12609       AnalyzeFileEvent();
12610     }
12611
12612     if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
12613         long int w, b; // [HGM] adjourn: restore saved clock times
12614         char *p = strstr(gameInfo.resultDetails, "(Clocks:");
12615         if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
12616             timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
12617             timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
12618         }
12619     }
12620
12621     if(creatingBook) return TRUE;
12622     if (!matchMode && pos > 0) {
12623         ToNrEvent(pos); // [HGM] no autoplay if selected on position
12624     } else
12625     if (matchMode || appData.timeDelay == 0) {
12626       ToEndEvent();
12627     } else if (appData.timeDelay > 0) {
12628       AutoPlayGameLoop();
12629     }
12630
12631     if (appData.debugMode)
12632         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
12633
12634     loadFlag = 0; /* [HGM] true game starts */
12635     return TRUE;
12636 }
12637
12638 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
12639 int
12640 ReloadPosition (int offset)
12641 {
12642     int positionNumber = lastLoadPositionNumber + offset;
12643     if (lastLoadPositionFP == NULL) {
12644         DisplayError(_("No position has been loaded yet"), 0);
12645         return FALSE;
12646     }
12647     if (positionNumber <= 0) {
12648         DisplayError(_("Can't back up any further"), 0);
12649         return FALSE;
12650     }
12651     return LoadPosition(lastLoadPositionFP, positionNumber,
12652                         lastLoadPositionTitle);
12653 }
12654
12655 /* Load the nth position from the given file */
12656 int
12657 LoadPositionFromFile (char *filename, int n, char *title)
12658 {
12659     FILE *f;
12660     char buf[MSG_SIZ];
12661
12662     if (strcmp(filename, "-") == 0) {
12663         return LoadPosition(stdin, n, "stdin");
12664     } else {
12665         f = fopen(filename, "rb");
12666         if (f == NULL) {
12667             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12668             DisplayError(buf, errno);
12669             return FALSE;
12670         } else {
12671             return LoadPosition(f, n, title);
12672         }
12673     }
12674 }
12675
12676 /* Load the nth position from the given open file, and close it */
12677 int
12678 LoadPosition (FILE *f, int positionNumber, char *title)
12679 {
12680     char *p, line[MSG_SIZ];
12681     Board initial_position;
12682     int i, j, fenMode, pn;
12683
12684     if (gameMode == Training )
12685         SetTrainingModeOff();
12686
12687     if (gameMode != BeginningOfGame) {
12688         Reset(FALSE, TRUE);
12689     }
12690     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12691         fclose(lastLoadPositionFP);
12692     }
12693     if (positionNumber == 0) positionNumber = 1;
12694     lastLoadPositionFP = f;
12695     lastLoadPositionNumber = positionNumber;
12696     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12697     if (first.pr == NoProc && !appData.noChessProgram) {
12698       StartChessProgram(&first);
12699       InitChessProgram(&first, FALSE);
12700     }
12701     pn = positionNumber;
12702     if (positionNumber < 0) {
12703         /* Negative position number means to seek to that byte offset */
12704         if (fseek(f, -positionNumber, 0) == -1) {
12705             DisplayError(_("Can't seek on position file"), 0);
12706             return FALSE;
12707         };
12708         pn = 1;
12709     } else {
12710         if (fseek(f, 0, 0) == -1) {
12711             if (f == lastLoadPositionFP ?
12712                 positionNumber == lastLoadPositionNumber + 1 :
12713                 positionNumber == 1) {
12714                 pn = 1;
12715             } else {
12716                 DisplayError(_("Can't seek on position file"), 0);
12717                 return FALSE;
12718             }
12719         }
12720     }
12721     /* See if this file is FEN or old-style xboard */
12722     if (fgets(line, MSG_SIZ, f) == NULL) {
12723         DisplayError(_("Position not found in file"), 0);
12724         return FALSE;
12725     }
12726     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12727     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12728
12729     if (pn >= 2) {
12730         if (fenMode || line[0] == '#') pn--;
12731         while (pn > 0) {
12732             /* skip positions before number pn */
12733             if (fgets(line, MSG_SIZ, f) == NULL) {
12734                 Reset(TRUE, TRUE);
12735                 DisplayError(_("Position not found in file"), 0);
12736                 return FALSE;
12737             }
12738             if (fenMode || line[0] == '#') pn--;
12739         }
12740     }
12741
12742     if (fenMode) {
12743         if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
12744             DisplayError(_("Bad FEN position in file"), 0);
12745             return FALSE;
12746         }
12747     } else {
12748         (void) fgets(line, MSG_SIZ, f);
12749         (void) fgets(line, MSG_SIZ, f);
12750
12751         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12752             (void) fgets(line, MSG_SIZ, f);
12753             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12754                 if (*p == ' ')
12755                   continue;
12756                 initial_position[i][j++] = CharToPiece(*p);
12757             }
12758         }
12759
12760         blackPlaysFirst = FALSE;
12761         if (!feof(f)) {
12762             (void) fgets(line, MSG_SIZ, f);
12763             if (strncmp(line, "black", strlen("black"))==0)
12764               blackPlaysFirst = TRUE;
12765         }
12766     }
12767     startedFromSetupPosition = TRUE;
12768
12769     CopyBoard(boards[0], initial_position);
12770     if (blackPlaysFirst) {
12771         currentMove = forwardMostMove = backwardMostMove = 1;
12772         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12773         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12774         CopyBoard(boards[1], initial_position);
12775         DisplayMessage("", _("Black to play"));
12776     } else {
12777         currentMove = forwardMostMove = backwardMostMove = 0;
12778         DisplayMessage("", _("White to play"));
12779     }
12780     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12781     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12782         SendToProgram("force\n", &first);
12783         SendBoard(&first, forwardMostMove);
12784     }
12785     if (appData.debugMode) {
12786 int i, j;
12787   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12788   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12789         fprintf(debugFP, "Load Position\n");
12790     }
12791
12792     if (positionNumber > 1) {
12793       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12794         DisplayTitle(line);
12795     } else {
12796         DisplayTitle(title);
12797     }
12798     gameMode = EditGame;
12799     ModeHighlight();
12800     ResetClocks();
12801     timeRemaining[0][1] = whiteTimeRemaining;
12802     timeRemaining[1][1] = blackTimeRemaining;
12803     DrawPosition(FALSE, boards[currentMove]);
12804
12805     return TRUE;
12806 }
12807
12808
12809 void
12810 CopyPlayerNameIntoFileName (char **dest, char *src)
12811 {
12812     while (*src != NULLCHAR && *src != ',') {
12813         if (*src == ' ') {
12814             *(*dest)++ = '_';
12815             src++;
12816         } else {
12817             *(*dest)++ = *src++;
12818         }
12819     }
12820 }
12821
12822 char *
12823 DefaultFileName (char *ext)
12824 {
12825     static char def[MSG_SIZ];
12826     char *p;
12827
12828     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12829         p = def;
12830         CopyPlayerNameIntoFileName(&p, gameInfo.white);
12831         *p++ = '-';
12832         CopyPlayerNameIntoFileName(&p, gameInfo.black);
12833         *p++ = '.';
12834         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12835     } else {
12836         def[0] = NULLCHAR;
12837     }
12838     return def;
12839 }
12840
12841 /* Save the current game to the given file */
12842 int
12843 SaveGameToFile (char *filename, int append)
12844 {
12845     FILE *f;
12846     char buf[MSG_SIZ];
12847     int result, i, t,tot=0;
12848
12849     if (strcmp(filename, "-") == 0) {
12850         return SaveGame(stdout, 0, NULL);
12851     } else {
12852         for(i=0; i<10; i++) { // upto 10 tries
12853              f = fopen(filename, append ? "a" : "w");
12854              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12855              if(f || errno != 13) break;
12856              DoSleep(t = 5 + random()%11); // wait 5-15 msec
12857              tot += t;
12858         }
12859         if (f == NULL) {
12860             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12861             DisplayError(buf, errno);
12862             return FALSE;
12863         } else {
12864             safeStrCpy(buf, lastMsg, MSG_SIZ);
12865             DisplayMessage(_("Waiting for access to save file"), "");
12866             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
12867             DisplayMessage(_("Saving game"), "");
12868             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
12869             result = SaveGame(f, 0, NULL);
12870             DisplayMessage(buf, "");
12871             return result;
12872         }
12873     }
12874 }
12875
12876 char *
12877 SavePart (char *str)
12878 {
12879     static char buf[MSG_SIZ];
12880     char *p;
12881
12882     p = strchr(str, ' ');
12883     if (p == NULL) return str;
12884     strncpy(buf, str, p - str);
12885     buf[p - str] = NULLCHAR;
12886     return buf;
12887 }
12888
12889 #define PGN_MAX_LINE 75
12890
12891 #define PGN_SIDE_WHITE  0
12892 #define PGN_SIDE_BLACK  1
12893
12894 static int
12895 FindFirstMoveOutOfBook (int side)
12896 {
12897     int result = -1;
12898
12899     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12900         int index = backwardMostMove;
12901         int has_book_hit = 0;
12902
12903         if( (index % 2) != side ) {
12904             index++;
12905         }
12906
12907         while( index < forwardMostMove ) {
12908             /* Check to see if engine is in book */
12909             int depth = pvInfoList[index].depth;
12910             int score = pvInfoList[index].score;
12911             int in_book = 0;
12912
12913             if( depth <= 2 ) {
12914                 in_book = 1;
12915             }
12916             else if( score == 0 && depth == 63 ) {
12917                 in_book = 1; /* Zappa */
12918             }
12919             else if( score == 2 && depth == 99 ) {
12920                 in_book = 1; /* Abrok */
12921             }
12922
12923             has_book_hit += in_book;
12924
12925             if( ! in_book ) {
12926                 result = index;
12927
12928                 break;
12929             }
12930
12931             index += 2;
12932         }
12933     }
12934
12935     return result;
12936 }
12937
12938 void
12939 GetOutOfBookInfo (char * buf)
12940 {
12941     int oob[2];
12942     int i;
12943     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12944
12945     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12946     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12947
12948     *buf = '\0';
12949
12950     if( oob[0] >= 0 || oob[1] >= 0 ) {
12951         for( i=0; i<2; i++ ) {
12952             int idx = oob[i];
12953
12954             if( idx >= 0 ) {
12955                 if( i > 0 && oob[0] >= 0 ) {
12956                     strcat( buf, "   " );
12957                 }
12958
12959                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12960                 sprintf( buf+strlen(buf), "%s%.2f",
12961                     pvInfoList[idx].score >= 0 ? "+" : "",
12962                     pvInfoList[idx].score / 100.0 );
12963             }
12964         }
12965     }
12966 }
12967
12968 /* Save game in PGN style and close the file */
12969 int
12970 SaveGamePGN (FILE *f)
12971 {
12972     int i, offset, linelen, newblock;
12973 //    char *movetext;
12974     char numtext[32];
12975     int movelen, numlen, blank;
12976     char move_buffer[100]; /* [AS] Buffer for move+PV info */
12977
12978     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12979
12980     PrintPGNTags(f, &gameInfo);
12981
12982     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
12983
12984     if (backwardMostMove > 0 || startedFromSetupPosition) {
12985         char *fen = PositionToFEN(backwardMostMove, NULL, 1);
12986         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12987         fprintf(f, "\n{--------------\n");
12988         PrintPosition(f, backwardMostMove);
12989         fprintf(f, "--------------}\n");
12990         free(fen);
12991     }
12992     else {
12993         /* [AS] Out of book annotation */
12994         if( appData.saveOutOfBookInfo ) {
12995             char buf[64];
12996
12997             GetOutOfBookInfo( buf );
12998
12999             if( buf[0] != '\0' ) {
13000                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
13001             }
13002         }
13003
13004         fprintf(f, "\n");
13005     }
13006
13007     i = backwardMostMove;
13008     linelen = 0;
13009     newblock = TRUE;
13010
13011     while (i < forwardMostMove) {
13012         /* Print comments preceding this move */
13013         if (commentList[i] != NULL) {
13014             if (linelen > 0) fprintf(f, "\n");
13015             fprintf(f, "%s", commentList[i]);
13016             linelen = 0;
13017             newblock = TRUE;
13018         }
13019
13020         /* Format move number */
13021         if ((i % 2) == 0)
13022           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13023         else
13024           if (newblock)
13025             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13026           else
13027             numtext[0] = NULLCHAR;
13028
13029         numlen = strlen(numtext);
13030         newblock = FALSE;
13031
13032         /* Print move number */
13033         blank = linelen > 0 && numlen > 0;
13034         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13035             fprintf(f, "\n");
13036             linelen = 0;
13037             blank = 0;
13038         }
13039         if (blank) {
13040             fprintf(f, " ");
13041             linelen++;
13042         }
13043         fprintf(f, "%s", numtext);
13044         linelen += numlen;
13045
13046         /* Get move */
13047         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13048         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13049
13050         /* Print move */
13051         blank = linelen > 0 && movelen > 0;
13052         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13053             fprintf(f, "\n");
13054             linelen = 0;
13055             blank = 0;
13056         }
13057         if (blank) {
13058             fprintf(f, " ");
13059             linelen++;
13060         }
13061         fprintf(f, "%s", move_buffer);
13062         linelen += movelen;
13063
13064         /* [AS] Add PV info if present */
13065         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13066             /* [HGM] add time */
13067             char buf[MSG_SIZ]; int seconds;
13068
13069             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13070
13071             if( seconds <= 0)
13072               buf[0] = 0;
13073             else
13074               if( seconds < 30 )
13075                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13076               else
13077                 {
13078                   seconds = (seconds + 4)/10; // round to full seconds
13079                   if( seconds < 60 )
13080                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
13081                   else
13082                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
13083                 }
13084
13085             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
13086                       pvInfoList[i].score >= 0 ? "+" : "",
13087                       pvInfoList[i].score / 100.0,
13088                       pvInfoList[i].depth,
13089                       buf );
13090
13091             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
13092
13093             /* Print score/depth */
13094             blank = linelen > 0 && movelen > 0;
13095             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13096                 fprintf(f, "\n");
13097                 linelen = 0;
13098                 blank = 0;
13099             }
13100             if (blank) {
13101                 fprintf(f, " ");
13102                 linelen++;
13103             }
13104             fprintf(f, "%s", move_buffer);
13105             linelen += movelen;
13106         }
13107
13108         i++;
13109     }
13110
13111     /* Start a new line */
13112     if (linelen > 0) fprintf(f, "\n");
13113
13114     /* Print comments after last move */
13115     if (commentList[i] != NULL) {
13116         fprintf(f, "%s\n", commentList[i]);
13117     }
13118
13119     /* Print result */
13120     if (gameInfo.resultDetails != NULL &&
13121         gameInfo.resultDetails[0] != NULLCHAR) {
13122         char buf[MSG_SIZ], *p = gameInfo.resultDetails;
13123         if(gameInfo.result == GameUnfinished && appData.clockMode &&
13124            (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
13125             snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
13126         fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
13127     } else {
13128         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13129     }
13130
13131     fclose(f);
13132     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13133     return TRUE;
13134 }
13135
13136 /* Save game in old style and close the file */
13137 int
13138 SaveGameOldStyle (FILE *f)
13139 {
13140     int i, offset;
13141     time_t tm;
13142
13143     tm = time((time_t *) NULL);
13144
13145     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
13146     PrintOpponents(f);
13147
13148     if (backwardMostMove > 0 || startedFromSetupPosition) {
13149         fprintf(f, "\n[--------------\n");
13150         PrintPosition(f, backwardMostMove);
13151         fprintf(f, "--------------]\n");
13152     } else {
13153         fprintf(f, "\n");
13154     }
13155
13156     i = backwardMostMove;
13157     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13158
13159     while (i < forwardMostMove) {
13160         if (commentList[i] != NULL) {
13161             fprintf(f, "[%s]\n", commentList[i]);
13162         }
13163
13164         if ((i % 2) == 1) {
13165             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
13166             i++;
13167         } else {
13168             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
13169             i++;
13170             if (commentList[i] != NULL) {
13171                 fprintf(f, "\n");
13172                 continue;
13173             }
13174             if (i >= forwardMostMove) {
13175                 fprintf(f, "\n");
13176                 break;
13177             }
13178             fprintf(f, "%s\n", parseList[i]);
13179             i++;
13180         }
13181     }
13182
13183     if (commentList[i] != NULL) {
13184         fprintf(f, "[%s]\n", commentList[i]);
13185     }
13186
13187     /* This isn't really the old style, but it's close enough */
13188     if (gameInfo.resultDetails != NULL &&
13189         gameInfo.resultDetails[0] != NULLCHAR) {
13190         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13191                 gameInfo.resultDetails);
13192     } else {
13193         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13194     }
13195
13196     fclose(f);
13197     return TRUE;
13198 }
13199
13200 /* Save the current game to open file f and close the file */
13201 int
13202 SaveGame (FILE *f, int dummy, char *dummy2)
13203 {
13204     if (gameMode == EditPosition) EditPositionDone(TRUE);
13205     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13206     if (appData.oldSaveStyle)
13207       return SaveGameOldStyle(f);
13208     else
13209       return SaveGamePGN(f);
13210 }
13211
13212 /* Save the current position to the given file */
13213 int
13214 SavePositionToFile (char *filename)
13215 {
13216     FILE *f;
13217     char buf[MSG_SIZ];
13218
13219     if (strcmp(filename, "-") == 0) {
13220         return SavePosition(stdout, 0, NULL);
13221     } else {
13222         f = fopen(filename, "a");
13223         if (f == NULL) {
13224             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13225             DisplayError(buf, errno);
13226             return FALSE;
13227         } else {
13228             safeStrCpy(buf, lastMsg, MSG_SIZ);
13229             DisplayMessage(_("Waiting for access to save file"), "");
13230             flock(fileno(f), LOCK_EX); // [HGM] lock
13231             DisplayMessage(_("Saving position"), "");
13232             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
13233             SavePosition(f, 0, NULL);
13234             DisplayMessage(buf, "");
13235             return TRUE;
13236         }
13237     }
13238 }
13239
13240 /* Save the current position to the given open file and close the file */
13241 int
13242 SavePosition (FILE *f, int dummy, char *dummy2)
13243 {
13244     time_t tm;
13245     char *fen;
13246
13247     if (gameMode == EditPosition) EditPositionDone(TRUE);
13248     if (appData.oldSaveStyle) {
13249         tm = time((time_t *) NULL);
13250
13251         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
13252         PrintOpponents(f);
13253         fprintf(f, "[--------------\n");
13254         PrintPosition(f, currentMove);
13255         fprintf(f, "--------------]\n");
13256     } else {
13257         fen = PositionToFEN(currentMove, NULL, 1);
13258         fprintf(f, "%s\n", fen);
13259         free(fen);
13260     }
13261     fclose(f);
13262     return TRUE;
13263 }
13264
13265 void
13266 ReloadCmailMsgEvent (int unregister)
13267 {
13268 #if !WIN32
13269     static char *inFilename = NULL;
13270     static char *outFilename;
13271     int i;
13272     struct stat inbuf, outbuf;
13273     int status;
13274
13275     /* Any registered moves are unregistered if unregister is set, */
13276     /* i.e. invoked by the signal handler */
13277     if (unregister) {
13278         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13279             cmailMoveRegistered[i] = FALSE;
13280             if (cmailCommentList[i] != NULL) {
13281                 free(cmailCommentList[i]);
13282                 cmailCommentList[i] = NULL;
13283             }
13284         }
13285         nCmailMovesRegistered = 0;
13286     }
13287
13288     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13289         cmailResult[i] = CMAIL_NOT_RESULT;
13290     }
13291     nCmailResults = 0;
13292
13293     if (inFilename == NULL) {
13294         /* Because the filenames are static they only get malloced once  */
13295         /* and they never get freed                                      */
13296         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
13297         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
13298
13299         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
13300         sprintf(outFilename, "%s.out", appData.cmailGameName);
13301     }
13302
13303     status = stat(outFilename, &outbuf);
13304     if (status < 0) {
13305         cmailMailedMove = FALSE;
13306     } else {
13307         status = stat(inFilename, &inbuf);
13308         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
13309     }
13310
13311     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
13312        counts the games, notes how each one terminated, etc.
13313
13314        It would be nice to remove this kludge and instead gather all
13315        the information while building the game list.  (And to keep it
13316        in the game list nodes instead of having a bunch of fixed-size
13317        parallel arrays.)  Note this will require getting each game's
13318        termination from the PGN tags, as the game list builder does
13319        not process the game moves.  --mann
13320        */
13321     cmailMsgLoaded = TRUE;
13322     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
13323
13324     /* Load first game in the file or popup game menu */
13325     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
13326
13327 #endif /* !WIN32 */
13328     return;
13329 }
13330
13331 int
13332 RegisterMove ()
13333 {
13334     FILE *f;
13335     char string[MSG_SIZ];
13336
13337     if (   cmailMailedMove
13338         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
13339         return TRUE;            /* Allow free viewing  */
13340     }
13341
13342     /* Unregister move to ensure that we don't leave RegisterMove        */
13343     /* with the move registered when the conditions for registering no   */
13344     /* longer hold                                                       */
13345     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
13346         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
13347         nCmailMovesRegistered --;
13348
13349         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
13350           {
13351               free(cmailCommentList[lastLoadGameNumber - 1]);
13352               cmailCommentList[lastLoadGameNumber - 1] = NULL;
13353           }
13354     }
13355
13356     if (cmailOldMove == -1) {
13357         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
13358         return FALSE;
13359     }
13360
13361     if (currentMove > cmailOldMove + 1) {
13362         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
13363         return FALSE;
13364     }
13365
13366     if (currentMove < cmailOldMove) {
13367         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
13368         return FALSE;
13369     }
13370
13371     if (forwardMostMove > currentMove) {
13372         /* Silently truncate extra moves */
13373         TruncateGame();
13374     }
13375
13376     if (   (currentMove == cmailOldMove + 1)
13377         || (   (currentMove == cmailOldMove)
13378             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
13379                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
13380         if (gameInfo.result != GameUnfinished) {
13381             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
13382         }
13383
13384         if (commentList[currentMove] != NULL) {
13385             cmailCommentList[lastLoadGameNumber - 1]
13386               = StrSave(commentList[currentMove]);
13387         }
13388         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
13389
13390         if (appData.debugMode)
13391           fprintf(debugFP, "Saving %s for game %d\n",
13392                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
13393
13394         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
13395
13396         f = fopen(string, "w");
13397         if (appData.oldSaveStyle) {
13398             SaveGameOldStyle(f); /* also closes the file */
13399
13400             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
13401             f = fopen(string, "w");
13402             SavePosition(f, 0, NULL); /* also closes the file */
13403         } else {
13404             fprintf(f, "{--------------\n");
13405             PrintPosition(f, currentMove);
13406             fprintf(f, "--------------}\n\n");
13407
13408             SaveGame(f, 0, NULL); /* also closes the file*/
13409         }
13410
13411         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
13412         nCmailMovesRegistered ++;
13413     } else if (nCmailGames == 1) {
13414         DisplayError(_("You have not made a move yet"), 0);
13415         return FALSE;
13416     }
13417
13418     return TRUE;
13419 }
13420
13421 void
13422 MailMoveEvent ()
13423 {
13424 #if !WIN32
13425     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
13426     FILE *commandOutput;
13427     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
13428     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
13429     int nBuffers;
13430     int i;
13431     int archived;
13432     char *arcDir;
13433
13434     if (! cmailMsgLoaded) {
13435         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
13436         return;
13437     }
13438
13439     if (nCmailGames == nCmailResults) {
13440         DisplayError(_("No unfinished games"), 0);
13441         return;
13442     }
13443
13444 #if CMAIL_PROHIBIT_REMAIL
13445     if (cmailMailedMove) {
13446       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);
13447         DisplayError(msg, 0);
13448         return;
13449     }
13450 #endif
13451
13452     if (! (cmailMailedMove || RegisterMove())) return;
13453
13454     if (   cmailMailedMove
13455         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13456       snprintf(string, MSG_SIZ, partCommandString,
13457                appData.debugMode ? " -v" : "", appData.cmailGameName);
13458         commandOutput = popen(string, "r");
13459
13460         if (commandOutput == NULL) {
13461             DisplayError(_("Failed to invoke cmail"), 0);
13462         } else {
13463             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13464                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13465             }
13466             if (nBuffers > 1) {
13467                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13468                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13469                 nBytes = MSG_SIZ - 1;
13470             } else {
13471                 (void) memcpy(msg, buffer, nBytes);
13472             }
13473             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13474
13475             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13476                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
13477
13478                 archived = TRUE;
13479                 for (i = 0; i < nCmailGames; i ++) {
13480                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
13481                         archived = FALSE;
13482                     }
13483                 }
13484                 if (   archived
13485                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13486                         != NULL)) {
13487                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13488                            arcDir,
13489                            appData.cmailGameName,
13490                            gameInfo.date);
13491                     LoadGameFromFile(buffer, 1, buffer, FALSE);
13492                     cmailMsgLoaded = FALSE;
13493                 }
13494             }
13495
13496             DisplayInformation(msg);
13497             pclose(commandOutput);
13498         }
13499     } else {
13500         if ((*cmailMsg) != '\0') {
13501             DisplayInformation(cmailMsg);
13502         }
13503     }
13504
13505     return;
13506 #endif /* !WIN32 */
13507 }
13508
13509 char *
13510 CmailMsg ()
13511 {
13512 #if WIN32
13513     return NULL;
13514 #else
13515     int  prependComma = 0;
13516     char number[5];
13517     char string[MSG_SIZ];       /* Space for game-list */
13518     int  i;
13519
13520     if (!cmailMsgLoaded) return "";
13521
13522     if (cmailMailedMove) {
13523       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13524     } else {
13525         /* Create a list of games left */
13526       snprintf(string, MSG_SIZ, "[");
13527         for (i = 0; i < nCmailGames; i ++) {
13528             if (! (   cmailMoveRegistered[i]
13529                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13530                 if (prependComma) {
13531                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13532                 } else {
13533                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13534                     prependComma = 1;
13535                 }
13536
13537                 strcat(string, number);
13538             }
13539         }
13540         strcat(string, "]");
13541
13542         if (nCmailMovesRegistered + nCmailResults == 0) {
13543             switch (nCmailGames) {
13544               case 1:
13545                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13546                 break;
13547
13548               case 2:
13549                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13550                 break;
13551
13552               default:
13553                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13554                          nCmailGames);
13555                 break;
13556             }
13557         } else {
13558             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13559               case 1:
13560                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13561                          string);
13562                 break;
13563
13564               case 0:
13565                 if (nCmailResults == nCmailGames) {
13566                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13567                 } else {
13568                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13569                 }
13570                 break;
13571
13572               default:
13573                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13574                          string);
13575             }
13576         }
13577     }
13578     return cmailMsg;
13579 #endif /* WIN32 */
13580 }
13581
13582 void
13583 ResetGameEvent ()
13584 {
13585     if (gameMode == Training)
13586       SetTrainingModeOff();
13587
13588     Reset(TRUE, TRUE);
13589     cmailMsgLoaded = FALSE;
13590     if (appData.icsActive) {
13591       SendToICS(ics_prefix);
13592       SendToICS("refresh\n");
13593     }
13594 }
13595
13596 void
13597 ExitEvent (int status)
13598 {
13599     exiting++;
13600     if (exiting > 2) {
13601       /* Give up on clean exit */
13602       exit(status);
13603     }
13604     if (exiting > 1) {
13605       /* Keep trying for clean exit */
13606       return;
13607     }
13608
13609     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
13610
13611     if (telnetISR != NULL) {
13612       RemoveInputSource(telnetISR);
13613     }
13614     if (icsPR != NoProc) {
13615       DestroyChildProcess(icsPR, TRUE);
13616     }
13617
13618     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
13619     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
13620
13621     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
13622     /* make sure this other one finishes before killing it!                  */
13623     if(endingGame) { int count = 0;
13624         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
13625         while(endingGame && count++ < 10) DoSleep(1);
13626         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
13627     }
13628
13629     /* Kill off chess programs */
13630     if (first.pr != NoProc) {
13631         ExitAnalyzeMode();
13632
13633         DoSleep( appData.delayBeforeQuit );
13634         SendToProgram("quit\n", &first);
13635         DoSleep( appData.delayAfterQuit );
13636         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
13637     }
13638     if (second.pr != NoProc) {
13639         DoSleep( appData.delayBeforeQuit );
13640         SendToProgram("quit\n", &second);
13641         DoSleep( appData.delayAfterQuit );
13642         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
13643     }
13644     if (first.isr != NULL) {
13645         RemoveInputSource(first.isr);
13646     }
13647     if (second.isr != NULL) {
13648         RemoveInputSource(second.isr);
13649     }
13650
13651     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13652     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13653
13654     ShutDownFrontEnd();
13655     exit(status);
13656 }
13657
13658 void
13659 PauseEngine (ChessProgramState *cps)
13660 {
13661     SendToProgram("pause\n", cps);
13662     cps->pause = 2;
13663 }
13664
13665 void
13666 UnPauseEngine (ChessProgramState *cps)
13667 {
13668     SendToProgram("resume\n", cps);
13669     cps->pause = 1;
13670 }
13671
13672 void
13673 PauseEvent ()
13674 {
13675     if (appData.debugMode)
13676         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13677     if (pausing) {
13678         pausing = FALSE;
13679         ModeHighlight();
13680         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
13681             StartClocks();
13682             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
13683                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
13684                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
13685             }
13686             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
13687             HandleMachineMove(stashedInputMove, stalledEngine);
13688             stalledEngine = NULL;
13689             return;
13690         }
13691         if (gameMode == MachinePlaysWhite ||
13692             gameMode == TwoMachinesPlay   ||
13693             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
13694             if(first.pause)  UnPauseEngine(&first);
13695             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
13696             if(second.pause) UnPauseEngine(&second);
13697             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
13698             StartClocks();
13699         } else {
13700             DisplayBothClocks();
13701         }
13702         if (gameMode == PlayFromGameFile) {
13703             if (appData.timeDelay >= 0)
13704                 AutoPlayGameLoop();
13705         } else if (gameMode == IcsExamining && pauseExamInvalid) {
13706             Reset(FALSE, TRUE);
13707             SendToICS(ics_prefix);
13708             SendToICS("refresh\n");
13709         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
13710             ForwardInner(forwardMostMove);
13711         }
13712         pauseExamInvalid = FALSE;
13713     } else {
13714         switch (gameMode) {
13715           default:
13716             return;
13717           case IcsExamining:
13718             pauseExamForwardMostMove = forwardMostMove;
13719             pauseExamInvalid = FALSE;
13720             /* fall through */
13721           case IcsObserving:
13722           case IcsPlayingWhite:
13723           case IcsPlayingBlack:
13724             pausing = TRUE;
13725             ModeHighlight();
13726             return;
13727           case PlayFromGameFile:
13728             (void) StopLoadGameTimer();
13729             pausing = TRUE;
13730             ModeHighlight();
13731             break;
13732           case BeginningOfGame:
13733             if (appData.icsActive) return;
13734             /* else fall through */
13735           case MachinePlaysWhite:
13736           case MachinePlaysBlack:
13737           case TwoMachinesPlay:
13738             if (forwardMostMove == 0)
13739               return;           /* don't pause if no one has moved */
13740             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
13741                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
13742                 if(onMove->pause) {           // thinking engine can be paused
13743                     PauseEngine(onMove);      // do it
13744                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
13745                         PauseEngine(onMove->other);
13746                     else
13747                         SendToProgram("easy\n", onMove->other);
13748                     StopClocks();
13749                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
13750             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
13751                 if(first.pause) {
13752                     PauseEngine(&first);
13753                     StopClocks();
13754                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
13755             } else { // human on move, pause pondering by either method
13756                 if(first.pause)
13757                     PauseEngine(&first);
13758                 else if(appData.ponderNextMove)
13759                     SendToProgram("easy\n", &first);
13760                 StopClocks();
13761             }
13762             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
13763           case AnalyzeMode:
13764             pausing = TRUE;
13765             ModeHighlight();
13766             break;
13767         }
13768     }
13769 }
13770
13771 void
13772 EditCommentEvent ()
13773 {
13774     char title[MSG_SIZ];
13775
13776     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13777       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13778     } else {
13779       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13780                WhiteOnMove(currentMove - 1) ? " " : ".. ",
13781                parseList[currentMove - 1]);
13782     }
13783
13784     EditCommentPopUp(currentMove, title, commentList[currentMove]);
13785 }
13786
13787
13788 void
13789 EditTagsEvent ()
13790 {
13791     char *tags = PGNTags(&gameInfo);
13792     bookUp = FALSE;
13793     EditTagsPopUp(tags, NULL);
13794     free(tags);
13795 }
13796
13797 void
13798 ToggleSecond ()
13799 {
13800   if(second.analyzing) {
13801     SendToProgram("exit\n", &second);
13802     second.analyzing = FALSE;
13803   } else {
13804     if (second.pr == NoProc) StartChessProgram(&second);
13805     InitChessProgram(&second, FALSE);
13806     FeedMovesToProgram(&second, currentMove);
13807
13808     SendToProgram("analyze\n", &second);
13809     second.analyzing = TRUE;
13810   }
13811 }
13812
13813 /* Toggle ShowThinking */
13814 void
13815 ToggleShowThinking()
13816 {
13817   appData.showThinking = !appData.showThinking;
13818   ShowThinkingEvent();
13819 }
13820
13821 int
13822 AnalyzeModeEvent ()
13823 {
13824     char buf[MSG_SIZ];
13825
13826     if (!first.analysisSupport) {
13827       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
13828       DisplayError(buf, 0);
13829       return 0;
13830     }
13831     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
13832     if (appData.icsActive) {
13833         if (gameMode != IcsObserving) {
13834           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
13835             DisplayError(buf, 0);
13836             /* secure check */
13837             if (appData.icsEngineAnalyze) {
13838                 if (appData.debugMode)
13839                     fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
13840                 ExitAnalyzeMode();
13841                 ModeHighlight();
13842             }
13843             return 0;
13844         }
13845         /* if enable, user wants to disable icsEngineAnalyze */
13846         if (appData.icsEngineAnalyze) {
13847                 ExitAnalyzeMode();
13848                 ModeHighlight();
13849                 return 0;
13850         }
13851         appData.icsEngineAnalyze = TRUE;
13852         if (appData.debugMode)
13853             fprintf(debugFP, "ICS engine analyze starting... \n");
13854     }
13855
13856     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
13857     if (appData.noChessProgram || gameMode == AnalyzeMode)
13858       return 0;
13859
13860     if (gameMode != AnalyzeFile) {
13861         if (!appData.icsEngineAnalyze) {
13862                EditGameEvent();
13863                if (gameMode != EditGame) return 0;
13864         }
13865         if (!appData.showThinking) ToggleShowThinking();
13866         ResurrectChessProgram();
13867         SendToProgram("analyze\n", &first);
13868         first.analyzing = TRUE;
13869         /*first.maybeThinking = TRUE;*/
13870         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13871         EngineOutputPopUp();
13872     }
13873     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
13874     pausing = FALSE;
13875     ModeHighlight();
13876     SetGameInfo();
13877
13878     StartAnalysisClock();
13879     GetTimeMark(&lastNodeCountTime);
13880     lastNodeCount = 0;
13881     return 1;
13882 }
13883
13884 void
13885 AnalyzeFileEvent ()
13886 {
13887     if (appData.noChessProgram || gameMode == AnalyzeFile)
13888       return;
13889
13890     if (!first.analysisSupport) {
13891       char buf[MSG_SIZ];
13892       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
13893       DisplayError(buf, 0);
13894       return;
13895     }
13896
13897     if (gameMode != AnalyzeMode) {
13898         keepInfo = 1; // mere annotating should not alter PGN tags
13899         EditGameEvent();
13900         keepInfo = 0;
13901         if (gameMode != EditGame) return;
13902         if (!appData.showThinking) ToggleShowThinking();
13903         ResurrectChessProgram();
13904         SendToProgram("analyze\n", &first);
13905         first.analyzing = TRUE;
13906         /*first.maybeThinking = TRUE;*/
13907         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13908         EngineOutputPopUp();
13909     }
13910     gameMode = AnalyzeFile;
13911     pausing = FALSE;
13912     ModeHighlight();
13913
13914     StartAnalysisClock();
13915     GetTimeMark(&lastNodeCountTime);
13916     lastNodeCount = 0;
13917     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
13918     AnalysisPeriodicEvent(1);
13919 }
13920
13921 void
13922 MachineWhiteEvent ()
13923 {
13924     char buf[MSG_SIZ];
13925     char *bookHit = NULL;
13926
13927     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
13928       return;
13929
13930
13931     if (gameMode == PlayFromGameFile ||
13932         gameMode == TwoMachinesPlay  ||
13933         gameMode == Training         ||
13934         gameMode == AnalyzeMode      ||
13935         gameMode == EndOfGame)
13936         EditGameEvent();
13937
13938     if (gameMode == EditPosition)
13939         EditPositionDone(TRUE);
13940
13941     if (!WhiteOnMove(currentMove)) {
13942         DisplayError(_("It is not White's turn"), 0);
13943         return;
13944     }
13945
13946     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13947       ExitAnalyzeMode();
13948
13949     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13950         gameMode == AnalyzeFile)
13951         TruncateGame();
13952
13953     ResurrectChessProgram();    /* in case it isn't running */
13954     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
13955         gameMode = MachinePlaysWhite;
13956         ResetClocks();
13957     } else
13958     gameMode = MachinePlaysWhite;
13959     pausing = FALSE;
13960     ModeHighlight();
13961     SetGameInfo();
13962     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13963     DisplayTitle(buf);
13964     if (first.sendName) {
13965       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
13966       SendToProgram(buf, &first);
13967     }
13968     if (first.sendTime) {
13969       if (first.useColors) {
13970         SendToProgram("black\n", &first); /*gnu kludge*/
13971       }
13972       SendTimeRemaining(&first, TRUE);
13973     }
13974     if (first.useColors) {
13975       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
13976     }
13977     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13978     SetMachineThinkingEnables();
13979     first.maybeThinking = TRUE;
13980     StartClocks();
13981     firstMove = FALSE;
13982
13983     if (appData.autoFlipView && !flipView) {
13984       flipView = !flipView;
13985       DrawPosition(FALSE, NULL);
13986       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13987     }
13988
13989     if(bookHit) { // [HGM] book: simulate book reply
13990         static char bookMove[MSG_SIZ]; // a bit generous?
13991
13992         programStats.nodes = programStats.depth = programStats.time =
13993         programStats.score = programStats.got_only_move = 0;
13994         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13995
13996         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13997         strcat(bookMove, bookHit);
13998         HandleMachineMove(bookMove, &first);
13999     }
14000 }
14001
14002 void
14003 MachineBlackEvent ()
14004 {
14005   char buf[MSG_SIZ];
14006   char *bookHit = NULL;
14007
14008     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
14009         return;
14010
14011
14012     if (gameMode == PlayFromGameFile ||
14013         gameMode == TwoMachinesPlay  ||
14014         gameMode == Training         ||
14015         gameMode == AnalyzeMode      ||
14016         gameMode == EndOfGame)
14017         EditGameEvent();
14018
14019     if (gameMode == EditPosition)
14020         EditPositionDone(TRUE);
14021
14022     if (WhiteOnMove(currentMove)) {
14023         DisplayError(_("It is not Black's turn"), 0);
14024         return;
14025     }
14026
14027     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14028       ExitAnalyzeMode();
14029
14030     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14031         gameMode == AnalyzeFile)
14032         TruncateGame();
14033
14034     ResurrectChessProgram();    /* in case it isn't running */
14035     gameMode = MachinePlaysBlack;
14036     pausing = FALSE;
14037     ModeHighlight();
14038     SetGameInfo();
14039     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14040     DisplayTitle(buf);
14041     if (first.sendName) {
14042       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14043       SendToProgram(buf, &first);
14044     }
14045     if (first.sendTime) {
14046       if (first.useColors) {
14047         SendToProgram("white\n", &first); /*gnu kludge*/
14048       }
14049       SendTimeRemaining(&first, FALSE);
14050     }
14051     if (first.useColors) {
14052       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14053     }
14054     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14055     SetMachineThinkingEnables();
14056     first.maybeThinking = TRUE;
14057     StartClocks();
14058
14059     if (appData.autoFlipView && flipView) {
14060       flipView = !flipView;
14061       DrawPosition(FALSE, NULL);
14062       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14063     }
14064     if(bookHit) { // [HGM] book: simulate book reply
14065         static char bookMove[MSG_SIZ]; // a bit generous?
14066
14067         programStats.nodes = programStats.depth = programStats.time =
14068         programStats.score = programStats.got_only_move = 0;
14069         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14070
14071         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14072         strcat(bookMove, bookHit);
14073         HandleMachineMove(bookMove, &first);
14074     }
14075 }
14076
14077
14078 void
14079 DisplayTwoMachinesTitle ()
14080 {
14081     char buf[MSG_SIZ];
14082     if (appData.matchGames > 0) {
14083         if(appData.tourneyFile[0]) {
14084           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14085                    gameInfo.white, _("vs."), gameInfo.black,
14086                    nextGame+1, appData.matchGames+1,
14087                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14088         } else
14089         if (first.twoMachinesColor[0] == 'w') {
14090           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14091                    gameInfo.white, _("vs."),  gameInfo.black,
14092                    first.matchWins, second.matchWins,
14093                    matchGame - 1 - (first.matchWins + second.matchWins));
14094         } else {
14095           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14096                    gameInfo.white, _("vs."), gameInfo.black,
14097                    second.matchWins, first.matchWins,
14098                    matchGame - 1 - (first.matchWins + second.matchWins));
14099         }
14100     } else {
14101       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14102     }
14103     DisplayTitle(buf);
14104 }
14105
14106 void
14107 SettingsMenuIfReady ()
14108 {
14109   if (second.lastPing != second.lastPong) {
14110     DisplayMessage("", _("Waiting for second chess program"));
14111     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14112     return;
14113   }
14114   ThawUI();
14115   DisplayMessage("", "");
14116   SettingsPopUp(&second);
14117 }
14118
14119 int
14120 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
14121 {
14122     char buf[MSG_SIZ];
14123     if (cps->pr == NoProc) {
14124         StartChessProgram(cps);
14125         if (cps->protocolVersion == 1) {
14126           retry();
14127           ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
14128         } else {
14129           /* kludge: allow timeout for initial "feature" command */
14130           if(retry != TwoMachinesEventIfReady) FreezeUI();
14131           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
14132           DisplayMessage("", buf);
14133           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
14134         }
14135         return 1;
14136     }
14137     return 0;
14138 }
14139
14140 void
14141 TwoMachinesEvent P((void))
14142 {
14143     int i;
14144     char buf[MSG_SIZ];
14145     ChessProgramState *onmove;
14146     char *bookHit = NULL;
14147     static int stalling = 0;
14148     TimeMark now;
14149     long wait;
14150
14151     if (appData.noChessProgram) return;
14152
14153     switch (gameMode) {
14154       case TwoMachinesPlay:
14155         return;
14156       case MachinePlaysWhite:
14157       case MachinePlaysBlack:
14158         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14159             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
14160             return;
14161         }
14162         /* fall through */
14163       case BeginningOfGame:
14164       case PlayFromGameFile:
14165       case EndOfGame:
14166         EditGameEvent();
14167         if (gameMode != EditGame) return;
14168         break;
14169       case EditPosition:
14170         EditPositionDone(TRUE);
14171         break;
14172       case AnalyzeMode:
14173       case AnalyzeFile:
14174         ExitAnalyzeMode();
14175         break;
14176       case EditGame:
14177       default:
14178         break;
14179     }
14180
14181 //    forwardMostMove = currentMove;
14182     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
14183     startingEngine = TRUE;
14184
14185     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
14186
14187     if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14188     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14189       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14190       return;
14191     }
14192     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14193
14194     if(second.protocolVersion >= 2 && !strstr(second.variants, VariantName(gameInfo.variant))) {
14195         startingEngine = FALSE;
14196         DisplayError("second engine does not play this", 0);
14197         return;
14198     }
14199
14200     if(!stalling) {
14201       InitChessProgram(&second, FALSE); // unbalances ping of second engine
14202       SendToProgram("force\n", &second);
14203       stalling = 1;
14204       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14205       return;
14206     }
14207     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14208     if(appData.matchPause>10000 || appData.matchPause<10)
14209                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14210     wait = SubtractTimeMarks(&now, &pauseStart);
14211     if(wait < appData.matchPause) {
14212         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14213         return;
14214     }
14215     // we are now committed to starting the game
14216     stalling = 0;
14217     DisplayMessage("", "");
14218     if (startedFromSetupPosition) {
14219         SendBoard(&second, backwardMostMove);
14220     if (appData.debugMode) {
14221         fprintf(debugFP, "Two Machines\n");
14222     }
14223     }
14224     for (i = backwardMostMove; i < forwardMostMove; i++) {
14225         SendMoveToProgram(i, &second);
14226     }
14227
14228     gameMode = TwoMachinesPlay;
14229     pausing = startingEngine = FALSE;
14230     ModeHighlight(); // [HGM] logo: this triggers display update of logos
14231     SetGameInfo();
14232     DisplayTwoMachinesTitle();
14233     firstMove = TRUE;
14234     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14235         onmove = &first;
14236     } else {
14237         onmove = &second;
14238     }
14239     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14240     SendToProgram(first.computerString, &first);
14241     if (first.sendName) {
14242       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14243       SendToProgram(buf, &first);
14244     }
14245     SendToProgram(second.computerString, &second);
14246     if (second.sendName) {
14247       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
14248       SendToProgram(buf, &second);
14249     }
14250
14251     ResetClocks();
14252     if (!first.sendTime || !second.sendTime) {
14253         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14254         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14255     }
14256     if (onmove->sendTime) {
14257       if (onmove->useColors) {
14258         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
14259       }
14260       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
14261     }
14262     if (onmove->useColors) {
14263       SendToProgram(onmove->twoMachinesColor, onmove);
14264     }
14265     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
14266 //    SendToProgram("go\n", onmove);
14267     onmove->maybeThinking = TRUE;
14268     SetMachineThinkingEnables();
14269
14270     StartClocks();
14271
14272     if(bookHit) { // [HGM] book: simulate book reply
14273         static char bookMove[MSG_SIZ]; // a bit generous?
14274
14275         programStats.nodes = programStats.depth = programStats.time =
14276         programStats.score = programStats.got_only_move = 0;
14277         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14278
14279         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14280         strcat(bookMove, bookHit);
14281         savedMessage = bookMove; // args for deferred call
14282         savedState = onmove;
14283         ScheduleDelayedEvent(DeferredBookMove, 1);
14284     }
14285 }
14286
14287 void
14288 TrainingEvent ()
14289 {
14290     if (gameMode == Training) {
14291       SetTrainingModeOff();
14292       gameMode = PlayFromGameFile;
14293       DisplayMessage("", _("Training mode off"));
14294     } else {
14295       gameMode = Training;
14296       animateTraining = appData.animate;
14297
14298       /* make sure we are not already at the end of the game */
14299       if (currentMove < forwardMostMove) {
14300         SetTrainingModeOn();
14301         DisplayMessage("", _("Training mode on"));
14302       } else {
14303         gameMode = PlayFromGameFile;
14304         DisplayError(_("Already at end of game"), 0);
14305       }
14306     }
14307     ModeHighlight();
14308 }
14309
14310 void
14311 IcsClientEvent ()
14312 {
14313     if (!appData.icsActive) return;
14314     switch (gameMode) {
14315       case IcsPlayingWhite:
14316       case IcsPlayingBlack:
14317       case IcsObserving:
14318       case IcsIdle:
14319       case BeginningOfGame:
14320       case IcsExamining:
14321         return;
14322
14323       case EditGame:
14324         break;
14325
14326       case EditPosition:
14327         EditPositionDone(TRUE);
14328         break;
14329
14330       case AnalyzeMode:
14331       case AnalyzeFile:
14332         ExitAnalyzeMode();
14333         break;
14334
14335       default:
14336         EditGameEvent();
14337         break;
14338     }
14339
14340     gameMode = IcsIdle;
14341     ModeHighlight();
14342     return;
14343 }
14344
14345 void
14346 EditGameEvent ()
14347 {
14348     int i;
14349
14350     switch (gameMode) {
14351       case Training:
14352         SetTrainingModeOff();
14353         break;
14354       case MachinePlaysWhite:
14355       case MachinePlaysBlack:
14356       case BeginningOfGame:
14357         SendToProgram("force\n", &first);
14358         SetUserThinkingEnables();
14359         break;
14360       case PlayFromGameFile:
14361         (void) StopLoadGameTimer();
14362         if (gameFileFP != NULL) {
14363             gameFileFP = NULL;
14364         }
14365         break;
14366       case EditPosition:
14367         EditPositionDone(TRUE);
14368         break;
14369       case AnalyzeMode:
14370       case AnalyzeFile:
14371         ExitAnalyzeMode();
14372         SendToProgram("force\n", &first);
14373         break;
14374       case TwoMachinesPlay:
14375         GameEnds(EndOfFile, NULL, GE_PLAYER);
14376         ResurrectChessProgram();
14377         SetUserThinkingEnables();
14378         break;
14379       case EndOfGame:
14380         ResurrectChessProgram();
14381         break;
14382       case IcsPlayingBlack:
14383       case IcsPlayingWhite:
14384         DisplayError(_("Warning: You are still playing a game"), 0);
14385         break;
14386       case IcsObserving:
14387         DisplayError(_("Warning: You are still observing a game"), 0);
14388         break;
14389       case IcsExamining:
14390         DisplayError(_("Warning: You are still examining a game"), 0);
14391         break;
14392       case IcsIdle:
14393         break;
14394       case EditGame:
14395       default:
14396         return;
14397     }
14398
14399     pausing = FALSE;
14400     StopClocks();
14401     first.offeredDraw = second.offeredDraw = 0;
14402
14403     if (gameMode == PlayFromGameFile) {
14404         whiteTimeRemaining = timeRemaining[0][currentMove];
14405         blackTimeRemaining = timeRemaining[1][currentMove];
14406         DisplayTitle("");
14407     }
14408
14409     if (gameMode == MachinePlaysWhite ||
14410         gameMode == MachinePlaysBlack ||
14411         gameMode == TwoMachinesPlay ||
14412         gameMode == EndOfGame) {
14413         i = forwardMostMove;
14414         while (i > currentMove) {
14415             SendToProgram("undo\n", &first);
14416             i--;
14417         }
14418         if(!adjustedClock) {
14419         whiteTimeRemaining = timeRemaining[0][currentMove];
14420         blackTimeRemaining = timeRemaining[1][currentMove];
14421         DisplayBothClocks();
14422         }
14423         if (whiteFlag || blackFlag) {
14424             whiteFlag = blackFlag = 0;
14425         }
14426         DisplayTitle("");
14427     }
14428
14429     gameMode = EditGame;
14430     ModeHighlight();
14431     SetGameInfo();
14432 }
14433
14434
14435 void
14436 EditPositionEvent ()
14437 {
14438     if (gameMode == EditPosition) {
14439         EditGameEvent();
14440         return;
14441     }
14442
14443     EditGameEvent();
14444     if (gameMode != EditGame) return;
14445
14446     gameMode = EditPosition;
14447     ModeHighlight();
14448     SetGameInfo();
14449     if (currentMove > 0)
14450       CopyBoard(boards[0], boards[currentMove]);
14451
14452     blackPlaysFirst = !WhiteOnMove(currentMove);
14453     ResetClocks();
14454     currentMove = forwardMostMove = backwardMostMove = 0;
14455     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14456     DisplayMove(-1);
14457     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
14458 }
14459
14460 void
14461 ExitAnalyzeMode ()
14462 {
14463     /* [DM] icsEngineAnalyze - possible call from other functions */
14464     if (appData.icsEngineAnalyze) {
14465         appData.icsEngineAnalyze = FALSE;
14466
14467         DisplayMessage("",_("Close ICS engine analyze..."));
14468     }
14469     if (first.analysisSupport && first.analyzing) {
14470       SendToBoth("exit\n");
14471       first.analyzing = second.analyzing = FALSE;
14472     }
14473     thinkOutput[0] = NULLCHAR;
14474 }
14475
14476 void
14477 EditPositionDone (Boolean fakeRights)
14478 {
14479     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
14480
14481     startedFromSetupPosition = TRUE;
14482     InitChessProgram(&first, FALSE);
14483     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
14484       boards[0][EP_STATUS] = EP_NONE;
14485       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
14486       if(boards[0][0][BOARD_WIDTH>>1] == king) {
14487         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
14488         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
14489       } else boards[0][CASTLING][2] = NoRights;
14490       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
14491         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
14492         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
14493       } else boards[0][CASTLING][5] = NoRights;
14494       if(gameInfo.variant == VariantSChess) {
14495         int i;
14496         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
14497           boards[0][VIRGIN][i] = 0;
14498           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
14499           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
14500         }
14501       }
14502     }
14503     SendToProgram("force\n", &first);
14504     if (blackPlaysFirst) {
14505         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
14506         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
14507         currentMove = forwardMostMove = backwardMostMove = 1;
14508         CopyBoard(boards[1], boards[0]);
14509     } else {
14510         currentMove = forwardMostMove = backwardMostMove = 0;
14511     }
14512     SendBoard(&first, forwardMostMove);
14513     if (appData.debugMode) {
14514         fprintf(debugFP, "EditPosDone\n");
14515     }
14516     DisplayTitle("");
14517     DisplayMessage("", "");
14518     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14519     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14520     gameMode = EditGame;
14521     ModeHighlight();
14522     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14523     ClearHighlights(); /* [AS] */
14524 }
14525
14526 /* Pause for `ms' milliseconds */
14527 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14528 void
14529 TimeDelay (long ms)
14530 {
14531     TimeMark m1, m2;
14532
14533     GetTimeMark(&m1);
14534     do {
14535         GetTimeMark(&m2);
14536     } while (SubtractTimeMarks(&m2, &m1) < ms);
14537 }
14538
14539 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14540 void
14541 SendMultiLineToICS (char *buf)
14542 {
14543     char temp[MSG_SIZ+1], *p;
14544     int len;
14545
14546     len = strlen(buf);
14547     if (len > MSG_SIZ)
14548       len = MSG_SIZ;
14549
14550     strncpy(temp, buf, len);
14551     temp[len] = 0;
14552
14553     p = temp;
14554     while (*p) {
14555         if (*p == '\n' || *p == '\r')
14556           *p = ' ';
14557         ++p;
14558     }
14559
14560     strcat(temp, "\n");
14561     SendToICS(temp);
14562     SendToPlayer(temp, strlen(temp));
14563 }
14564
14565 void
14566 SetWhiteToPlayEvent ()
14567 {
14568     if (gameMode == EditPosition) {
14569         blackPlaysFirst = FALSE;
14570         DisplayBothClocks();    /* works because currentMove is 0 */
14571     } else if (gameMode == IcsExamining) {
14572         SendToICS(ics_prefix);
14573         SendToICS("tomove white\n");
14574     }
14575 }
14576
14577 void
14578 SetBlackToPlayEvent ()
14579 {
14580     if (gameMode == EditPosition) {
14581         blackPlaysFirst = TRUE;
14582         currentMove = 1;        /* kludge */
14583         DisplayBothClocks();
14584         currentMove = 0;
14585     } else if (gameMode == IcsExamining) {
14586         SendToICS(ics_prefix);
14587         SendToICS("tomove black\n");
14588     }
14589 }
14590
14591 void
14592 EditPositionMenuEvent (ChessSquare selection, int x, int y)
14593 {
14594     char buf[MSG_SIZ];
14595     ChessSquare piece = boards[0][y][x];
14596     static Board erasedBoard, currentBoard, menuBoard, nullBoard;
14597     static int lastVariant;
14598
14599     if (gameMode != EditPosition && gameMode != IcsExamining) return;
14600
14601     switch (selection) {
14602       case ClearBoard:
14603         CopyBoard(currentBoard, boards[0]);
14604         CopyBoard(menuBoard, initialPosition);
14605         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
14606             SendToICS(ics_prefix);
14607             SendToICS("bsetup clear\n");
14608         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
14609             SendToICS(ics_prefix);
14610             SendToICS("clearboard\n");
14611         } else {
14612             int nonEmpty = 0;
14613             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
14614                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
14615                 for (y = 0; y < BOARD_HEIGHT; y++) {
14616                     if (gameMode == IcsExamining) {
14617                         if (boards[currentMove][y][x] != EmptySquare) {
14618                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
14619                                     AAA + x, ONE + y);
14620                             SendToICS(buf);
14621                         }
14622                     } else {
14623                         if(boards[0][y][x] != p) nonEmpty++;
14624                         boards[0][y][x] = p;
14625                     }
14626                 }
14627                 menuBoard[1][x] = menuBoard[BOARD_HEIGHT-2][x] = p;
14628             }
14629             if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
14630                 for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates 
14631                     ChessSquare p = menuBoard[0][x];
14632                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[0][y] == p) menuBoard[0][y] = EmptySquare;
14633                     p = menuBoard[BOARD_HEIGHT-1][x];
14634                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[BOARD_HEIGHT-1][y] == p) menuBoard[BOARD_HEIGHT-1][y] = EmptySquare;
14635                 }
14636                 DisplayMessage("Clicking clock again restores position", "");
14637                 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
14638                 if(!nonEmpty) { // asked to clear an empty board
14639                     CopyBoard(boards[0], menuBoard);
14640                 } else
14641                 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
14642                     CopyBoard(boards[0], initialPosition);
14643                 } else
14644                 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
14645                                                                  && !CompareBoards(nullBoard, erasedBoard)) {
14646                     CopyBoard(boards[0], erasedBoard);
14647                 } else
14648                     CopyBoard(erasedBoard, currentBoard);
14649
14650             }
14651         }
14652         if (gameMode == EditPosition) {
14653             DrawPosition(FALSE, boards[0]);
14654         }
14655         break;
14656
14657       case WhitePlay:
14658         SetWhiteToPlayEvent();
14659         break;
14660
14661       case BlackPlay:
14662         SetBlackToPlayEvent();
14663         break;
14664
14665       case EmptySquare:
14666         if (gameMode == IcsExamining) {
14667             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14668             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
14669             SendToICS(buf);
14670         } else {
14671             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14672                 if(x == BOARD_LEFT-2) {
14673                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
14674                     boards[0][y][1] = 0;
14675                 } else
14676                 if(x == BOARD_RGHT+1) {
14677                     if(y >= gameInfo.holdingsSize) break;
14678                     boards[0][y][BOARD_WIDTH-2] = 0;
14679                 } else break;
14680             }
14681             boards[0][y][x] = EmptySquare;
14682             DrawPosition(FALSE, boards[0]);
14683         }
14684         break;
14685
14686       case PromotePiece:
14687         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
14688            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
14689             selection = (ChessSquare) (PROMOTED piece);
14690         } else if(piece == EmptySquare) selection = WhiteSilver;
14691         else selection = (ChessSquare)((int)piece - 1);
14692         goto defaultlabel;
14693
14694       case DemotePiece:
14695         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
14696            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
14697             selection = (ChessSquare) (DEMOTED piece);
14698         } else if(piece == EmptySquare) selection = BlackSilver;
14699         else selection = (ChessSquare)((int)piece + 1);
14700         goto defaultlabel;
14701
14702       case WhiteQueen:
14703       case BlackQueen:
14704         if(gameInfo.variant == VariantShatranj ||
14705            gameInfo.variant == VariantXiangqi  ||
14706            gameInfo.variant == VariantCourier  ||
14707            gameInfo.variant == VariantASEAN    ||
14708            gameInfo.variant == VariantMakruk     )
14709             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
14710         goto defaultlabel;
14711
14712       case WhiteKing:
14713       case BlackKing:
14714         if(gameInfo.variant == VariantXiangqi)
14715             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
14716         if(gameInfo.variant == VariantKnightmate)
14717             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
14718       default:
14719         defaultlabel:
14720         if (gameMode == IcsExamining) {
14721             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14722             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
14723                      PieceToChar(selection), AAA + x, ONE + y);
14724             SendToICS(buf);
14725         } else {
14726             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14727                 int n;
14728                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
14729                     n = PieceToNumber(selection - BlackPawn);
14730                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
14731                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
14732                     boards[0][BOARD_HEIGHT-1-n][1]++;
14733                 } else
14734                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
14735                     n = PieceToNumber(selection);
14736                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
14737                     boards[0][n][BOARD_WIDTH-1] = selection;
14738                     boards[0][n][BOARD_WIDTH-2]++;
14739                 }
14740             } else
14741             boards[0][y][x] = selection;
14742             DrawPosition(TRUE, boards[0]);
14743             ClearHighlights();
14744             fromX = fromY = -1;
14745         }
14746         break;
14747     }
14748 }
14749
14750
14751 void
14752 DropMenuEvent (ChessSquare selection, int x, int y)
14753 {
14754     ChessMove moveType;
14755
14756     switch (gameMode) {
14757       case IcsPlayingWhite:
14758       case MachinePlaysBlack:
14759         if (!WhiteOnMove(currentMove)) {
14760             DisplayMoveError(_("It is Black's turn"));
14761             return;
14762         }
14763         moveType = WhiteDrop;
14764         break;
14765       case IcsPlayingBlack:
14766       case MachinePlaysWhite:
14767         if (WhiteOnMove(currentMove)) {
14768             DisplayMoveError(_("It is White's turn"));
14769             return;
14770         }
14771         moveType = BlackDrop;
14772         break;
14773       case EditGame:
14774         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
14775         break;
14776       default:
14777         return;
14778     }
14779
14780     if (moveType == BlackDrop && selection < BlackPawn) {
14781       selection = (ChessSquare) ((int) selection
14782                                  + (int) BlackPawn - (int) WhitePawn);
14783     }
14784     if (boards[currentMove][y][x] != EmptySquare) {
14785         DisplayMoveError(_("That square is occupied"));
14786         return;
14787     }
14788
14789     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
14790 }
14791
14792 void
14793 AcceptEvent ()
14794 {
14795     /* Accept a pending offer of any kind from opponent */
14796
14797     if (appData.icsActive) {
14798         SendToICS(ics_prefix);
14799         SendToICS("accept\n");
14800     } else if (cmailMsgLoaded) {
14801         if (currentMove == cmailOldMove &&
14802             commentList[cmailOldMove] != NULL &&
14803             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14804                    "Black offers a draw" : "White offers a draw")) {
14805             TruncateGame();
14806             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14807             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14808         } else {
14809             DisplayError(_("There is no pending offer on this move"), 0);
14810             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14811         }
14812     } else {
14813         /* Not used for offers from chess program */
14814     }
14815 }
14816
14817 void
14818 DeclineEvent ()
14819 {
14820     /* Decline a pending offer of any kind from opponent */
14821
14822     if (appData.icsActive) {
14823         SendToICS(ics_prefix);
14824         SendToICS("decline\n");
14825     } else if (cmailMsgLoaded) {
14826         if (currentMove == cmailOldMove &&
14827             commentList[cmailOldMove] != NULL &&
14828             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14829                    "Black offers a draw" : "White offers a draw")) {
14830 #ifdef NOTDEF
14831             AppendComment(cmailOldMove, "Draw declined", TRUE);
14832             DisplayComment(cmailOldMove - 1, "Draw declined");
14833 #endif /*NOTDEF*/
14834         } else {
14835             DisplayError(_("There is no pending offer on this move"), 0);
14836         }
14837     } else {
14838         /* Not used for offers from chess program */
14839     }
14840 }
14841
14842 void
14843 RematchEvent ()
14844 {
14845     /* Issue ICS rematch command */
14846     if (appData.icsActive) {
14847         SendToICS(ics_prefix);
14848         SendToICS("rematch\n");
14849     }
14850 }
14851
14852 void
14853 CallFlagEvent ()
14854 {
14855     /* Call your opponent's flag (claim a win on time) */
14856     if (appData.icsActive) {
14857         SendToICS(ics_prefix);
14858         SendToICS("flag\n");
14859     } else {
14860         switch (gameMode) {
14861           default:
14862             return;
14863           case MachinePlaysWhite:
14864             if (whiteFlag) {
14865                 if (blackFlag)
14866                   GameEnds(GameIsDrawn, "Both players ran out of time",
14867                            GE_PLAYER);
14868                 else
14869                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
14870             } else {
14871                 DisplayError(_("Your opponent is not out of time"), 0);
14872             }
14873             break;
14874           case MachinePlaysBlack:
14875             if (blackFlag) {
14876                 if (whiteFlag)
14877                   GameEnds(GameIsDrawn, "Both players ran out of time",
14878                            GE_PLAYER);
14879                 else
14880                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
14881             } else {
14882                 DisplayError(_("Your opponent is not out of time"), 0);
14883             }
14884             break;
14885         }
14886     }
14887 }
14888
14889 void
14890 ClockClick (int which)
14891 {       // [HGM] code moved to back-end from winboard.c
14892         if(which) { // black clock
14893           if (gameMode == EditPosition || gameMode == IcsExamining) {
14894             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14895             SetBlackToPlayEvent();
14896           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
14897           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
14898           } else if (shiftKey) {
14899             AdjustClock(which, -1);
14900           } else if (gameMode == IcsPlayingWhite ||
14901                      gameMode == MachinePlaysBlack) {
14902             CallFlagEvent();
14903           }
14904         } else { // white clock
14905           if (gameMode == EditPosition || gameMode == IcsExamining) {
14906             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14907             SetWhiteToPlayEvent();
14908           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
14909           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
14910           } else if (shiftKey) {
14911             AdjustClock(which, -1);
14912           } else if (gameMode == IcsPlayingBlack ||
14913                    gameMode == MachinePlaysWhite) {
14914             CallFlagEvent();
14915           }
14916         }
14917 }
14918
14919 void
14920 DrawEvent ()
14921 {
14922     /* Offer draw or accept pending draw offer from opponent */
14923
14924     if (appData.icsActive) {
14925         /* Note: tournament rules require draw offers to be
14926            made after you make your move but before you punch
14927            your clock.  Currently ICS doesn't let you do that;
14928            instead, you immediately punch your clock after making
14929            a move, but you can offer a draw at any time. */
14930
14931         SendToICS(ics_prefix);
14932         SendToICS("draw\n");
14933         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
14934     } else if (cmailMsgLoaded) {
14935         if (currentMove == cmailOldMove &&
14936             commentList[cmailOldMove] != NULL &&
14937             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14938                    "Black offers a draw" : "White offers a draw")) {
14939             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14940             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14941         } else if (currentMove == cmailOldMove + 1) {
14942             char *offer = WhiteOnMove(cmailOldMove) ?
14943               "White offers a draw" : "Black offers a draw";
14944             AppendComment(currentMove, offer, TRUE);
14945             DisplayComment(currentMove - 1, offer);
14946             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
14947         } else {
14948             DisplayError(_("You must make your move before offering a draw"), 0);
14949             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14950         }
14951     } else if (first.offeredDraw) {
14952         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
14953     } else {
14954         if (first.sendDrawOffers) {
14955             SendToProgram("draw\n", &first);
14956             userOfferedDraw = TRUE;
14957         }
14958     }
14959 }
14960
14961 void
14962 AdjournEvent ()
14963 {
14964     /* Offer Adjourn or accept pending Adjourn offer from opponent */
14965
14966     if (appData.icsActive) {
14967         SendToICS(ics_prefix);
14968         SendToICS("adjourn\n");
14969     } else {
14970         /* Currently GNU Chess doesn't offer or accept Adjourns */
14971     }
14972 }
14973
14974
14975 void
14976 AbortEvent ()
14977 {
14978     /* Offer Abort or accept pending Abort offer from opponent */
14979
14980     if (appData.icsActive) {
14981         SendToICS(ics_prefix);
14982         SendToICS("abort\n");
14983     } else {
14984         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
14985     }
14986 }
14987
14988 void
14989 ResignEvent ()
14990 {
14991     /* Resign.  You can do this even if it's not your turn. */
14992
14993     if (appData.icsActive) {
14994         SendToICS(ics_prefix);
14995         SendToICS("resign\n");
14996     } else {
14997         switch (gameMode) {
14998           case MachinePlaysWhite:
14999             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15000             break;
15001           case MachinePlaysBlack:
15002             GameEnds(BlackWins, "White resigns", GE_PLAYER);
15003             break;
15004           case EditGame:
15005             if (cmailMsgLoaded) {
15006                 TruncateGame();
15007                 if (WhiteOnMove(cmailOldMove)) {
15008                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
15009                 } else {
15010                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15011                 }
15012                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
15013             }
15014             break;
15015           default:
15016             break;
15017         }
15018     }
15019 }
15020
15021
15022 void
15023 StopObservingEvent ()
15024 {
15025     /* Stop observing current games */
15026     SendToICS(ics_prefix);
15027     SendToICS("unobserve\n");
15028 }
15029
15030 void
15031 StopExaminingEvent ()
15032 {
15033     /* Stop observing current game */
15034     SendToICS(ics_prefix);
15035     SendToICS("unexamine\n");
15036 }
15037
15038 void
15039 ForwardInner (int target)
15040 {
15041     int limit; int oldSeekGraphUp = seekGraphUp;
15042
15043     if (appData.debugMode)
15044         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
15045                 target, currentMove, forwardMostMove);
15046
15047     if (gameMode == EditPosition)
15048       return;
15049
15050     seekGraphUp = FALSE;
15051     MarkTargetSquares(1);
15052
15053     if (gameMode == PlayFromGameFile && !pausing)
15054       PauseEvent();
15055
15056     if (gameMode == IcsExamining && pausing)
15057       limit = pauseExamForwardMostMove;
15058     else
15059       limit = forwardMostMove;
15060
15061     if (target > limit) target = limit;
15062
15063     if (target > 0 && moveList[target - 1][0]) {
15064         int fromX, fromY, toX, toY;
15065         toX = moveList[target - 1][2] - AAA;
15066         toY = moveList[target - 1][3] - ONE;
15067         if (moveList[target - 1][1] == '@') {
15068             if (appData.highlightLastMove) {
15069                 SetHighlights(-1, -1, toX, toY);
15070             }
15071         } else {
15072             fromX = moveList[target - 1][0] - AAA;
15073             fromY = moveList[target - 1][1] - ONE;
15074             if (target == currentMove + 1) {
15075                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
15076             }
15077             if (appData.highlightLastMove) {
15078                 SetHighlights(fromX, fromY, toX, toY);
15079             }
15080         }
15081     }
15082     if (gameMode == EditGame || gameMode == AnalyzeMode ||
15083         gameMode == Training || gameMode == PlayFromGameFile ||
15084         gameMode == AnalyzeFile) {
15085         while (currentMove < target) {
15086             if(second.analyzing) SendMoveToProgram(currentMove, &second);
15087             SendMoveToProgram(currentMove++, &first);
15088         }
15089     } else {
15090         currentMove = target;
15091     }
15092
15093     if (gameMode == EditGame || gameMode == EndOfGame) {
15094         whiteTimeRemaining = timeRemaining[0][currentMove];
15095         blackTimeRemaining = timeRemaining[1][currentMove];
15096     }
15097     DisplayBothClocks();
15098     DisplayMove(currentMove - 1);
15099     DrawPosition(oldSeekGraphUp, boards[currentMove]);
15100     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15101     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
15102         DisplayComment(currentMove - 1, commentList[currentMove]);
15103     }
15104     ClearMap(); // [HGM] exclude: invalidate map
15105 }
15106
15107
15108 void
15109 ForwardEvent ()
15110 {
15111     if (gameMode == IcsExamining && !pausing) {
15112         SendToICS(ics_prefix);
15113         SendToICS("forward\n");
15114     } else {
15115         ForwardInner(currentMove + 1);
15116     }
15117 }
15118
15119 void
15120 ToEndEvent ()
15121 {
15122     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15123         /* to optimze, we temporarily turn off analysis mode while we feed
15124          * the remaining moves to the engine. Otherwise we get analysis output
15125          * after each move.
15126          */
15127         if (first.analysisSupport) {
15128           SendToProgram("exit\nforce\n", &first);
15129           first.analyzing = FALSE;
15130         }
15131     }
15132
15133     if (gameMode == IcsExamining && !pausing) {
15134         SendToICS(ics_prefix);
15135         SendToICS("forward 999999\n");
15136     } else {
15137         ForwardInner(forwardMostMove);
15138     }
15139
15140     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15141         /* we have fed all the moves, so reactivate analysis mode */
15142         SendToProgram("analyze\n", &first);
15143         first.analyzing = TRUE;
15144         /*first.maybeThinking = TRUE;*/
15145         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15146     }
15147 }
15148
15149 void
15150 BackwardInner (int target)
15151 {
15152     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
15153
15154     if (appData.debugMode)
15155         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
15156                 target, currentMove, forwardMostMove);
15157
15158     if (gameMode == EditPosition) return;
15159     seekGraphUp = FALSE;
15160     MarkTargetSquares(1);
15161     if (currentMove <= backwardMostMove) {
15162         ClearHighlights();
15163         DrawPosition(full_redraw, boards[currentMove]);
15164         return;
15165     }
15166     if (gameMode == PlayFromGameFile && !pausing)
15167       PauseEvent();
15168
15169     if (moveList[target][0]) {
15170         int fromX, fromY, toX, toY;
15171         toX = moveList[target][2] - AAA;
15172         toY = moveList[target][3] - ONE;
15173         if (moveList[target][1] == '@') {
15174             if (appData.highlightLastMove) {
15175                 SetHighlights(-1, -1, toX, toY);
15176             }
15177         } else {
15178             fromX = moveList[target][0] - AAA;
15179             fromY = moveList[target][1] - ONE;
15180             if (target == currentMove - 1) {
15181                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
15182             }
15183             if (appData.highlightLastMove) {
15184                 SetHighlights(fromX, fromY, toX, toY);
15185             }
15186         }
15187     }
15188     if (gameMode == EditGame || gameMode==AnalyzeMode ||
15189         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
15190         while (currentMove > target) {
15191             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
15192                 // null move cannot be undone. Reload program with move history before it.
15193                 int i;
15194                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
15195                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
15196                 }
15197                 SendBoard(&first, i);
15198               if(second.analyzing) SendBoard(&second, i);
15199                 for(currentMove=i; currentMove<target; currentMove++) {
15200                     SendMoveToProgram(currentMove, &first);
15201                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
15202                 }
15203                 break;
15204             }
15205             SendToBoth("undo\n");
15206             currentMove--;
15207         }
15208     } else {
15209         currentMove = target;
15210     }
15211
15212     if (gameMode == EditGame || gameMode == EndOfGame) {
15213         whiteTimeRemaining = timeRemaining[0][currentMove];
15214         blackTimeRemaining = timeRemaining[1][currentMove];
15215     }
15216     DisplayBothClocks();
15217     DisplayMove(currentMove - 1);
15218     DrawPosition(full_redraw, boards[currentMove]);
15219     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15220     // [HGM] PV info: routine tests if comment empty
15221     DisplayComment(currentMove - 1, commentList[currentMove]);
15222     ClearMap(); // [HGM] exclude: invalidate map
15223 }
15224
15225 void
15226 BackwardEvent ()
15227 {
15228     if (gameMode == IcsExamining && !pausing) {
15229         SendToICS(ics_prefix);
15230         SendToICS("backward\n");
15231     } else {
15232         BackwardInner(currentMove - 1);
15233     }
15234 }
15235
15236 void
15237 ToStartEvent ()
15238 {
15239     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15240         /* to optimize, we temporarily turn off analysis mode while we undo
15241          * all the moves. Otherwise we get analysis output after each undo.
15242          */
15243         if (first.analysisSupport) {
15244           SendToProgram("exit\nforce\n", &first);
15245           first.analyzing = FALSE;
15246         }
15247     }
15248
15249     if (gameMode == IcsExamining && !pausing) {
15250         SendToICS(ics_prefix);
15251         SendToICS("backward 999999\n");
15252     } else {
15253         BackwardInner(backwardMostMove);
15254     }
15255
15256     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15257         /* we have fed all the moves, so reactivate analysis mode */
15258         SendToProgram("analyze\n", &first);
15259         first.analyzing = TRUE;
15260         /*first.maybeThinking = TRUE;*/
15261         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15262     }
15263 }
15264
15265 void
15266 ToNrEvent (int to)
15267 {
15268   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
15269   if (to >= forwardMostMove) to = forwardMostMove;
15270   if (to <= backwardMostMove) to = backwardMostMove;
15271   if (to < currentMove) {
15272     BackwardInner(to);
15273   } else {
15274     ForwardInner(to);
15275   }
15276 }
15277
15278 void
15279 RevertEvent (Boolean annotate)
15280 {
15281     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
15282         return;
15283     }
15284     if (gameMode != IcsExamining) {
15285         DisplayError(_("You are not examining a game"), 0);
15286         return;
15287     }
15288     if (pausing) {
15289         DisplayError(_("You can't revert while pausing"), 0);
15290         return;
15291     }
15292     SendToICS(ics_prefix);
15293     SendToICS("revert\n");
15294 }
15295
15296 void
15297 RetractMoveEvent ()
15298 {
15299     switch (gameMode) {
15300       case MachinePlaysWhite:
15301       case MachinePlaysBlack:
15302         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15303             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
15304             return;
15305         }
15306         if (forwardMostMove < 2) return;
15307         currentMove = forwardMostMove = forwardMostMove - 2;
15308         whiteTimeRemaining = timeRemaining[0][currentMove];
15309         blackTimeRemaining = timeRemaining[1][currentMove];
15310         DisplayBothClocks();
15311         DisplayMove(currentMove - 1);
15312         ClearHighlights();/*!! could figure this out*/
15313         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
15314         SendToProgram("remove\n", &first);
15315         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
15316         break;
15317
15318       case BeginningOfGame:
15319       default:
15320         break;
15321
15322       case IcsPlayingWhite:
15323       case IcsPlayingBlack:
15324         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
15325             SendToICS(ics_prefix);
15326             SendToICS("takeback 2\n");
15327         } else {
15328             SendToICS(ics_prefix);
15329             SendToICS("takeback 1\n");
15330         }
15331         break;
15332     }
15333 }
15334
15335 void
15336 MoveNowEvent ()
15337 {
15338     ChessProgramState *cps;
15339
15340     switch (gameMode) {
15341       case MachinePlaysWhite:
15342         if (!WhiteOnMove(forwardMostMove)) {
15343             DisplayError(_("It is your turn"), 0);
15344             return;
15345         }
15346         cps = &first;
15347         break;
15348       case MachinePlaysBlack:
15349         if (WhiteOnMove(forwardMostMove)) {
15350             DisplayError(_("It is your turn"), 0);
15351             return;
15352         }
15353         cps = &first;
15354         break;
15355       case TwoMachinesPlay:
15356         if (WhiteOnMove(forwardMostMove) ==
15357             (first.twoMachinesColor[0] == 'w')) {
15358             cps = &first;
15359         } else {
15360             cps = &second;
15361         }
15362         break;
15363       case BeginningOfGame:
15364       default:
15365         return;
15366     }
15367     SendToProgram("?\n", cps);
15368 }
15369
15370 void
15371 TruncateGameEvent ()
15372 {
15373     EditGameEvent();
15374     if (gameMode != EditGame) return;
15375     TruncateGame();
15376 }
15377
15378 void
15379 TruncateGame ()
15380 {
15381     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
15382     if (forwardMostMove > currentMove) {
15383         if (gameInfo.resultDetails != NULL) {
15384             free(gameInfo.resultDetails);
15385             gameInfo.resultDetails = NULL;
15386             gameInfo.result = GameUnfinished;
15387         }
15388         forwardMostMove = currentMove;
15389         HistorySet(parseList, backwardMostMove, forwardMostMove,
15390                    currentMove-1);
15391     }
15392 }
15393
15394 void
15395 HintEvent ()
15396 {
15397     if (appData.noChessProgram) return;
15398     switch (gameMode) {
15399       case MachinePlaysWhite:
15400         if (WhiteOnMove(forwardMostMove)) {
15401             DisplayError(_("Wait until your turn."), 0);
15402             return;
15403         }
15404         break;
15405       case BeginningOfGame:
15406       case MachinePlaysBlack:
15407         if (!WhiteOnMove(forwardMostMove)) {
15408             DisplayError(_("Wait until your turn."), 0);
15409             return;
15410         }
15411         break;
15412       default:
15413         DisplayError(_("No hint available"), 0);
15414         return;
15415     }
15416     SendToProgram("hint\n", &first);
15417     hintRequested = TRUE;
15418 }
15419
15420 void
15421 CreateBookEvent ()
15422 {
15423     ListGame * lg = (ListGame *) gameList.head;
15424     FILE *f, *g;
15425     int nItem;
15426     static int secondTime = FALSE;
15427
15428     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
15429         DisplayError(_("Game list not loaded or empty"), 0);
15430         return;
15431     }
15432
15433     if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
15434         fclose(g);
15435         secondTime++;
15436         DisplayNote(_("Book file exists! Try again for overwrite."));
15437         return;
15438     }
15439
15440     creatingBook = TRUE;
15441     secondTime = FALSE;
15442
15443     /* Get list size */
15444     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
15445         LoadGame(f, nItem, "", TRUE);
15446         AddGameToBook(TRUE);
15447         lg = (ListGame *) lg->node.succ;
15448     }
15449
15450     creatingBook = FALSE;
15451     FlushBook();
15452 }
15453
15454 void
15455 BookEvent ()
15456 {
15457     if (appData.noChessProgram) return;
15458     switch (gameMode) {
15459       case MachinePlaysWhite:
15460         if (WhiteOnMove(forwardMostMove)) {
15461             DisplayError(_("Wait until your turn."), 0);
15462             return;
15463         }
15464         break;
15465       case BeginningOfGame:
15466       case MachinePlaysBlack:
15467         if (!WhiteOnMove(forwardMostMove)) {
15468             DisplayError(_("Wait until your turn."), 0);
15469             return;
15470         }
15471         break;
15472       case EditPosition:
15473         EditPositionDone(TRUE);
15474         break;
15475       case TwoMachinesPlay:
15476         return;
15477       default:
15478         break;
15479     }
15480     SendToProgram("bk\n", &first);
15481     bookOutput[0] = NULLCHAR;
15482     bookRequested = TRUE;
15483 }
15484
15485 void
15486 AboutGameEvent ()
15487 {
15488     char *tags = PGNTags(&gameInfo);
15489     TagsPopUp(tags, CmailMsg());
15490     free(tags);
15491 }
15492
15493 /* end button procedures */
15494
15495 void
15496 PrintPosition (FILE *fp, int move)
15497 {
15498     int i, j;
15499
15500     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15501         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15502             char c = PieceToChar(boards[move][i][j]);
15503             fputc(c == 'x' ? '.' : c, fp);
15504             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
15505         }
15506     }
15507     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
15508       fprintf(fp, "white to play\n");
15509     else
15510       fprintf(fp, "black to play\n");
15511 }
15512
15513 void
15514 PrintOpponents (FILE *fp)
15515 {
15516     if (gameInfo.white != NULL) {
15517         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
15518     } else {
15519         fprintf(fp, "\n");
15520     }
15521 }
15522
15523 /* Find last component of program's own name, using some heuristics */
15524 void
15525 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
15526 {
15527     char *p, *q, c;
15528     int local = (strcmp(host, "localhost") == 0);
15529     while (!local && (p = strchr(prog, ';')) != NULL) {
15530         p++;
15531         while (*p == ' ') p++;
15532         prog = p;
15533     }
15534     if (*prog == '"' || *prog == '\'') {
15535         q = strchr(prog + 1, *prog);
15536     } else {
15537         q = strchr(prog, ' ');
15538     }
15539     if (q == NULL) q = prog + strlen(prog);
15540     p = q;
15541     while (p >= prog && *p != '/' && *p != '\\') p--;
15542     p++;
15543     if(p == prog && *p == '"') p++;
15544     c = *q; *q = 0;
15545     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
15546     memcpy(buf, p, q - p);
15547     buf[q - p] = NULLCHAR;
15548     if (!local) {
15549         strcat(buf, "@");
15550         strcat(buf, host);
15551     }
15552 }
15553
15554 char *
15555 TimeControlTagValue ()
15556 {
15557     char buf[MSG_SIZ];
15558     if (!appData.clockMode) {
15559       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
15560     } else if (movesPerSession > 0) {
15561       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
15562     } else if (timeIncrement == 0) {
15563       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
15564     } else {
15565       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
15566     }
15567     return StrSave(buf);
15568 }
15569
15570 void
15571 SetGameInfo ()
15572 {
15573     /* This routine is used only for certain modes */
15574     VariantClass v = gameInfo.variant;
15575     ChessMove r = GameUnfinished;
15576     char *p = NULL;
15577
15578     if(keepInfo) return;
15579
15580     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
15581         r = gameInfo.result;
15582         p = gameInfo.resultDetails;
15583         gameInfo.resultDetails = NULL;
15584     }
15585     ClearGameInfo(&gameInfo);
15586     gameInfo.variant = v;
15587
15588     switch (gameMode) {
15589       case MachinePlaysWhite:
15590         gameInfo.event = StrSave( appData.pgnEventHeader );
15591         gameInfo.site = StrSave(HostName());
15592         gameInfo.date = PGNDate();
15593         gameInfo.round = StrSave("-");
15594         gameInfo.white = StrSave(first.tidy);
15595         gameInfo.black = StrSave(UserName());
15596         gameInfo.timeControl = TimeControlTagValue();
15597         break;
15598
15599       case MachinePlaysBlack:
15600         gameInfo.event = StrSave( appData.pgnEventHeader );
15601         gameInfo.site = StrSave(HostName());
15602         gameInfo.date = PGNDate();
15603         gameInfo.round = StrSave("-");
15604         gameInfo.white = StrSave(UserName());
15605         gameInfo.black = StrSave(first.tidy);
15606         gameInfo.timeControl = TimeControlTagValue();
15607         break;
15608
15609       case TwoMachinesPlay:
15610         gameInfo.event = StrSave( appData.pgnEventHeader );
15611         gameInfo.site = StrSave(HostName());
15612         gameInfo.date = PGNDate();
15613         if (roundNr > 0) {
15614             char buf[MSG_SIZ];
15615             snprintf(buf, MSG_SIZ, "%d", roundNr);
15616             gameInfo.round = StrSave(buf);
15617         } else {
15618             gameInfo.round = StrSave("-");
15619         }
15620         if (first.twoMachinesColor[0] == 'w') {
15621             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15622             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15623         } else {
15624             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15625             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15626         }
15627         gameInfo.timeControl = TimeControlTagValue();
15628         break;
15629
15630       case EditGame:
15631         gameInfo.event = StrSave("Edited game");
15632         gameInfo.site = StrSave(HostName());
15633         gameInfo.date = PGNDate();
15634         gameInfo.round = StrSave("-");
15635         gameInfo.white = StrSave("-");
15636         gameInfo.black = StrSave("-");
15637         gameInfo.result = r;
15638         gameInfo.resultDetails = p;
15639         break;
15640
15641       case EditPosition:
15642         gameInfo.event = StrSave("Edited position");
15643         gameInfo.site = StrSave(HostName());
15644         gameInfo.date = PGNDate();
15645         gameInfo.round = StrSave("-");
15646         gameInfo.white = StrSave("-");
15647         gameInfo.black = StrSave("-");
15648         break;
15649
15650       case IcsPlayingWhite:
15651       case IcsPlayingBlack:
15652       case IcsObserving:
15653       case IcsExamining:
15654         break;
15655
15656       case PlayFromGameFile:
15657         gameInfo.event = StrSave("Game from non-PGN file");
15658         gameInfo.site = StrSave(HostName());
15659         gameInfo.date = PGNDate();
15660         gameInfo.round = StrSave("-");
15661         gameInfo.white = StrSave("?");
15662         gameInfo.black = StrSave("?");
15663         break;
15664
15665       default:
15666         break;
15667     }
15668 }
15669
15670 void
15671 ReplaceComment (int index, char *text)
15672 {
15673     int len;
15674     char *p;
15675     float score;
15676
15677     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
15678        pvInfoList[index-1].depth == len &&
15679        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
15680        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
15681     while (*text == '\n') text++;
15682     len = strlen(text);
15683     while (len > 0 && text[len - 1] == '\n') len--;
15684
15685     if (commentList[index] != NULL)
15686       free(commentList[index]);
15687
15688     if (len == 0) {
15689         commentList[index] = NULL;
15690         return;
15691     }
15692   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
15693       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
15694       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
15695     commentList[index] = (char *) malloc(len + 2);
15696     strncpy(commentList[index], text, len);
15697     commentList[index][len] = '\n';
15698     commentList[index][len + 1] = NULLCHAR;
15699   } else {
15700     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
15701     char *p;
15702     commentList[index] = (char *) malloc(len + 7);
15703     safeStrCpy(commentList[index], "{\n", 3);
15704     safeStrCpy(commentList[index]+2, text, len+1);
15705     commentList[index][len+2] = NULLCHAR;
15706     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
15707     strcat(commentList[index], "\n}\n");
15708   }
15709 }
15710
15711 void
15712 CrushCRs (char *text)
15713 {
15714   char *p = text;
15715   char *q = text;
15716   char ch;
15717
15718   do {
15719     ch = *p++;
15720     if (ch == '\r') continue;
15721     *q++ = ch;
15722   } while (ch != '\0');
15723 }
15724
15725 void
15726 AppendComment (int index, char *text, Boolean addBraces)
15727 /* addBraces  tells if we should add {} */
15728 {
15729     int oldlen, len;
15730     char *old;
15731
15732 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
15733     if(addBraces == 3) addBraces = 0; else // force appending literally
15734     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
15735
15736     CrushCRs(text);
15737     while (*text == '\n') text++;
15738     len = strlen(text);
15739     while (len > 0 && text[len - 1] == '\n') len--;
15740     text[len] = NULLCHAR;
15741
15742     if (len == 0) return;
15743
15744     if (commentList[index] != NULL) {
15745       Boolean addClosingBrace = addBraces;
15746         old = commentList[index];
15747         oldlen = strlen(old);
15748         while(commentList[index][oldlen-1] ==  '\n')
15749           commentList[index][--oldlen] = NULLCHAR;
15750         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
15751         safeStrCpy(commentList[index], old, oldlen + len + 6);
15752         free(old);
15753         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
15754         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
15755           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
15756           while (*text == '\n') { text++; len--; }
15757           commentList[index][--oldlen] = NULLCHAR;
15758       }
15759         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
15760         else          strcat(commentList[index], "\n");
15761         strcat(commentList[index], text);
15762         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
15763         else          strcat(commentList[index], "\n");
15764     } else {
15765         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
15766         if(addBraces)
15767           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
15768         else commentList[index][0] = NULLCHAR;
15769         strcat(commentList[index], text);
15770         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
15771         if(addBraces == TRUE) strcat(commentList[index], "}\n");
15772     }
15773 }
15774
15775 static char *
15776 FindStr (char * text, char * sub_text)
15777 {
15778     char * result = strstr( text, sub_text );
15779
15780     if( result != NULL ) {
15781         result += strlen( sub_text );
15782     }
15783
15784     return result;
15785 }
15786
15787 /* [AS] Try to extract PV info from PGN comment */
15788 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
15789 char *
15790 GetInfoFromComment (int index, char * text)
15791 {
15792     char * sep = text, *p;
15793
15794     if( text != NULL && index > 0 ) {
15795         int score = 0;
15796         int depth = 0;
15797         int time = -1, sec = 0, deci;
15798         char * s_eval = FindStr( text, "[%eval " );
15799         char * s_emt = FindStr( text, "[%emt " );
15800 #if 0
15801         if( s_eval != NULL || s_emt != NULL ) {
15802 #else
15803         if(0) { // [HGM] this code is not finished, and could actually be detrimental
15804 #endif
15805             /* New style */
15806             char delim;
15807
15808             if( s_eval != NULL ) {
15809                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
15810                     return text;
15811                 }
15812
15813                 if( delim != ']' ) {
15814                     return text;
15815                 }
15816             }
15817
15818             if( s_emt != NULL ) {
15819             }
15820                 return text;
15821         }
15822         else {
15823             /* We expect something like: [+|-]nnn.nn/dd */
15824             int score_lo = 0;
15825
15826             if(*text != '{') return text; // [HGM] braces: must be normal comment
15827
15828             sep = strchr( text, '/' );
15829             if( sep == NULL || sep < (text+4) ) {
15830                 return text;
15831             }
15832
15833             p = text;
15834             if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
15835             if(p[1] == '(') { // comment starts with PV
15836                p = strchr(p, ')'); // locate end of PV
15837                if(p == NULL || sep < p+5) return text;
15838                // at this point we have something like "{(.*) +0.23/6 ..."
15839                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
15840                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
15841                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
15842             }
15843             time = -1; sec = -1; deci = -1;
15844             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
15845                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
15846                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
15847                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
15848                 return text;
15849             }
15850
15851             if( score_lo < 0 || score_lo >= 100 ) {
15852                 return text;
15853             }
15854
15855             if(sec >= 0) time = 600*time + 10*sec; else
15856             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
15857
15858             score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
15859
15860             /* [HGM] PV time: now locate end of PV info */
15861             while( *++sep >= '0' && *sep <= '9'); // strip depth
15862             if(time >= 0)
15863             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
15864             if(sec >= 0)
15865             while( *++sep >= '0' && *sep <= '9'); // strip seconds
15866             if(deci >= 0)
15867             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
15868             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
15869         }
15870
15871         if( depth <= 0 ) {
15872             return text;
15873         }
15874
15875         if( time < 0 ) {
15876             time = -1;
15877         }
15878
15879         pvInfoList[index-1].depth = depth;
15880         pvInfoList[index-1].score = score;
15881         pvInfoList[index-1].time  = 10*time; // centi-sec
15882         if(*sep == '}') *sep = 0; else *--sep = '{';
15883         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
15884     }
15885     return sep;
15886 }
15887
15888 void
15889 SendToProgram (char *message, ChessProgramState *cps)
15890 {
15891     int count, outCount, error;
15892     char buf[MSG_SIZ];
15893
15894     if (cps->pr == NoProc) return;
15895     Attention(cps);
15896
15897     if (appData.debugMode) {
15898         TimeMark now;
15899         GetTimeMark(&now);
15900         fprintf(debugFP, "%ld >%-6s: %s",
15901                 SubtractTimeMarks(&now, &programStartTime),
15902                 cps->which, message);
15903         if(serverFP)
15904             fprintf(serverFP, "%ld >%-6s: %s",
15905                 SubtractTimeMarks(&now, &programStartTime),
15906                 cps->which, message), fflush(serverFP);
15907     }
15908
15909     count = strlen(message);
15910     outCount = OutputToProcess(cps->pr, message, count, &error);
15911     if (outCount < count && !exiting
15912                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
15913       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15914       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
15915         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15916             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15917                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15918                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15919                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15920             } else {
15921                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15922                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15923                 gameInfo.result = res;
15924             }
15925             gameInfo.resultDetails = StrSave(buf);
15926         }
15927         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15928         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15929     }
15930 }
15931
15932 void
15933 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
15934 {
15935     char *end_str;
15936     char buf[MSG_SIZ];
15937     ChessProgramState *cps = (ChessProgramState *)closure;
15938
15939     if (isr != cps->isr) return; /* Killed intentionally */
15940     if (count <= 0) {
15941         if (count == 0) {
15942             RemoveInputSource(cps->isr);
15943             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
15944                     _(cps->which), cps->program);
15945             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
15946             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15947                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15948                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15949                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15950                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15951                 } else {
15952                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15953                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15954                     gameInfo.result = res;
15955                 }
15956                 gameInfo.resultDetails = StrSave(buf);
15957             }
15958             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15959             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
15960         } else {
15961             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
15962                     _(cps->which), cps->program);
15963             RemoveInputSource(cps->isr);
15964
15965             /* [AS] Program is misbehaving badly... kill it */
15966             if( count == -2 ) {
15967                 DestroyChildProcess( cps->pr, 9 );
15968                 cps->pr = NoProc;
15969             }
15970
15971             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15972         }
15973         return;
15974     }
15975
15976     if ((end_str = strchr(message, '\r')) != NULL)
15977       *end_str = NULLCHAR;
15978     if ((end_str = strchr(message, '\n')) != NULL)
15979       *end_str = NULLCHAR;
15980
15981     if (appData.debugMode) {
15982         TimeMark now; int print = 1;
15983         char *quote = ""; char c; int i;
15984
15985         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
15986                 char start = message[0];
15987                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
15988                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
15989                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
15990                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
15991                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
15992                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
15993                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
15994                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
15995                    sscanf(message, "hint: %c", &c)!=1 &&
15996                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
15997                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
15998                     print = (appData.engineComments >= 2);
15999                 }
16000                 message[0] = start; // restore original message
16001         }
16002         if(print) {
16003                 GetTimeMark(&now);
16004                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
16005                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16006                         quote,
16007                         message);
16008                 if(serverFP)
16009                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
16010                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16011                         quote,
16012                         message), fflush(serverFP);
16013         }
16014     }
16015
16016     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
16017     if (appData.icsEngineAnalyze) {
16018         if (strstr(message, "whisper") != NULL ||
16019              strstr(message, "kibitz") != NULL ||
16020             strstr(message, "tellics") != NULL) return;
16021     }
16022
16023     HandleMachineMove(message, cps);
16024 }
16025
16026
16027 void
16028 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
16029 {
16030     char buf[MSG_SIZ];
16031     int seconds;
16032
16033     if( timeControl_2 > 0 ) {
16034         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
16035             tc = timeControl_2;
16036         }
16037     }
16038     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
16039     inc /= cps->timeOdds;
16040     st  /= cps->timeOdds;
16041
16042     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
16043
16044     if (st > 0) {
16045       /* Set exact time per move, normally using st command */
16046       if (cps->stKludge) {
16047         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
16048         seconds = st % 60;
16049         if (seconds == 0) {
16050           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
16051         } else {
16052           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
16053         }
16054       } else {
16055         snprintf(buf, MSG_SIZ, "st %d\n", st);
16056       }
16057     } else {
16058       /* Set conventional or incremental time control, using level command */
16059       if (seconds == 0) {
16060         /* Note old gnuchess bug -- minutes:seconds used to not work.
16061            Fixed in later versions, but still avoid :seconds
16062            when seconds is 0. */
16063         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
16064       } else {
16065         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
16066                  seconds, inc/1000.);
16067       }
16068     }
16069     SendToProgram(buf, cps);
16070
16071     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
16072     /* Orthogonally, limit search to given depth */
16073     if (sd > 0) {
16074       if (cps->sdKludge) {
16075         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
16076       } else {
16077         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
16078       }
16079       SendToProgram(buf, cps);
16080     }
16081
16082     if(cps->nps >= 0) { /* [HGM] nps */
16083         if(cps->supportsNPS == FALSE)
16084           cps->nps = -1; // don't use if engine explicitly says not supported!
16085         else {
16086           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
16087           SendToProgram(buf, cps);
16088         }
16089     }
16090 }
16091
16092 ChessProgramState *
16093 WhitePlayer ()
16094 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
16095 {
16096     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
16097        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
16098         return &second;
16099     return &first;
16100 }
16101
16102 void
16103 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
16104 {
16105     char message[MSG_SIZ];
16106     long time, otime;
16107
16108     /* Note: this routine must be called when the clocks are stopped
16109        or when they have *just* been set or switched; otherwise
16110        it will be off by the time since the current tick started.
16111     */
16112     if (machineWhite) {
16113         time = whiteTimeRemaining / 10;
16114         otime = blackTimeRemaining / 10;
16115     } else {
16116         time = blackTimeRemaining / 10;
16117         otime = whiteTimeRemaining / 10;
16118     }
16119     /* [HGM] translate opponent's time by time-odds factor */
16120     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
16121
16122     if (time <= 0) time = 1;
16123     if (otime <= 0) otime = 1;
16124
16125     snprintf(message, MSG_SIZ, "time %ld\n", time);
16126     SendToProgram(message, cps);
16127
16128     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
16129     SendToProgram(message, cps);
16130 }
16131
16132 char *
16133 EngineDefinedVariant (ChessProgramState *cps, int n)
16134 {   // return name of n-th unknown variant that engine supports
16135     static char buf[MSG_SIZ];
16136     char *p, *s = cps->variants;
16137     if(!s) return NULL;
16138     do { // parse string from variants feature
16139       VariantClass v;
16140         p = strchr(s, ',');
16141         if(p) *p = NULLCHAR;
16142       v = StringToVariant(s);
16143       if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
16144         if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
16145             if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
16146         }
16147         if(p) *p++ = ',';
16148         if(n < 0) return buf;
16149     } while(s = p);
16150     return NULL;
16151 }
16152
16153 int
16154 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16155 {
16156   char buf[MSG_SIZ];
16157   int len = strlen(name);
16158   int val;
16159
16160   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16161     (*p) += len + 1;
16162     sscanf(*p, "%d", &val);
16163     *loc = (val != 0);
16164     while (**p && **p != ' ')
16165       (*p)++;
16166     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16167     SendToProgram(buf, cps);
16168     return TRUE;
16169   }
16170   return FALSE;
16171 }
16172
16173 int
16174 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16175 {
16176   char buf[MSG_SIZ];
16177   int len = strlen(name);
16178   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16179     (*p) += len + 1;
16180     sscanf(*p, "%d", loc);
16181     while (**p && **p != ' ') (*p)++;
16182     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16183     SendToProgram(buf, cps);
16184     return TRUE;
16185   }
16186   return FALSE;
16187 }
16188
16189 int
16190 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
16191 {
16192   char buf[MSG_SIZ];
16193   int len = strlen(name);
16194   if (strncmp((*p), name, len) == 0
16195       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
16196     (*p) += len + 2;
16197     ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
16198     sscanf(*p, "%[^\"]", *loc);
16199     while (**p && **p != '\"') (*p)++;
16200     if (**p == '\"') (*p)++;
16201     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16202     SendToProgram(buf, cps);
16203     return TRUE;
16204   }
16205   return FALSE;
16206 }
16207
16208 int
16209 ParseOption (Option *opt, ChessProgramState *cps)
16210 // [HGM] options: process the string that defines an engine option, and determine
16211 // name, type, default value, and allowed value range
16212 {
16213         char *p, *q, buf[MSG_SIZ];
16214         int n, min = (-1)<<31, max = 1<<31, def;
16215
16216         if(p = strstr(opt->name, " -spin ")) {
16217             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16218             if(max < min) max = min; // enforce consistency
16219             if(def < min) def = min;
16220             if(def > max) def = max;
16221             opt->value = def;
16222             opt->min = min;
16223             opt->max = max;
16224             opt->type = Spin;
16225         } else if((p = strstr(opt->name, " -slider "))) {
16226             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
16227             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16228             if(max < min) max = min; // enforce consistency
16229             if(def < min) def = min;
16230             if(def > max) def = max;
16231             opt->value = def;
16232             opt->min = min;
16233             opt->max = max;
16234             opt->type = Spin; // Slider;
16235         } else if((p = strstr(opt->name, " -string "))) {
16236             opt->textValue = p+9;
16237             opt->type = TextBox;
16238         } else if((p = strstr(opt->name, " -file "))) {
16239             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16240             opt->textValue = p+7;
16241             opt->type = FileName; // FileName;
16242         } else if((p = strstr(opt->name, " -path "))) {
16243             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16244             opt->textValue = p+7;
16245             opt->type = PathName; // PathName;
16246         } else if(p = strstr(opt->name, " -check ")) {
16247             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
16248             opt->value = (def != 0);
16249             opt->type = CheckBox;
16250         } else if(p = strstr(opt->name, " -combo ")) {
16251             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
16252             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
16253             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
16254             opt->value = n = 0;
16255             while(q = StrStr(q, " /// ")) {
16256                 n++; *q = 0;    // count choices, and null-terminate each of them
16257                 q += 5;
16258                 if(*q == '*') { // remember default, which is marked with * prefix
16259                     q++;
16260                     opt->value = n;
16261                 }
16262                 cps->comboList[cps->comboCnt++] = q;
16263             }
16264             cps->comboList[cps->comboCnt++] = NULL;
16265             opt->max = n + 1;
16266             opt->type = ComboBox;
16267         } else if(p = strstr(opt->name, " -button")) {
16268             opt->type = Button;
16269         } else if(p = strstr(opt->name, " -save")) {
16270             opt->type = SaveButton;
16271         } else return FALSE;
16272         *p = 0; // terminate option name
16273         // now look if the command-line options define a setting for this engine option.
16274         if(cps->optionSettings && cps->optionSettings[0])
16275             p = strstr(cps->optionSettings, opt->name); else p = NULL;
16276         if(p && (p == cps->optionSettings || p[-1] == ',')) {
16277           snprintf(buf, MSG_SIZ, "option %s", p);
16278                 if(p = strstr(buf, ",")) *p = 0;
16279                 if(q = strchr(buf, '=')) switch(opt->type) {
16280                     case ComboBox:
16281                         for(n=0; n<opt->max; n++)
16282                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
16283                         break;
16284                     case TextBox:
16285                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
16286                         break;
16287                     case Spin:
16288                     case CheckBox:
16289                         opt->value = atoi(q+1);
16290                     default:
16291                         break;
16292                 }
16293                 strcat(buf, "\n");
16294                 SendToProgram(buf, cps);
16295         }
16296         return TRUE;
16297 }
16298
16299 void
16300 FeatureDone (ChessProgramState *cps, int val)
16301 {
16302   DelayedEventCallback cb = GetDelayedEvent();
16303   if ((cb == InitBackEnd3 && cps == &first) ||
16304       (cb == SettingsMenuIfReady && cps == &second) ||
16305       (cb == LoadEngine) ||
16306       (cb == TwoMachinesEventIfReady)) {
16307     CancelDelayedEvent();
16308     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
16309   }
16310   cps->initDone = val;
16311   if(val) cps->reload = FALSE;
16312 }
16313
16314 /* Parse feature command from engine */
16315 void
16316 ParseFeatures (char *args, ChessProgramState *cps)
16317 {
16318   char *p = args;
16319   char *q = NULL;
16320   int val;
16321   char buf[MSG_SIZ];
16322
16323   for (;;) {
16324     while (*p == ' ') p++;
16325     if (*p == NULLCHAR) return;
16326
16327     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
16328     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
16329     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
16330     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
16331     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
16332     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
16333     if (BoolFeature(&p, "reuse", &val, cps)) {
16334       /* Engine can disable reuse, but can't enable it if user said no */
16335       if (!val) cps->reuse = FALSE;
16336       continue;
16337     }
16338     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
16339     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
16340       if (gameMode == TwoMachinesPlay) {
16341         DisplayTwoMachinesTitle();
16342       } else {
16343         DisplayTitle("");
16344       }
16345       continue;
16346     }
16347     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
16348     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
16349     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
16350     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
16351     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
16352     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
16353     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
16354     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
16355     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
16356     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
16357     if (IntFeature(&p, "done", &val, cps)) {
16358       FeatureDone(cps, val);
16359       continue;
16360     }
16361     /* Added by Tord: */
16362     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
16363     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
16364     /* End of additions by Tord */
16365
16366     /* [HGM] added features: */
16367     if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
16368     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
16369     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
16370     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
16371     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
16372     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
16373     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
16374     if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
16375         if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
16376         FREE(cps->option[cps->nrOptions].name);
16377         cps->option[cps->nrOptions].name = q; q = NULL;
16378         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
16379           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
16380             SendToProgram(buf, cps);
16381             continue;
16382         }
16383         if(cps->nrOptions >= MAX_OPTIONS) {
16384             cps->nrOptions--;
16385             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
16386             DisplayError(buf, 0);
16387         }
16388         continue;
16389     }
16390     /* End of additions by HGM */
16391
16392     /* unknown feature: complain and skip */
16393     q = p;
16394     while (*q && *q != '=') q++;
16395     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
16396     SendToProgram(buf, cps);
16397     p = q;
16398     if (*p == '=') {
16399       p++;
16400       if (*p == '\"') {
16401         p++;
16402         while (*p && *p != '\"') p++;
16403         if (*p == '\"') p++;
16404       } else {
16405         while (*p && *p != ' ') p++;
16406       }
16407     }
16408   }
16409
16410 }
16411
16412 void
16413 PeriodicUpdatesEvent (int newState)
16414 {
16415     if (newState == appData.periodicUpdates)
16416       return;
16417
16418     appData.periodicUpdates=newState;
16419
16420     /* Display type changes, so update it now */
16421 //    DisplayAnalysis();
16422
16423     /* Get the ball rolling again... */
16424     if (newState) {
16425         AnalysisPeriodicEvent(1);
16426         StartAnalysisClock();
16427     }
16428 }
16429
16430 void
16431 PonderNextMoveEvent (int newState)
16432 {
16433     if (newState == appData.ponderNextMove) return;
16434     if (gameMode == EditPosition) EditPositionDone(TRUE);
16435     if (newState) {
16436         SendToProgram("hard\n", &first);
16437         if (gameMode == TwoMachinesPlay) {
16438             SendToProgram("hard\n", &second);
16439         }
16440     } else {
16441         SendToProgram("easy\n", &first);
16442         thinkOutput[0] = NULLCHAR;
16443         if (gameMode == TwoMachinesPlay) {
16444             SendToProgram("easy\n", &second);
16445         }
16446     }
16447     appData.ponderNextMove = newState;
16448 }
16449
16450 void
16451 NewSettingEvent (int option, int *feature, char *command, int value)
16452 {
16453     char buf[MSG_SIZ];
16454
16455     if (gameMode == EditPosition) EditPositionDone(TRUE);
16456     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
16457     if(feature == NULL || *feature) SendToProgram(buf, &first);
16458     if (gameMode == TwoMachinesPlay) {
16459         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
16460     }
16461 }
16462
16463 void
16464 ShowThinkingEvent ()
16465 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
16466 {
16467     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
16468     int newState = appData.showThinking
16469         // [HGM] thinking: other features now need thinking output as well
16470         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
16471
16472     if (oldState == newState) return;
16473     oldState = newState;
16474     if (gameMode == EditPosition) EditPositionDone(TRUE);
16475     if (oldState) {
16476         SendToProgram("post\n", &first);
16477         if (gameMode == TwoMachinesPlay) {
16478             SendToProgram("post\n", &second);
16479         }
16480     } else {
16481         SendToProgram("nopost\n", &first);
16482         thinkOutput[0] = NULLCHAR;
16483         if (gameMode == TwoMachinesPlay) {
16484             SendToProgram("nopost\n", &second);
16485         }
16486     }
16487 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
16488 }
16489
16490 void
16491 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
16492 {
16493   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
16494   if (pr == NoProc) return;
16495   AskQuestion(title, question, replyPrefix, pr);
16496 }
16497
16498 void
16499 TypeInEvent (char firstChar)
16500 {
16501     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
16502         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
16503         gameMode == AnalyzeMode || gameMode == EditGame ||
16504         gameMode == EditPosition || gameMode == IcsExamining ||
16505         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
16506         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
16507                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
16508                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
16509         gameMode == Training) PopUpMoveDialog(firstChar);
16510 }
16511
16512 void
16513 TypeInDoneEvent (char *move)
16514 {
16515         Board board;
16516         int n, fromX, fromY, toX, toY;
16517         char promoChar;
16518         ChessMove moveType;
16519
16520         // [HGM] FENedit
16521         if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
16522                 EditPositionPasteFEN(move);
16523                 return;
16524         }
16525         // [HGM] movenum: allow move number to be typed in any mode
16526         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
16527           ToNrEvent(2*n-1);
16528           return;
16529         }
16530         // undocumented kludge: allow command-line option to be typed in!
16531         // (potentially fatal, and does not implement the effect of the option.)
16532         // should only be used for options that are values on which future decisions will be made,
16533         // and definitely not on options that would be used during initialization.
16534         if(strstr(move, "!!! -") == move) {
16535             ParseArgsFromString(move+4);
16536             return;
16537         }
16538
16539       if (gameMode != EditGame && currentMove != forwardMostMove &&
16540         gameMode != Training) {
16541         DisplayMoveError(_("Displayed move is not current"));
16542       } else {
16543         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16544           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
16545         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
16546         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16547           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
16548           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
16549         } else {
16550           DisplayMoveError(_("Could not parse move"));
16551         }
16552       }
16553 }
16554
16555 void
16556 DisplayMove (int moveNumber)
16557 {
16558     char message[MSG_SIZ];
16559     char res[MSG_SIZ];
16560     char cpThinkOutput[MSG_SIZ];
16561
16562     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
16563
16564     if (moveNumber == forwardMostMove - 1 ||
16565         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16566
16567         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
16568
16569         if (strchr(cpThinkOutput, '\n')) {
16570             *strchr(cpThinkOutput, '\n') = NULLCHAR;
16571         }
16572     } else {
16573         *cpThinkOutput = NULLCHAR;
16574     }
16575
16576     /* [AS] Hide thinking from human user */
16577     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
16578         *cpThinkOutput = NULLCHAR;
16579         if( thinkOutput[0] != NULLCHAR ) {
16580             int i;
16581
16582             for( i=0; i<=hiddenThinkOutputState; i++ ) {
16583                 cpThinkOutput[i] = '.';
16584             }
16585             cpThinkOutput[i] = NULLCHAR;
16586             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
16587         }
16588     }
16589
16590     if (moveNumber == forwardMostMove - 1 &&
16591         gameInfo.resultDetails != NULL) {
16592         if (gameInfo.resultDetails[0] == NULLCHAR) {
16593           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
16594         } else {
16595           snprintf(res, MSG_SIZ, " {%s} %s",
16596                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
16597         }
16598     } else {
16599         res[0] = NULLCHAR;
16600     }
16601
16602     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16603         DisplayMessage(res, cpThinkOutput);
16604     } else {
16605       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
16606                 WhiteOnMove(moveNumber) ? " " : ".. ",
16607                 parseList[moveNumber], res);
16608         DisplayMessage(message, cpThinkOutput);
16609     }
16610 }
16611
16612 void
16613 DisplayComment (int moveNumber, char *text)
16614 {
16615     char title[MSG_SIZ];
16616
16617     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16618       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
16619     } else {
16620       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
16621               WhiteOnMove(moveNumber) ? " " : ".. ",
16622               parseList[moveNumber]);
16623     }
16624     if (text != NULL && (appData.autoDisplayComment || commentUp))
16625         CommentPopUp(title, text);
16626 }
16627
16628 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
16629  * might be busy thinking or pondering.  It can be omitted if your
16630  * gnuchess is configured to stop thinking immediately on any user
16631  * input.  However, that gnuchess feature depends on the FIONREAD
16632  * ioctl, which does not work properly on some flavors of Unix.
16633  */
16634 void
16635 Attention (ChessProgramState *cps)
16636 {
16637 #if ATTENTION
16638     if (!cps->useSigint) return;
16639     if (appData.noChessProgram || (cps->pr == NoProc)) return;
16640     switch (gameMode) {
16641       case MachinePlaysWhite:
16642       case MachinePlaysBlack:
16643       case TwoMachinesPlay:
16644       case IcsPlayingWhite:
16645       case IcsPlayingBlack:
16646       case AnalyzeMode:
16647       case AnalyzeFile:
16648         /* Skip if we know it isn't thinking */
16649         if (!cps->maybeThinking) return;
16650         if (appData.debugMode)
16651           fprintf(debugFP, "Interrupting %s\n", cps->which);
16652         InterruptChildProcess(cps->pr);
16653         cps->maybeThinking = FALSE;
16654         break;
16655       default:
16656         break;
16657     }
16658 #endif /*ATTENTION*/
16659 }
16660
16661 int
16662 CheckFlags ()
16663 {
16664     if (whiteTimeRemaining <= 0) {
16665         if (!whiteFlag) {
16666             whiteFlag = TRUE;
16667             if (appData.icsActive) {
16668                 if (appData.autoCallFlag &&
16669                     gameMode == IcsPlayingBlack && !blackFlag) {
16670                   SendToICS(ics_prefix);
16671                   SendToICS("flag\n");
16672                 }
16673             } else {
16674                 if (blackFlag) {
16675                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16676                 } else {
16677                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
16678                     if (appData.autoCallFlag) {
16679                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
16680                         return TRUE;
16681                     }
16682                 }
16683             }
16684         }
16685     }
16686     if (blackTimeRemaining <= 0) {
16687         if (!blackFlag) {
16688             blackFlag = TRUE;
16689             if (appData.icsActive) {
16690                 if (appData.autoCallFlag &&
16691                     gameMode == IcsPlayingWhite && !whiteFlag) {
16692                   SendToICS(ics_prefix);
16693                   SendToICS("flag\n");
16694                 }
16695             } else {
16696                 if (whiteFlag) {
16697                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16698                 } else {
16699                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
16700                     if (appData.autoCallFlag) {
16701                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
16702                         return TRUE;
16703                     }
16704                 }
16705             }
16706         }
16707     }
16708     return FALSE;
16709 }
16710
16711 void
16712 CheckTimeControl ()
16713 {
16714     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
16715         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
16716
16717     /*
16718      * add time to clocks when time control is achieved ([HGM] now also used for increment)
16719      */
16720     if ( !WhiteOnMove(forwardMostMove) ) {
16721         /* White made time control */
16722         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
16723         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
16724         /* [HGM] time odds: correct new time quota for time odds! */
16725                                             / WhitePlayer()->timeOdds;
16726         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
16727     } else {
16728         lastBlack -= blackTimeRemaining;
16729         /* Black made time control */
16730         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
16731                                             / WhitePlayer()->other->timeOdds;
16732         lastWhite = whiteTimeRemaining;
16733     }
16734 }
16735
16736 void
16737 DisplayBothClocks ()
16738 {
16739     int wom = gameMode == EditPosition ?
16740       !blackPlaysFirst : WhiteOnMove(currentMove);
16741     DisplayWhiteClock(whiteTimeRemaining, wom);
16742     DisplayBlackClock(blackTimeRemaining, !wom);
16743 }
16744
16745
16746 /* Timekeeping seems to be a portability nightmare.  I think everyone
16747    has ftime(), but I'm really not sure, so I'm including some ifdefs
16748    to use other calls if you don't.  Clocks will be less accurate if
16749    you have neither ftime nor gettimeofday.
16750 */
16751
16752 /* VS 2008 requires the #include outside of the function */
16753 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
16754 #include <sys/timeb.h>
16755 #endif
16756
16757 /* Get the current time as a TimeMark */
16758 void
16759 GetTimeMark (TimeMark *tm)
16760 {
16761 #if HAVE_GETTIMEOFDAY
16762
16763     struct timeval timeVal;
16764     struct timezone timeZone;
16765
16766     gettimeofday(&timeVal, &timeZone);
16767     tm->sec = (long) timeVal.tv_sec;
16768     tm->ms = (int) (timeVal.tv_usec / 1000L);
16769
16770 #else /*!HAVE_GETTIMEOFDAY*/
16771 #if HAVE_FTIME
16772
16773 // include <sys/timeb.h> / moved to just above start of function
16774     struct timeb timeB;
16775
16776     ftime(&timeB);
16777     tm->sec = (long) timeB.time;
16778     tm->ms = (int) timeB.millitm;
16779
16780 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
16781     tm->sec = (long) time(NULL);
16782     tm->ms = 0;
16783 #endif
16784 #endif
16785 }
16786
16787 /* Return the difference in milliseconds between two
16788    time marks.  We assume the difference will fit in a long!
16789 */
16790 long
16791 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
16792 {
16793     return 1000L*(tm2->sec - tm1->sec) +
16794            (long) (tm2->ms - tm1->ms);
16795 }
16796
16797
16798 /*
16799  * Code to manage the game clocks.
16800  *
16801  * In tournament play, black starts the clock and then white makes a move.
16802  * We give the human user a slight advantage if he is playing white---the
16803  * clocks don't run until he makes his first move, so it takes zero time.
16804  * Also, we don't account for network lag, so we could get out of sync
16805  * with GNU Chess's clock -- but then, referees are always right.
16806  */
16807
16808 static TimeMark tickStartTM;
16809 static long intendedTickLength;
16810
16811 long
16812 NextTickLength (long timeRemaining)
16813 {
16814     long nominalTickLength, nextTickLength;
16815
16816     if (timeRemaining > 0L && timeRemaining <= 10000L)
16817       nominalTickLength = 100L;
16818     else
16819       nominalTickLength = 1000L;
16820     nextTickLength = timeRemaining % nominalTickLength;
16821     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
16822
16823     return nextTickLength;
16824 }
16825
16826 /* Adjust clock one minute up or down */
16827 void
16828 AdjustClock (Boolean which, int dir)
16829 {
16830     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
16831     if(which) blackTimeRemaining += 60000*dir;
16832     else      whiteTimeRemaining += 60000*dir;
16833     DisplayBothClocks();
16834     adjustedClock = TRUE;
16835 }
16836
16837 /* Stop clocks and reset to a fresh time control */
16838 void
16839 ResetClocks ()
16840 {
16841     (void) StopClockTimer();
16842     if (appData.icsActive) {
16843         whiteTimeRemaining = blackTimeRemaining = 0;
16844     } else if (searchTime) {
16845         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16846         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16847     } else { /* [HGM] correct new time quote for time odds */
16848         whiteTC = blackTC = fullTimeControlString;
16849         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
16850         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
16851     }
16852     if (whiteFlag || blackFlag) {
16853         DisplayTitle("");
16854         whiteFlag = blackFlag = FALSE;
16855     }
16856     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
16857     DisplayBothClocks();
16858     adjustedClock = FALSE;
16859 }
16860
16861 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
16862
16863 /* Decrement running clock by amount of time that has passed */
16864 void
16865 DecrementClocks ()
16866 {
16867     long timeRemaining;
16868     long lastTickLength, fudge;
16869     TimeMark now;
16870
16871     if (!appData.clockMode) return;
16872     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
16873
16874     GetTimeMark(&now);
16875
16876     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16877
16878     /* Fudge if we woke up a little too soon */
16879     fudge = intendedTickLength - lastTickLength;
16880     if (fudge < 0 || fudge > FUDGE) fudge = 0;
16881
16882     if (WhiteOnMove(forwardMostMove)) {
16883         if(whiteNPS >= 0) lastTickLength = 0;
16884         timeRemaining = whiteTimeRemaining -= lastTickLength;
16885         if(timeRemaining < 0 && !appData.icsActive) {
16886             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
16887             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
16888                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
16889                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
16890             }
16891         }
16892         DisplayWhiteClock(whiteTimeRemaining - fudge,
16893                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16894     } else {
16895         if(blackNPS >= 0) lastTickLength = 0;
16896         timeRemaining = blackTimeRemaining -= lastTickLength;
16897         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
16898             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
16899             if(suddenDeath) {
16900                 blackStartMove = forwardMostMove;
16901                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
16902             }
16903         }
16904         DisplayBlackClock(blackTimeRemaining - fudge,
16905                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16906     }
16907     if (CheckFlags()) return;
16908
16909     if(twoBoards) { // count down secondary board's clocks as well
16910         activePartnerTime -= lastTickLength;
16911         partnerUp = 1;
16912         if(activePartner == 'W')
16913             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
16914         else
16915             DisplayBlackClock(activePartnerTime, TRUE);
16916         partnerUp = 0;
16917     }
16918
16919     tickStartTM = now;
16920     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
16921     StartClockTimer(intendedTickLength);
16922
16923     /* if the time remaining has fallen below the alarm threshold, sound the
16924      * alarm. if the alarm has sounded and (due to a takeback or time control
16925      * with increment) the time remaining has increased to a level above the
16926      * threshold, reset the alarm so it can sound again.
16927      */
16928
16929     if (appData.icsActive && appData.icsAlarm) {
16930
16931         /* make sure we are dealing with the user's clock */
16932         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
16933                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
16934            )) return;
16935
16936         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
16937             alarmSounded = FALSE;
16938         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
16939             PlayAlarmSound();
16940             alarmSounded = TRUE;
16941         }
16942     }
16943 }
16944
16945
16946 /* A player has just moved, so stop the previously running
16947    clock and (if in clock mode) start the other one.
16948    We redisplay both clocks in case we're in ICS mode, because
16949    ICS gives us an update to both clocks after every move.
16950    Note that this routine is called *after* forwardMostMove
16951    is updated, so the last fractional tick must be subtracted
16952    from the color that is *not* on move now.
16953 */
16954 void
16955 SwitchClocks (int newMoveNr)
16956 {
16957     long lastTickLength;
16958     TimeMark now;
16959     int flagged = FALSE;
16960
16961     GetTimeMark(&now);
16962
16963     if (StopClockTimer() && appData.clockMode) {
16964         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16965         if (!WhiteOnMove(forwardMostMove)) {
16966             if(blackNPS >= 0) lastTickLength = 0;
16967             blackTimeRemaining -= lastTickLength;
16968            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16969 //         if(pvInfoList[forwardMostMove].time == -1)
16970                  pvInfoList[forwardMostMove].time =               // use GUI time
16971                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
16972         } else {
16973            if(whiteNPS >= 0) lastTickLength = 0;
16974            whiteTimeRemaining -= lastTickLength;
16975            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16976 //         if(pvInfoList[forwardMostMove].time == -1)
16977                  pvInfoList[forwardMostMove].time =
16978                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
16979         }
16980         flagged = CheckFlags();
16981     }
16982     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
16983     CheckTimeControl();
16984
16985     if (flagged || !appData.clockMode) return;
16986
16987     switch (gameMode) {
16988       case MachinePlaysBlack:
16989       case MachinePlaysWhite:
16990       case BeginningOfGame:
16991         if (pausing) return;
16992         break;
16993
16994       case EditGame:
16995       case PlayFromGameFile:
16996       case IcsExamining:
16997         return;
16998
16999       default:
17000         break;
17001     }
17002
17003     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
17004         if(WhiteOnMove(forwardMostMove))
17005              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17006         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17007     }
17008
17009     tickStartTM = now;
17010     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17011       whiteTimeRemaining : blackTimeRemaining);
17012     StartClockTimer(intendedTickLength);
17013 }
17014
17015
17016 /* Stop both clocks */
17017 void
17018 StopClocks ()
17019 {
17020     long lastTickLength;
17021     TimeMark now;
17022
17023     if (!StopClockTimer()) return;
17024     if (!appData.clockMode) return;
17025
17026     GetTimeMark(&now);
17027
17028     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17029     if (WhiteOnMove(forwardMostMove)) {
17030         if(whiteNPS >= 0) lastTickLength = 0;
17031         whiteTimeRemaining -= lastTickLength;
17032         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
17033     } else {
17034         if(blackNPS >= 0) lastTickLength = 0;
17035         blackTimeRemaining -= lastTickLength;
17036         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
17037     }
17038     CheckFlags();
17039 }
17040
17041 /* Start clock of player on move.  Time may have been reset, so
17042    if clock is already running, stop and restart it. */
17043 void
17044 StartClocks ()
17045 {
17046     (void) StopClockTimer(); /* in case it was running already */
17047     DisplayBothClocks();
17048     if (CheckFlags()) return;
17049
17050     if (!appData.clockMode) return;
17051     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
17052
17053     GetTimeMark(&tickStartTM);
17054     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17055       whiteTimeRemaining : blackTimeRemaining);
17056
17057    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
17058     whiteNPS = blackNPS = -1;
17059     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
17060        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
17061         whiteNPS = first.nps;
17062     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
17063        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
17064         blackNPS = first.nps;
17065     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
17066         whiteNPS = second.nps;
17067     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
17068         blackNPS = second.nps;
17069     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
17070
17071     StartClockTimer(intendedTickLength);
17072 }
17073
17074 char *
17075 TimeString (long ms)
17076 {
17077     long second, minute, hour, day;
17078     char *sign = "";
17079     static char buf[32];
17080
17081     if (ms > 0 && ms <= 9900) {
17082       /* convert milliseconds to tenths, rounding up */
17083       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
17084
17085       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
17086       return buf;
17087     }
17088
17089     /* convert milliseconds to seconds, rounding up */
17090     /* use floating point to avoid strangeness of integer division
17091        with negative dividends on many machines */
17092     second = (long) floor(((double) (ms + 999L)) / 1000.0);
17093
17094     if (second < 0) {
17095         sign = "-";
17096         second = -second;
17097     }
17098
17099     day = second / (60 * 60 * 24);
17100     second = second % (60 * 60 * 24);
17101     hour = second / (60 * 60);
17102     second = second % (60 * 60);
17103     minute = second / 60;
17104     second = second % 60;
17105
17106     if (day > 0)
17107       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
17108               sign, day, hour, minute, second);
17109     else if (hour > 0)
17110       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
17111     else
17112       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
17113
17114     return buf;
17115 }
17116
17117
17118 /*
17119  * This is necessary because some C libraries aren't ANSI C compliant yet.
17120  */
17121 char *
17122 StrStr (char *string, char *match)
17123 {
17124     int i, length;
17125
17126     length = strlen(match);
17127
17128     for (i = strlen(string) - length; i >= 0; i--, string++)
17129       if (!strncmp(match, string, length))
17130         return string;
17131
17132     return NULL;
17133 }
17134
17135 char *
17136 StrCaseStr (char *string, char *match)
17137 {
17138     int i, j, length;
17139
17140     length = strlen(match);
17141
17142     for (i = strlen(string) - length; i >= 0; i--, string++) {
17143         for (j = 0; j < length; j++) {
17144             if (ToLower(match[j]) != ToLower(string[j]))
17145               break;
17146         }
17147         if (j == length) return string;
17148     }
17149
17150     return NULL;
17151 }
17152
17153 #ifndef _amigados
17154 int
17155 StrCaseCmp (char *s1, char *s2)
17156 {
17157     char c1, c2;
17158
17159     for (;;) {
17160         c1 = ToLower(*s1++);
17161         c2 = ToLower(*s2++);
17162         if (c1 > c2) return 1;
17163         if (c1 < c2) return -1;
17164         if (c1 == NULLCHAR) return 0;
17165     }
17166 }
17167
17168
17169 int
17170 ToLower (int c)
17171 {
17172     return isupper(c) ? tolower(c) : c;
17173 }
17174
17175
17176 int
17177 ToUpper (int c)
17178 {
17179     return islower(c) ? toupper(c) : c;
17180 }
17181 #endif /* !_amigados    */
17182
17183 char *
17184 StrSave (char *s)
17185 {
17186   char *ret;
17187
17188   if ((ret = (char *) malloc(strlen(s) + 1)))
17189     {
17190       safeStrCpy(ret, s, strlen(s)+1);
17191     }
17192   return ret;
17193 }
17194
17195 char *
17196 StrSavePtr (char *s, char **savePtr)
17197 {
17198     if (*savePtr) {
17199         free(*savePtr);
17200     }
17201     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
17202       safeStrCpy(*savePtr, s, strlen(s)+1);
17203     }
17204     return(*savePtr);
17205 }
17206
17207 char *
17208 PGNDate ()
17209 {
17210     time_t clock;
17211     struct tm *tm;
17212     char buf[MSG_SIZ];
17213
17214     clock = time((time_t *)NULL);
17215     tm = localtime(&clock);
17216     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
17217             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
17218     return StrSave(buf);
17219 }
17220
17221
17222 char *
17223 PositionToFEN (int move, char *overrideCastling, int moveCounts)
17224 {
17225     int i, j, fromX, fromY, toX, toY;
17226     int whiteToPlay;
17227     char buf[MSG_SIZ];
17228     char *p, *q;
17229     int emptycount;
17230     ChessSquare piece;
17231
17232     whiteToPlay = (gameMode == EditPosition) ?
17233       !blackPlaysFirst : (move % 2 == 0);
17234     p = buf;
17235
17236     /* Piece placement data */
17237     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17238         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
17239         emptycount = 0;
17240         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
17241             if (boards[move][i][j] == EmptySquare) {
17242                 emptycount++;
17243             } else { ChessSquare piece = boards[move][i][j];
17244                 if (emptycount > 0) {
17245                     if(emptycount<10) /* [HGM] can be >= 10 */
17246                         *p++ = '0' + emptycount;
17247                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17248                     emptycount = 0;
17249                 }
17250                 if(PieceToChar(piece) == '+') {
17251                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
17252                     *p++ = '+';
17253                     piece = (ChessSquare)(DEMOTED piece);
17254                 }
17255                 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
17256                 if(p[-1] == '~') {
17257                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
17258                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
17259                     *p++ = '~';
17260                 }
17261             }
17262         }
17263         if (emptycount > 0) {
17264             if(emptycount<10) /* [HGM] can be >= 10 */
17265                 *p++ = '0' + emptycount;
17266             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17267             emptycount = 0;
17268         }
17269         *p++ = '/';
17270     }
17271     *(p - 1) = ' ';
17272
17273     /* [HGM] print Crazyhouse or Shogi holdings */
17274     if( gameInfo.holdingsWidth ) {
17275         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
17276         q = p;
17277         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
17278             piece = boards[move][i][BOARD_WIDTH-1];
17279             if( piece != EmptySquare )
17280               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
17281                   *p++ = PieceToChar(piece);
17282         }
17283         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
17284             piece = boards[move][BOARD_HEIGHT-i-1][0];
17285             if( piece != EmptySquare )
17286               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
17287                   *p++ = PieceToChar(piece);
17288         }
17289
17290         if( q == p ) *p++ = '-';
17291         *p++ = ']';
17292         *p++ = ' ';
17293     }
17294
17295     /* Active color */
17296     *p++ = whiteToPlay ? 'w' : 'b';
17297     *p++ = ' ';
17298
17299   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
17300     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
17301   } else {
17302   if(nrCastlingRights) {
17303      q = p;
17304      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
17305        /* [HGM] write directly from rights */
17306            if(boards[move][CASTLING][2] != NoRights &&
17307               boards[move][CASTLING][0] != NoRights   )
17308                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
17309            if(boards[move][CASTLING][2] != NoRights &&
17310               boards[move][CASTLING][1] != NoRights   )
17311                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
17312            if(boards[move][CASTLING][5] != NoRights &&
17313               boards[move][CASTLING][3] != NoRights   )
17314                 *p++ = boards[move][CASTLING][3] + AAA;
17315            if(boards[move][CASTLING][5] != NoRights &&
17316               boards[move][CASTLING][4] != NoRights   )
17317                 *p++ = boards[move][CASTLING][4] + AAA;
17318      } else {
17319
17320         /* [HGM] write true castling rights */
17321         if( nrCastlingRights == 6 ) {
17322             int q, k=0;
17323             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
17324                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
17325             q = (boards[move][CASTLING][1] == BOARD_LEFT &&
17326                  boards[move][CASTLING][2] != NoRights  );
17327             if(gameInfo.variant == VariantSChess) { // for S-Chess, indicate all vrgin backrank pieces
17328                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_RGHT]; // count white held pieces
17329                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17330                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
17331                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
17332             }
17333             if(q) *p++ = 'Q';
17334             k = 0;
17335             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
17336                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
17337             q = (boards[move][CASTLING][4] == BOARD_LEFT &&
17338                  boards[move][CASTLING][5] != NoRights  );
17339             if(gameInfo.variant == VariantSChess) {
17340                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_LEFT-1]; // count black held pieces
17341                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17342                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
17343                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17344             }
17345             if(q) *p++ = 'q';
17346         }
17347      }
17348      if (q == p) *p++ = '-'; /* No castling rights */
17349      *p++ = ' ';
17350   }
17351
17352   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17353      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17354      gameInfo.variant != VariantMakruk   && gameInfo.variant != VariantASEAN ) {
17355     /* En passant target square */
17356     if (move > backwardMostMove) {
17357         fromX = moveList[move - 1][0] - AAA;
17358         fromY = moveList[move - 1][1] - ONE;
17359         toX = moveList[move - 1][2] - AAA;
17360         toY = moveList[move - 1][3] - ONE;
17361         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
17362             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
17363             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
17364             fromX == toX) {
17365             /* 2-square pawn move just happened */
17366             *p++ = toX + AAA;
17367             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17368         } else {
17369             *p++ = '-';
17370         }
17371     } else if(move == backwardMostMove) {
17372         // [HGM] perhaps we should always do it like this, and forget the above?
17373         if((signed char)boards[move][EP_STATUS] >= 0) {
17374             *p++ = boards[move][EP_STATUS] + AAA;
17375             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17376         } else {
17377             *p++ = '-';
17378         }
17379     } else {
17380         *p++ = '-';
17381     }
17382     *p++ = ' ';
17383   }
17384   }
17385
17386     if(moveCounts)
17387     {   int i = 0, j=move;
17388
17389         /* [HGM] find reversible plies */
17390         if (appData.debugMode) { int k;
17391             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
17392             for(k=backwardMostMove; k<=forwardMostMove; k++)
17393                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
17394
17395         }
17396
17397         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
17398         if( j == backwardMostMove ) i += initialRulePlies;
17399         sprintf(p, "%d ", i);
17400         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
17401
17402         /* Fullmove number */
17403         sprintf(p, "%d", (move / 2) + 1);
17404     } else *--p = NULLCHAR;
17405
17406     return StrSave(buf);
17407 }
17408
17409 Boolean
17410 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
17411 {
17412     int i, j, k, w=0;
17413     char *p, c;
17414     int emptycount, virgin[BOARD_FILES];
17415     ChessSquare piece;
17416
17417     p = fen;
17418
17419     /* Piece placement data */
17420     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17421         j = 0;
17422         for (;;) {
17423             if (*p == '/' || *p == ' ' || *p == '[' ) {
17424                 if(j > w) w = j;
17425                 emptycount = gameInfo.boardWidth - j;
17426                 while (emptycount--)
17427                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17428                 if (*p == '/') p++;
17429                 else if(autoSize) { // we stumbled unexpectedly into end of board
17430                     for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
17431                         for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
17432                     }
17433                     appData.NrRanks = gameInfo.boardHeight - i; i=0;
17434                 }
17435                 break;
17436 #if(BOARD_FILES >= 10)
17437             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
17438                 p++; emptycount=10;
17439                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17440                 while (emptycount--)
17441                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17442 #endif
17443             } else if (*p == '*') {
17444                 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
17445             } else if (isdigit(*p)) {
17446                 emptycount = *p++ - '0';
17447                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
17448                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17449                 while (emptycount--)
17450                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17451             } else if (*p == '+' || isalpha(*p)) {
17452                 if (j >= gameInfo.boardWidth) return FALSE;
17453                 if(*p=='+') {
17454                     piece = CharToPiece(*++p);
17455                     if(piece == EmptySquare) return FALSE; /* unknown piece */
17456                     piece = (ChessSquare) (PROMOTED piece ); p++;
17457                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
17458                 } else piece = CharToPiece(*p++);
17459
17460                 if(piece==EmptySquare) return FALSE; /* unknown piece */
17461                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
17462                     piece = (ChessSquare) (PROMOTED piece);
17463                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
17464                     p++;
17465                 }
17466                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
17467             } else {
17468                 return FALSE;
17469             }
17470         }
17471     }
17472     while (*p == '/' || *p == ' ') p++;
17473
17474     if(autoSize) appData.NrFiles = w, InitPosition(TRUE);
17475
17476     /* [HGM] by default clear Crazyhouse holdings, if present */
17477     if(gameInfo.holdingsWidth) {
17478        for(i=0; i<BOARD_HEIGHT; i++) {
17479            board[i][0]             = EmptySquare; /* black holdings */
17480            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
17481            board[i][1]             = (ChessSquare) 0; /* black counts */
17482            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
17483        }
17484     }
17485
17486     /* [HGM] look for Crazyhouse holdings here */
17487     while(*p==' ') p++;
17488     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
17489         if(*p == '[') p++;
17490         if(*p == '-' ) p++; /* empty holdings */ else {
17491             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
17492             /* if we would allow FEN reading to set board size, we would   */
17493             /* have to add holdings and shift the board read so far here   */
17494             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
17495                 p++;
17496                 if((int) piece >= (int) BlackPawn ) {
17497                     i = (int)piece - (int)BlackPawn;
17498                     i = PieceToNumber((ChessSquare)i);
17499                     if( i >= gameInfo.holdingsSize ) return FALSE;
17500                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
17501                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
17502                 } else {
17503                     i = (int)piece - (int)WhitePawn;
17504                     i = PieceToNumber((ChessSquare)i);
17505                     if( i >= gameInfo.holdingsSize ) return FALSE;
17506                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
17507                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
17508                 }
17509             }
17510         }
17511         if(*p == ']') p++;
17512     }
17513
17514     while(*p == ' ') p++;
17515
17516     /* Active color */
17517     c = *p++;
17518     if(appData.colorNickNames) {
17519       if( c == appData.colorNickNames[0] ) c = 'w'; else
17520       if( c == appData.colorNickNames[1] ) c = 'b';
17521     }
17522     switch (c) {
17523       case 'w':
17524         *blackPlaysFirst = FALSE;
17525         break;
17526       case 'b':
17527         *blackPlaysFirst = TRUE;
17528         break;
17529       default:
17530         return FALSE;
17531     }
17532
17533     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
17534     /* return the extra info in global variiables             */
17535
17536     /* set defaults in case FEN is incomplete */
17537     board[EP_STATUS] = EP_UNKNOWN;
17538     for(i=0; i<nrCastlingRights; i++ ) {
17539         board[CASTLING][i] =
17540             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
17541     }   /* assume possible unless obviously impossible */
17542     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
17543     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
17544     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
17545                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
17546     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
17547     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
17548     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
17549                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
17550     FENrulePlies = 0;
17551
17552     while(*p==' ') p++;
17553     if(nrCastlingRights) {
17554       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
17555       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
17556           /* castling indicator present, so default becomes no castlings */
17557           for(i=0; i<nrCastlingRights; i++ ) {
17558                  board[CASTLING][i] = NoRights;
17559           }
17560       }
17561       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
17562              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess) &&
17563              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
17564              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
17565         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
17566
17567         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
17568             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
17569             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
17570         }
17571         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
17572             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
17573         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
17574                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
17575         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
17576                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
17577         switch(c) {
17578           case'K':
17579               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
17580               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
17581               board[CASTLING][2] = whiteKingFile;
17582               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
17583               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17584               break;
17585           case'Q':
17586               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
17587               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
17588               board[CASTLING][2] = whiteKingFile;
17589               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
17590               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17591               break;
17592           case'k':
17593               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
17594               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
17595               board[CASTLING][5] = blackKingFile;
17596               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
17597               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17598               break;
17599           case'q':
17600               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
17601               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
17602               board[CASTLING][5] = blackKingFile;
17603               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
17604               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17605           case '-':
17606               break;
17607           default: /* FRC castlings */
17608               if(c >= 'a') { /* black rights */
17609                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
17610                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17611                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
17612                   if(i == BOARD_RGHT) break;
17613                   board[CASTLING][5] = i;
17614                   c -= AAA;
17615                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
17616                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
17617                   if(c > i)
17618                       board[CASTLING][3] = c;
17619                   else
17620                       board[CASTLING][4] = c;
17621               } else { /* white rights */
17622                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
17623                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17624                     if(board[0][i] == WhiteKing) break;
17625                   if(i == BOARD_RGHT) break;
17626                   board[CASTLING][2] = i;
17627                   c -= AAA - 'a' + 'A';
17628                   if(board[0][c] >= WhiteKing) break;
17629                   if(c > i)
17630                       board[CASTLING][0] = c;
17631                   else
17632                       board[CASTLING][1] = c;
17633               }
17634         }
17635       }
17636       for(i=0; i<nrCastlingRights; i++)
17637         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
17638       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = virgin[i];
17639     if (appData.debugMode) {
17640         fprintf(debugFP, "FEN castling rights:");
17641         for(i=0; i<nrCastlingRights; i++)
17642         fprintf(debugFP, " %d", board[CASTLING][i]);
17643         fprintf(debugFP, "\n");
17644     }
17645
17646       while(*p==' ') p++;
17647     }
17648
17649     /* read e.p. field in games that know e.p. capture */
17650     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17651        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17652        gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
17653       if(*p=='-') {
17654         p++; board[EP_STATUS] = EP_NONE;
17655       } else {
17656          char c = *p++ - AAA;
17657
17658          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
17659          if(*p >= '0' && *p <='9') p++;
17660          board[EP_STATUS] = c;
17661       }
17662     }
17663
17664
17665     if(sscanf(p, "%d", &i) == 1) {
17666         FENrulePlies = i; /* 50-move ply counter */
17667         /* (The move number is still ignored)    */
17668     }
17669
17670     return TRUE;
17671 }
17672
17673 void
17674 EditPositionPasteFEN (char *fen)
17675 {
17676   if (fen != NULL) {
17677     Board initial_position;
17678
17679     if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
17680       DisplayError(_("Bad FEN position in clipboard"), 0);
17681       return ;
17682     } else {
17683       int savedBlackPlaysFirst = blackPlaysFirst;
17684       EditPositionEvent();
17685       blackPlaysFirst = savedBlackPlaysFirst;
17686       CopyBoard(boards[0], initial_position);
17687       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
17688       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
17689       DisplayBothClocks();
17690       DrawPosition(FALSE, boards[currentMove]);
17691     }
17692   }
17693 }
17694
17695 static char cseq[12] = "\\   ";
17696
17697 Boolean
17698 set_cont_sequence (char *new_seq)
17699 {
17700     int len;
17701     Boolean ret;
17702
17703     // handle bad attempts to set the sequence
17704         if (!new_seq)
17705                 return 0; // acceptable error - no debug
17706
17707     len = strlen(new_seq);
17708     ret = (len > 0) && (len < sizeof(cseq));
17709     if (ret)
17710       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
17711     else if (appData.debugMode)
17712       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
17713     return ret;
17714 }
17715
17716 /*
17717     reformat a source message so words don't cross the width boundary.  internal
17718     newlines are not removed.  returns the wrapped size (no null character unless
17719     included in source message).  If dest is NULL, only calculate the size required
17720     for the dest buffer.  lp argument indicats line position upon entry, and it's
17721     passed back upon exit.
17722 */
17723 int
17724 wrap (char *dest, char *src, int count, int width, int *lp)
17725 {
17726     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
17727
17728     cseq_len = strlen(cseq);
17729     old_line = line = *lp;
17730     ansi = len = clen = 0;
17731
17732     for (i=0; i < count; i++)
17733     {
17734         if (src[i] == '\033')
17735             ansi = 1;
17736
17737         // if we hit the width, back up
17738         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
17739         {
17740             // store i & len in case the word is too long
17741             old_i = i, old_len = len;
17742
17743             // find the end of the last word
17744             while (i && src[i] != ' ' && src[i] != '\n')
17745             {
17746                 i--;
17747                 len--;
17748             }
17749
17750             // word too long?  restore i & len before splitting it
17751             if ((old_i-i+clen) >= width)
17752             {
17753                 i = old_i;
17754                 len = old_len;
17755             }
17756
17757             // extra space?
17758             if (i && src[i-1] == ' ')
17759                 len--;
17760
17761             if (src[i] != ' ' && src[i] != '\n')
17762             {
17763                 i--;
17764                 if (len)
17765                     len--;
17766             }
17767
17768             // now append the newline and continuation sequence
17769             if (dest)
17770                 dest[len] = '\n';
17771             len++;
17772             if (dest)
17773                 strncpy(dest+len, cseq, cseq_len);
17774             len += cseq_len;
17775             line = cseq_len;
17776             clen = cseq_len;
17777             continue;
17778         }
17779
17780         if (dest)
17781             dest[len] = src[i];
17782         len++;
17783         if (!ansi)
17784             line++;
17785         if (src[i] == '\n')
17786             line = 0;
17787         if (src[i] == 'm')
17788             ansi = 0;
17789     }
17790     if (dest && appData.debugMode)
17791     {
17792         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
17793             count, width, line, len, *lp);
17794         show_bytes(debugFP, src, count);
17795         fprintf(debugFP, "\ndest: ");
17796         show_bytes(debugFP, dest, len);
17797         fprintf(debugFP, "\n");
17798     }
17799     *lp = dest ? line : old_line;
17800
17801     return len;
17802 }
17803
17804 // [HGM] vari: routines for shelving variations
17805 Boolean modeRestore = FALSE;
17806
17807 void
17808 PushInner (int firstMove, int lastMove)
17809 {
17810         int i, j, nrMoves = lastMove - firstMove;
17811
17812         // push current tail of game on stack
17813         savedResult[storedGames] = gameInfo.result;
17814         savedDetails[storedGames] = gameInfo.resultDetails;
17815         gameInfo.resultDetails = NULL;
17816         savedFirst[storedGames] = firstMove;
17817         savedLast [storedGames] = lastMove;
17818         savedFramePtr[storedGames] = framePtr;
17819         framePtr -= nrMoves; // reserve space for the boards
17820         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
17821             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
17822             for(j=0; j<MOVE_LEN; j++)
17823                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
17824             for(j=0; j<2*MOVE_LEN; j++)
17825                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
17826             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
17827             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
17828             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
17829             pvInfoList[firstMove+i-1].depth = 0;
17830             commentList[framePtr+i] = commentList[firstMove+i];
17831             commentList[firstMove+i] = NULL;
17832         }
17833
17834         storedGames++;
17835         forwardMostMove = firstMove; // truncate game so we can start variation
17836 }
17837
17838 void
17839 PushTail (int firstMove, int lastMove)
17840 {
17841         if(appData.icsActive) { // only in local mode
17842                 forwardMostMove = currentMove; // mimic old ICS behavior
17843                 return;
17844         }
17845         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
17846
17847         PushInner(firstMove, lastMove);
17848         if(storedGames == 1) GreyRevert(FALSE);
17849         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
17850 }
17851
17852 void
17853 PopInner (Boolean annotate)
17854 {
17855         int i, j, nrMoves;
17856         char buf[8000], moveBuf[20];
17857
17858         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
17859         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
17860         nrMoves = savedLast[storedGames] - currentMove;
17861         if(annotate) {
17862                 int cnt = 10;
17863                 if(!WhiteOnMove(currentMove))
17864                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
17865                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
17866                 for(i=currentMove; i<forwardMostMove; i++) {
17867                         if(WhiteOnMove(i))
17868                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
17869                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
17870                         strcat(buf, moveBuf);
17871                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
17872                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
17873                 }
17874                 strcat(buf, ")");
17875         }
17876         for(i=1; i<=nrMoves; i++) { // copy last variation back
17877             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
17878             for(j=0; j<MOVE_LEN; j++)
17879                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
17880             for(j=0; j<2*MOVE_LEN; j++)
17881                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
17882             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
17883             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
17884             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
17885             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
17886             commentList[currentMove+i] = commentList[framePtr+i];
17887             commentList[framePtr+i] = NULL;
17888         }
17889         if(annotate) AppendComment(currentMove+1, buf, FALSE);
17890         framePtr = savedFramePtr[storedGames];
17891         gameInfo.result = savedResult[storedGames];
17892         if(gameInfo.resultDetails != NULL) {
17893             free(gameInfo.resultDetails);
17894       }
17895         gameInfo.resultDetails = savedDetails[storedGames];
17896         forwardMostMove = currentMove + nrMoves;
17897 }
17898
17899 Boolean
17900 PopTail (Boolean annotate)
17901 {
17902         if(appData.icsActive) return FALSE; // only in local mode
17903         if(!storedGames) return FALSE; // sanity
17904         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
17905
17906         PopInner(annotate);
17907         if(currentMove < forwardMostMove) ForwardEvent(); else
17908         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
17909
17910         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
17911         return TRUE;
17912 }
17913
17914 void
17915 CleanupTail ()
17916 {       // remove all shelved variations
17917         int i;
17918         for(i=0; i<storedGames; i++) {
17919             if(savedDetails[i])
17920                 free(savedDetails[i]);
17921             savedDetails[i] = NULL;
17922         }
17923         for(i=framePtr; i<MAX_MOVES; i++) {
17924                 if(commentList[i]) free(commentList[i]);
17925                 commentList[i] = NULL;
17926         }
17927         framePtr = MAX_MOVES-1;
17928         storedGames = 0;
17929 }
17930
17931 void
17932 LoadVariation (int index, char *text)
17933 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
17934         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
17935         int level = 0, move;
17936
17937         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
17938         // first find outermost bracketing variation
17939         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
17940             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
17941                 if(*p == '{') wait = '}'; else
17942                 if(*p == '[') wait = ']'; else
17943                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
17944                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
17945             }
17946             if(*p == wait) wait = NULLCHAR; // closing ]} found
17947             p++;
17948         }
17949         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
17950         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
17951         end[1] = NULLCHAR; // clip off comment beyond variation
17952         ToNrEvent(currentMove-1);
17953         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
17954         // kludge: use ParsePV() to append variation to game
17955         move = currentMove;
17956         ParsePV(start, TRUE, TRUE);
17957         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
17958         ClearPremoveHighlights();
17959         CommentPopDown();
17960         ToNrEvent(currentMove+1);
17961 }
17962
17963 void
17964 LoadTheme ()
17965 {
17966     char *p, *q, buf[MSG_SIZ];
17967     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
17968         snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
17969         ParseArgsFromString(buf);
17970         ActivateTheme(TRUE); // also redo colors
17971         return;
17972     }
17973     p = nickName;
17974     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
17975     {
17976         int len;
17977         q = appData.themeNames;
17978         snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
17979       if(appData.useBitmaps) {
17980         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
17981                 appData.liteBackTextureFile, appData.darkBackTextureFile,
17982                 appData.liteBackTextureMode,
17983                 appData.darkBackTextureMode );
17984       } else {
17985         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
17986                 Col2Text(2),   // lightSquareColor
17987                 Col2Text(3) ); // darkSquareColor
17988       }
17989       if(appData.useBorder) {
17990         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
17991                 appData.border);
17992       } else {
17993         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
17994       }
17995       if(appData.useFont) {
17996         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
17997                 appData.renderPiecesWithFont,
17998                 appData.fontToPieceTable,
17999                 Col2Text(9),    // appData.fontBackColorWhite
18000                 Col2Text(10) ); // appData.fontForeColorBlack
18001       } else {
18002         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
18003                 appData.pieceDirectory);
18004         if(!appData.pieceDirectory[0])
18005           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
18006                 Col2Text(0),   // whitePieceColor
18007                 Col2Text(1) ); // blackPieceColor
18008       }
18009       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
18010                 Col2Text(4),   // highlightSquareColor
18011                 Col2Text(5) ); // premoveHighlightColor
18012         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
18013         if(insert != q) insert[-1] = NULLCHAR;
18014         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
18015         if(q)   free(q);
18016     }
18017     ActivateTheme(FALSE);
18018 }