5774c4d159058b5058b503a6d4c11ae1e071f3f7
[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 ChessSquare ChuArray[6][BOARD_FILES] = {
650     { WhiteLance, WhiteUnicorn, WhiteMan, WhiteFerz, WhiteWazir, WhiteKing,
651       WhiteAlfil, WhiteWazir, WhiteFerz, WhiteMan, WhiteUnicorn, WhiteLance },
652     { BlackLance, BlackUnicorn, BlackMan, BlackFerz, BlackWazir, BlackAlfil,
653       BlackKing, BlackWazir, BlackFerz, BlackMan, BlackUnicorn, BlackLance },
654     { WhiteCannon, EmptySquare, WhiteBishop, EmptySquare, WhiteNightrider, WhiteMarshall,
655       WhiteAngel, WhiteNightrider, EmptySquare, WhiteBishop, EmptySquare, WhiteCannon },
656     { BlackCannon, EmptySquare, BlackBishop, EmptySquare, BlackNightrider, BlackAngel,
657       BlackMarshall, BlackNightrider, EmptySquare, BlackBishop, EmptySquare, BlackCannon },
658     { WhiteFalcon, WhiteSilver, WhiteRook, WhiteCardinal, WhiteDragon, WhiteLion,
659       WhiteQueen, WhiteDragon, WhiteCardinal, WhiteRook, WhiteSilver, WhiteFalcon },
660     { BlackFalcon, BlackSilver, BlackRook, BlackCardinal, BlackDragon, BlackQueen,
661       BlackLion, BlackDragon, BlackCardinal, BlackRook, BlackSilver, BlackFalcon }
662 };
663 #else // !(BOARD_FILES>=12)
664 #define CourierArray CapablancaArray
665 #define ChuArray CapablancaArray
666 #endif // !(BOARD_FILES>=12)
667
668
669 Board initialPosition;
670
671
672 /* Convert str to a rating. Checks for special cases of "----",
673
674    "++++", etc. Also strips ()'s */
675 int
676 string_to_rating (char *str)
677 {
678   while(*str && !isdigit(*str)) ++str;
679   if (!*str)
680     return 0;   /* One of the special "no rating" cases */
681   else
682     return atoi(str);
683 }
684
685 void
686 ClearProgramStats ()
687 {
688     /* Init programStats */
689     programStats.movelist[0] = 0;
690     programStats.depth = 0;
691     programStats.nr_moves = 0;
692     programStats.moves_left = 0;
693     programStats.nodes = 0;
694     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
695     programStats.score = 0;
696     programStats.got_only_move = 0;
697     programStats.got_fail = 0;
698     programStats.line_is_book = 0;
699 }
700
701 void
702 CommonEngineInit ()
703 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
704     if (appData.firstPlaysBlack) {
705         first.twoMachinesColor = "black\n";
706         second.twoMachinesColor = "white\n";
707     } else {
708         first.twoMachinesColor = "white\n";
709         second.twoMachinesColor = "black\n";
710     }
711
712     first.other = &second;
713     second.other = &first;
714
715     { float norm = 1;
716         if(appData.timeOddsMode) {
717             norm = appData.timeOdds[0];
718             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
719         }
720         first.timeOdds  = appData.timeOdds[0]/norm;
721         second.timeOdds = appData.timeOdds[1]/norm;
722     }
723
724     if(programVersion) free(programVersion);
725     if (appData.noChessProgram) {
726         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
727         sprintf(programVersion, "%s", PACKAGE_STRING);
728     } else {
729       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
730       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
731       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
732     }
733 }
734
735 void
736 UnloadEngine (ChessProgramState *cps)
737 {
738         /* Kill off first chess program */
739         if (cps->isr != NULL)
740           RemoveInputSource(cps->isr);
741         cps->isr = NULL;
742
743         if (cps->pr != NoProc) {
744             ExitAnalyzeMode();
745             DoSleep( appData.delayBeforeQuit );
746             SendToProgram("quit\n", cps);
747             DoSleep( appData.delayAfterQuit );
748             DestroyChildProcess(cps->pr, cps->useSigterm);
749         }
750         cps->pr = NoProc;
751         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
752 }
753
754 void
755 ClearOptions (ChessProgramState *cps)
756 {
757     int i;
758     cps->nrOptions = cps->comboCnt = 0;
759     for(i=0; i<MAX_OPTIONS; i++) {
760         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
761         cps->option[i].textValue = 0;
762     }
763 }
764
765 char *engineNames[] = {
766   /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
767      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
768 N_("first"),
769   /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
770      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
771 N_("second")
772 };
773
774 void
775 InitEngine (ChessProgramState *cps, int n)
776 {   // [HGM] all engine initialiation put in a function that does one engine
777
778     ClearOptions(cps);
779
780     cps->which = engineNames[n];
781     cps->maybeThinking = FALSE;
782     cps->pr = NoProc;
783     cps->isr = NULL;
784     cps->sendTime = 2;
785     cps->sendDrawOffers = 1;
786
787     cps->program = appData.chessProgram[n];
788     cps->host = appData.host[n];
789     cps->dir = appData.directory[n];
790     cps->initString = appData.engInitString[n];
791     cps->computerString = appData.computerString[n];
792     cps->useSigint  = TRUE;
793     cps->useSigterm = TRUE;
794     cps->reuse = appData.reuse[n];
795     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
796     cps->useSetboard = FALSE;
797     cps->useSAN = FALSE;
798     cps->usePing = FALSE;
799     cps->lastPing = 0;
800     cps->lastPong = 0;
801     cps->usePlayother = FALSE;
802     cps->useColors = TRUE;
803     cps->useUsermove = FALSE;
804     cps->sendICS = FALSE;
805     cps->sendName = appData.icsActive;
806     cps->sdKludge = FALSE;
807     cps->stKludge = FALSE;
808     if(cps->tidy == NULL) cps->tidy = (char*) malloc(MSG_SIZ);
809     TidyProgramName(cps->program, cps->host, cps->tidy);
810     cps->matchWins = 0;
811     ASSIGN(cps->variants, appData.variant);
812     cps->analysisSupport = 2; /* detect */
813     cps->analyzing = FALSE;
814     cps->initDone = FALSE;
815     cps->reload = FALSE;
816
817     /* New features added by Tord: */
818     cps->useFEN960 = FALSE;
819     cps->useOOCastle = TRUE;
820     /* End of new features added by Tord. */
821     cps->fenOverride  = appData.fenOverride[n];
822
823     /* [HGM] time odds: set factor for each machine */
824     cps->timeOdds  = appData.timeOdds[n];
825
826     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
827     cps->accumulateTC = appData.accumulateTC[n];
828     cps->maxNrOfSessions = 1;
829
830     /* [HGM] debug */
831     cps->debug = FALSE;
832
833     cps->supportsNPS = UNKNOWN;
834     cps->memSize = FALSE;
835     cps->maxCores = FALSE;
836     ASSIGN(cps->egtFormats, "");
837
838     /* [HGM] options */
839     cps->optionSettings  = appData.engOptions[n];
840
841     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
842     cps->isUCI = appData.isUCI[n]; /* [AS] */
843     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
844     cps->highlight = 0;
845
846     if (appData.protocolVersion[n] > PROTOVER
847         || appData.protocolVersion[n] < 1)
848       {
849         char buf[MSG_SIZ];
850         int len;
851
852         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
853                        appData.protocolVersion[n]);
854         if( (len >= MSG_SIZ) && appData.debugMode )
855           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
856
857         DisplayFatalError(buf, 0, 2);
858       }
859     else
860       {
861         cps->protocolVersion = appData.protocolVersion[n];
862       }
863
864     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
865     ParseFeatures(appData.featureDefaults, cps);
866 }
867
868 ChessProgramState *savCps;
869
870 GameMode oldMode;
871
872 void
873 LoadEngine ()
874 {
875     int i;
876     if(WaitForEngine(savCps, LoadEngine)) return;
877     CommonEngineInit(); // recalculate time odds
878     if(gameInfo.variant != StringToVariant(appData.variant)) {
879         // we changed variant when loading the engine; this forces us to reset
880         Reset(TRUE, savCps != &first);
881         oldMode = BeginningOfGame; // to prevent restoring old mode
882     }
883     InitChessProgram(savCps, FALSE);
884     if(gameMode == EditGame) SendToProgram("force\n", savCps); // in EditGame mode engine must be in force mode
885     DisplayMessage("", "");
886     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
887     for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
888     ThawUI();
889     SetGNUMode();
890     if(oldMode == AnalyzeMode) AnalyzeModeEvent();
891 }
892
893 void
894 ReplaceEngine (ChessProgramState *cps, int n)
895 {
896     oldMode = gameMode; // remember mode, so it can be restored after loading sequence is complete
897     keepInfo = 1;
898     if(oldMode != BeginningOfGame) EditGameEvent();
899     keepInfo = 0;
900     UnloadEngine(cps);
901     appData.noChessProgram = FALSE;
902     appData.clockMode = TRUE;
903     InitEngine(cps, n);
904     UpdateLogos(TRUE);
905     if(n) return; // only startup first engine immediately; second can wait
906     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
907     LoadEngine();
908 }
909
910 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
911 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
912
913 static char resetOptions[] =
914         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
915         "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
916         "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 "
917         "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
918
919 void
920 FloatToFront(char **list, char *engineLine)
921 {
922     char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
923     int i=0;
924     if(appData.recentEngines <= 0) return;
925     TidyProgramName(engineLine, "localhost", tidy+1);
926     tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
927     strncpy(buf+1, *list, MSG_SIZ-50);
928     if(p = strstr(buf, tidy)) { // tidy name appears in list
929         q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
930         while(*p++ = *++q); // squeeze out
931     }
932     strcat(tidy, buf+1); // put list behind tidy name
933     p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
934     if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
935     ASSIGN(*list, tidy+1);
936 }
937
938 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
939
940 void
941 Load (ChessProgramState *cps, int i)
942 {
943     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ], buf3[MSG_SIZ], jar;
944     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
945         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
946         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
947         ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
948         FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
949         appData.firstProtocolVersion = PROTOVER;
950         ParseArgsFromString(buf);
951         SwapEngines(i);
952         ReplaceEngine(cps, i);
953         FloatToFront(&appData.recentEngineList, engineLine);
954         return;
955     }
956     p = engineName;
957     while(q = strchr(p, SLASH)) p = q+1;
958     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
959     if(engineDir[0] != NULLCHAR) {
960         ASSIGN(appData.directory[i], engineDir); p = engineName;
961     } else if(p != engineName) { // derive directory from engine path, when not given
962         p[-1] = 0;
963         ASSIGN(appData.directory[i], engineName);
964         p[-1] = SLASH;
965         if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
966     } else { ASSIGN(appData.directory[i], "."); }
967     jar = (strstr(p, ".jar") == p + strlen(p) - 4);
968     if(params[0]) {
969         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
970         snprintf(command, MSG_SIZ, "%s %s", p, params);
971         p = command;
972     }
973     if(jar) { snprintf(buf3, MSG_SIZ, "java -jar %s", p); p = buf3; }
974     ASSIGN(appData.chessProgram[i], p);
975     appData.isUCI[i] = isUCI;
976     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
977     appData.hasOwnBookUCI[i] = hasBook;
978     if(!nickName[0]) useNick = FALSE;
979     if(useNick) ASSIGN(appData.pgnName[i], nickName);
980     if(addToList) {
981         int len;
982         char quote;
983         q = firstChessProgramNames;
984         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
985         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
986         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
987                         quote, p, quote, appData.directory[i],
988                         useNick ? " -fn \"" : "",
989                         useNick ? nickName : "",
990                         useNick ? "\"" : "",
991                         v1 ? " -firstProtocolVersion 1" : "",
992                         hasBook ? "" : " -fNoOwnBookUCI",
993                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
994                         storeVariant ? " -variant " : "",
995                         storeVariant ? VariantName(gameInfo.variant) : "");
996         if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
997         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
998         if(insert != q) insert[-1] = NULLCHAR;
999         snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
1000         if(q)   free(q);
1001         FloatToFront(&appData.recentEngineList, buf);
1002     }
1003     ReplaceEngine(cps, i);
1004 }
1005
1006 void
1007 InitTimeControls ()
1008 {
1009     int matched, min, sec;
1010     /*
1011      * Parse timeControl resource
1012      */
1013     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
1014                           appData.movesPerSession)) {
1015         char buf[MSG_SIZ];
1016         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
1017         DisplayFatalError(buf, 0, 2);
1018     }
1019
1020     /*
1021      * Parse searchTime resource
1022      */
1023     if (*appData.searchTime != NULLCHAR) {
1024         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
1025         if (matched == 1) {
1026             searchTime = min * 60;
1027         } else if (matched == 2) {
1028             searchTime = min * 60 + sec;
1029         } else {
1030             char buf[MSG_SIZ];
1031             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
1032             DisplayFatalError(buf, 0, 2);
1033         }
1034     }
1035 }
1036
1037 void
1038 InitBackEnd1 ()
1039 {
1040
1041     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1042     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1043
1044     GetTimeMark(&programStartTime);
1045     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1046     appData.seedBase = random() + (random()<<15);
1047     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1048
1049     ClearProgramStats();
1050     programStats.ok_to_send = 1;
1051     programStats.seen_stat = 0;
1052
1053     /*
1054      * Initialize game list
1055      */
1056     ListNew(&gameList);
1057
1058
1059     /*
1060      * Internet chess server status
1061      */
1062     if (appData.icsActive) {
1063         appData.matchMode = FALSE;
1064         appData.matchGames = 0;
1065 #if ZIPPY
1066         appData.noChessProgram = !appData.zippyPlay;
1067 #else
1068         appData.zippyPlay = FALSE;
1069         appData.zippyTalk = FALSE;
1070         appData.noChessProgram = TRUE;
1071 #endif
1072         if (*appData.icsHelper != NULLCHAR) {
1073             appData.useTelnet = TRUE;
1074             appData.telnetProgram = appData.icsHelper;
1075         }
1076     } else {
1077         appData.zippyTalk = appData.zippyPlay = FALSE;
1078     }
1079
1080     /* [AS] Initialize pv info list [HGM] and game state */
1081     {
1082         int i, j;
1083
1084         for( i=0; i<=framePtr; i++ ) {
1085             pvInfoList[i].depth = -1;
1086             boards[i][EP_STATUS] = EP_NONE;
1087             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1088         }
1089     }
1090
1091     InitTimeControls();
1092
1093     /* [AS] Adjudication threshold */
1094     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1095
1096     InitEngine(&first, 0);
1097     InitEngine(&second, 1);
1098     CommonEngineInit();
1099
1100     pairing.which = "pairing"; // pairing engine
1101     pairing.pr = NoProc;
1102     pairing.isr = NULL;
1103     pairing.program = appData.pairingEngine;
1104     pairing.host = "localhost";
1105     pairing.dir = ".";
1106
1107     if (appData.icsActive) {
1108         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1109     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1110         appData.clockMode = FALSE;
1111         first.sendTime = second.sendTime = 0;
1112     }
1113
1114 #if ZIPPY
1115     /* Override some settings from environment variables, for backward
1116        compatibility.  Unfortunately it's not feasible to have the env
1117        vars just set defaults, at least in xboard.  Ugh.
1118     */
1119     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1120       ZippyInit();
1121     }
1122 #endif
1123
1124     if (!appData.icsActive) {
1125       char buf[MSG_SIZ];
1126       int len;
1127
1128       /* Check for variants that are supported only in ICS mode,
1129          or not at all.  Some that are accepted here nevertheless
1130          have bugs; see comments below.
1131       */
1132       VariantClass variant = StringToVariant(appData.variant);
1133       switch (variant) {
1134       case VariantBughouse:     /* need four players and two boards */
1135       case VariantKriegspiel:   /* need to hide pieces and move details */
1136         /* case VariantFischeRandom: (Fabien: moved below) */
1137         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1138         if( (len >= MSG_SIZ) && appData.debugMode )
1139           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1140
1141         DisplayFatalError(buf, 0, 2);
1142         return;
1143
1144       case VariantUnknown:
1145       case VariantLoadable:
1146       case Variant29:
1147       case Variant30:
1148       case Variant31:
1149       case Variant32:
1150       case Variant33:
1151       case Variant34:
1152       case Variant35:
1153       case Variant36:
1154       default:
1155         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1156         if( (len >= MSG_SIZ) && appData.debugMode )
1157           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1158
1159         DisplayFatalError(buf, 0, 2);
1160         return;
1161
1162       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1163       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1164       case VariantGothic:     /* [HGM] should work */
1165       case VariantCapablanca: /* [HGM] should work */
1166       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1167       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1168       case VariantChu:        /* [HGM] experimental */
1169       case VariantKnightmate: /* [HGM] should work */
1170       case VariantCylinder:   /* [HGM] untested */
1171       case VariantFalcon:     /* [HGM] untested */
1172       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1173                                  offboard interposition not understood */
1174       case VariantNormal:     /* definitely works! */
1175       case VariantWildCastle: /* pieces not automatically shuffled */
1176       case VariantNoCastle:   /* pieces not automatically shuffled */
1177       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1178       case VariantLosers:     /* should work except for win condition,
1179                                  and doesn't know captures are mandatory */
1180       case VariantSuicide:    /* should work except for win condition,
1181                                  and doesn't know captures are mandatory */
1182       case VariantGiveaway:   /* should work except for win condition,
1183                                  and doesn't know captures are mandatory */
1184       case VariantTwoKings:   /* should work */
1185       case VariantAtomic:     /* should work except for win condition */
1186       case Variant3Check:     /* should work except for win condition */
1187       case VariantShatranj:   /* should work except for all win conditions */
1188       case VariantMakruk:     /* should work except for draw countdown */
1189       case VariantASEAN :     /* should work except for draw countdown */
1190       case VariantBerolina:   /* might work if TestLegality is off */
1191       case VariantCapaRandom: /* should work */
1192       case VariantJanus:      /* should work */
1193       case VariantSuper:      /* experimental */
1194       case VariantGreat:      /* experimental, requires legality testing to be off */
1195       case VariantSChess:     /* S-Chess, should work */
1196       case VariantGrand:      /* should work */
1197       case VariantSpartan:    /* should work */
1198         break;
1199       }
1200     }
1201
1202 }
1203
1204 int
1205 NextIntegerFromString (char ** str, long * value)
1206 {
1207     int result = -1;
1208     char * s = *str;
1209
1210     while( *s == ' ' || *s == '\t' ) {
1211         s++;
1212     }
1213
1214     *value = 0;
1215
1216     if( *s >= '0' && *s <= '9' ) {
1217         while( *s >= '0' && *s <= '9' ) {
1218             *value = *value * 10 + (*s - '0');
1219             s++;
1220         }
1221
1222         result = 0;
1223     }
1224
1225     *str = s;
1226
1227     return result;
1228 }
1229
1230 int
1231 NextTimeControlFromString (char ** str, long * value)
1232 {
1233     long temp;
1234     int result = NextIntegerFromString( str, &temp );
1235
1236     if( result == 0 ) {
1237         *value = temp * 60; /* Minutes */
1238         if( **str == ':' ) {
1239             (*str)++;
1240             result = NextIntegerFromString( str, &temp );
1241             *value += temp; /* Seconds */
1242         }
1243     }
1244
1245     return result;
1246 }
1247
1248 int
1249 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1250 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1251     int result = -1, type = 0; long temp, temp2;
1252
1253     if(**str != ':') return -1; // old params remain in force!
1254     (*str)++;
1255     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1256     if( NextIntegerFromString( str, &temp ) ) return -1;
1257     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1258
1259     if(**str != '/') {
1260         /* time only: incremental or sudden-death time control */
1261         if(**str == '+') { /* increment follows; read it */
1262             (*str)++;
1263             if(**str == '!') type = *(*str)++; // Bronstein TC
1264             if(result = NextIntegerFromString( str, &temp2)) return -1;
1265             *inc = temp2 * 1000;
1266             if(**str == '.') { // read fraction of increment
1267                 char *start = ++(*str);
1268                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1269                 temp2 *= 1000;
1270                 while(start++ < *str) temp2 /= 10;
1271                 *inc += temp2;
1272             }
1273         } else *inc = 0;
1274         *moves = 0; *tc = temp * 1000; *incType = type;
1275         return 0;
1276     }
1277
1278     (*str)++; /* classical time control */
1279     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1280
1281     if(result == 0) {
1282         *moves = temp;
1283         *tc    = temp2 * 1000;
1284         *inc   = 0;
1285         *incType = type;
1286     }
1287     return result;
1288 }
1289
1290 int
1291 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1292 {   /* [HGM] get time to add from the multi-session time-control string */
1293     int incType, moves=1; /* kludge to force reading of first session */
1294     long time, increment;
1295     char *s = tcString;
1296
1297     if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1298     do {
1299         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1300         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1301         if(movenr == -1) return time;    /* last move before new session     */
1302         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1303         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1304         if(!moves) return increment;     /* current session is incremental   */
1305         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1306     } while(movenr >= -1);               /* try again for next session       */
1307
1308     return 0; // no new time quota on this move
1309 }
1310
1311 int
1312 ParseTimeControl (char *tc, float ti, int mps)
1313 {
1314   long tc1;
1315   long tc2;
1316   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1317   int min, sec=0;
1318
1319   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1320   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1321       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1322   if(ti > 0) {
1323
1324     if(mps)
1325       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1326     else
1327       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1328   } else {
1329     if(mps)
1330       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1331     else
1332       snprintf(buf, MSG_SIZ, ":%s", mytc);
1333   }
1334   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1335
1336   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1337     return FALSE;
1338   }
1339
1340   if( *tc == '/' ) {
1341     /* Parse second time control */
1342     tc++;
1343
1344     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1345       return FALSE;
1346     }
1347
1348     if( tc2 == 0 ) {
1349       return FALSE;
1350     }
1351
1352     timeControl_2 = tc2 * 1000;
1353   }
1354   else {
1355     timeControl_2 = 0;
1356   }
1357
1358   if( tc1 == 0 ) {
1359     return FALSE;
1360   }
1361
1362   timeControl = tc1 * 1000;
1363
1364   if (ti >= 0) {
1365     timeIncrement = ti * 1000;  /* convert to ms */
1366     movesPerSession = 0;
1367   } else {
1368     timeIncrement = 0;
1369     movesPerSession = mps;
1370   }
1371   return TRUE;
1372 }
1373
1374 void
1375 InitBackEnd2 ()
1376 {
1377     if (appData.debugMode) {
1378 #    ifdef __GIT_VERSION
1379       fprintf(debugFP, "Version: %s (%s)\n", programVersion, __GIT_VERSION);
1380 #    else
1381       fprintf(debugFP, "Version: %s\n", programVersion);
1382 #    endif
1383     }
1384     ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1385
1386     set_cont_sequence(appData.wrapContSeq);
1387     if (appData.matchGames > 0) {
1388         appData.matchMode = TRUE;
1389     } else if (appData.matchMode) {
1390         appData.matchGames = 1;
1391     }
1392     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1393         appData.matchGames = appData.sameColorGames;
1394     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1395         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1396         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1397     }
1398     Reset(TRUE, FALSE);
1399     if (appData.noChessProgram || first.protocolVersion == 1) {
1400       InitBackEnd3();
1401     } else {
1402       /* kludge: allow timeout for initial "feature" commands */
1403       FreezeUI();
1404       DisplayMessage("", _("Starting chess program"));
1405       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1406     }
1407 }
1408
1409 int
1410 CalculateIndex (int index, int gameNr)
1411 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1412     int res;
1413     if(index > 0) return index; // fixed nmber
1414     if(index == 0) return 1;
1415     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1416     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1417     return res;
1418 }
1419
1420 int
1421 LoadGameOrPosition (int gameNr)
1422 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1423     if (*appData.loadGameFile != NULLCHAR) {
1424         if (!LoadGameFromFile(appData.loadGameFile,
1425                 CalculateIndex(appData.loadGameIndex, gameNr),
1426                               appData.loadGameFile, FALSE)) {
1427             DisplayFatalError(_("Bad game file"), 0, 1);
1428             return 0;
1429         }
1430     } else if (*appData.loadPositionFile != NULLCHAR) {
1431         if (!LoadPositionFromFile(appData.loadPositionFile,
1432                 CalculateIndex(appData.loadPositionIndex, gameNr),
1433                                   appData.loadPositionFile)) {
1434             DisplayFatalError(_("Bad position file"), 0, 1);
1435             return 0;
1436         }
1437     }
1438     return 1;
1439 }
1440
1441 void
1442 ReserveGame (int gameNr, char resChar)
1443 {
1444     FILE *tf = fopen(appData.tourneyFile, "r+");
1445     char *p, *q, c, buf[MSG_SIZ];
1446     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1447     safeStrCpy(buf, lastMsg, MSG_SIZ);
1448     DisplayMessage(_("Pick new game"), "");
1449     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1450     ParseArgsFromFile(tf);
1451     p = q = appData.results;
1452     if(appData.debugMode) {
1453       char *r = appData.participants;
1454       fprintf(debugFP, "results = '%s'\n", p);
1455       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1456       fprintf(debugFP, "\n");
1457     }
1458     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1459     nextGame = q - p;
1460     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1461     safeStrCpy(q, p, strlen(p) + 2);
1462     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1463     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1464     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1465         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1466         q[nextGame] = '*';
1467     }
1468     fseek(tf, -(strlen(p)+4), SEEK_END);
1469     c = fgetc(tf);
1470     if(c != '"') // depending on DOS or Unix line endings we can be one off
1471          fseek(tf, -(strlen(p)+2), SEEK_END);
1472     else fseek(tf, -(strlen(p)+3), SEEK_END);
1473     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1474     DisplayMessage(buf, "");
1475     free(p); appData.results = q;
1476     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1477        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1478       int round = appData.defaultMatchGames * appData.tourneyType;
1479       if(gameNr < 0 || appData.tourneyType < 1 ||  // gauntlet engine can always stay loaded as first engine
1480          appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1481         UnloadEngine(&first);  // next game belongs to other pairing;
1482         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1483     }
1484     if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1485 }
1486
1487 void
1488 MatchEvent (int mode)
1489 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1490         int dummy;
1491         if(matchMode) { // already in match mode: switch it off
1492             abortMatch = TRUE;
1493             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1494             return;
1495         }
1496 //      if(gameMode != BeginningOfGame) {
1497 //          DisplayError(_("You can only start a match from the initial position."), 0);
1498 //          return;
1499 //      }
1500         abortMatch = FALSE;
1501         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1502         /* Set up machine vs. machine match */
1503         nextGame = 0;
1504         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1505         if(appData.tourneyFile[0]) {
1506             ReserveGame(-1, 0);
1507             if(nextGame > appData.matchGames) {
1508                 char buf[MSG_SIZ];
1509                 if(strchr(appData.results, '*') == NULL) {
1510                     FILE *f;
1511                     appData.tourneyCycles++;
1512                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1513                         fclose(f);
1514                         NextTourneyGame(-1, &dummy);
1515                         ReserveGame(-1, 0);
1516                         if(nextGame <= appData.matchGames) {
1517                             DisplayNote(_("You restarted an already completed tourney.\nOne more cycle will now be added to it.\nGames commence in 10 sec."));
1518                             matchMode = mode;
1519                             ScheduleDelayedEvent(NextMatchGame, 10000);
1520                             return;
1521                         }
1522                     }
1523                 }
1524                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1525                 DisplayError(buf, 0);
1526                 appData.tourneyFile[0] = 0;
1527                 return;
1528             }
1529         } else
1530         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1531             DisplayFatalError(_("Can't have a match with no chess programs"),
1532                               0, 2);
1533             return;
1534         }
1535         matchMode = mode;
1536         matchGame = roundNr = 1;
1537         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1538         NextMatchGame();
1539 }
1540
1541 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1542
1543 void
1544 InitBackEnd3 P((void))
1545 {
1546     GameMode initialMode;
1547     char buf[MSG_SIZ];
1548     int err, len;
1549
1550     InitChessProgram(&first, startedFromSetupPosition);
1551
1552     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1553         free(programVersion);
1554         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1555         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1556         FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1557     }
1558
1559     if (appData.icsActive) {
1560 #ifdef WIN32
1561         /* [DM] Make a console window if needed [HGM] merged ifs */
1562         ConsoleCreate();
1563 #endif
1564         err = establish();
1565         if (err != 0)
1566           {
1567             if (*appData.icsCommPort != NULLCHAR)
1568               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1569                              appData.icsCommPort);
1570             else
1571               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1572                         appData.icsHost, appData.icsPort);
1573
1574             if( (len >= MSG_SIZ) && appData.debugMode )
1575               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1576
1577             DisplayFatalError(buf, err, 1);
1578             return;
1579         }
1580         SetICSMode();
1581         telnetISR =
1582           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1583         fromUserISR =
1584           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1585         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1586             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1587     } else if (appData.noChessProgram) {
1588         SetNCPMode();
1589     } else {
1590         SetGNUMode();
1591     }
1592
1593     if (*appData.cmailGameName != NULLCHAR) {
1594         SetCmailMode();
1595         OpenLoopback(&cmailPR);
1596         cmailISR =
1597           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1598     }
1599
1600     ThawUI();
1601     DisplayMessage("", "");
1602     if (StrCaseCmp(appData.initialMode, "") == 0) {
1603       initialMode = BeginningOfGame;
1604       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1605         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1606         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1607         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1608         ModeHighlight();
1609       }
1610     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1611       initialMode = TwoMachinesPlay;
1612     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1613       initialMode = AnalyzeFile;
1614     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1615       initialMode = AnalyzeMode;
1616     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1617       initialMode = MachinePlaysWhite;
1618     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1619       initialMode = MachinePlaysBlack;
1620     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1621       initialMode = EditGame;
1622     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1623       initialMode = EditPosition;
1624     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1625       initialMode = Training;
1626     } else {
1627       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1628       if( (len >= MSG_SIZ) && appData.debugMode )
1629         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1630
1631       DisplayFatalError(buf, 0, 2);
1632       return;
1633     }
1634
1635     if (appData.matchMode) {
1636         if(appData.tourneyFile[0]) { // start tourney from command line
1637             FILE *f;
1638             if(f = fopen(appData.tourneyFile, "r")) {
1639                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1640                 fclose(f);
1641                 appData.clockMode = TRUE;
1642                 SetGNUMode();
1643             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1644         }
1645         MatchEvent(TRUE);
1646     } else if (*appData.cmailGameName != NULLCHAR) {
1647         /* Set up cmail mode */
1648         ReloadCmailMsgEvent(TRUE);
1649     } else {
1650         /* Set up other modes */
1651         if (initialMode == AnalyzeFile) {
1652           if (*appData.loadGameFile == NULLCHAR) {
1653             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1654             return;
1655           }
1656         }
1657         if (*appData.loadGameFile != NULLCHAR) {
1658             (void) LoadGameFromFile(appData.loadGameFile,
1659                                     appData.loadGameIndex,
1660                                     appData.loadGameFile, TRUE);
1661         } else if (*appData.loadPositionFile != NULLCHAR) {
1662             (void) LoadPositionFromFile(appData.loadPositionFile,
1663                                         appData.loadPositionIndex,
1664                                         appData.loadPositionFile);
1665             /* [HGM] try to make self-starting even after FEN load */
1666             /* to allow automatic setup of fairy variants with wtm */
1667             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1668                 gameMode = BeginningOfGame;
1669                 setboardSpoiledMachineBlack = 1;
1670             }
1671             /* [HGM] loadPos: make that every new game uses the setup */
1672             /* from file as long as we do not switch variant          */
1673             if(!blackPlaysFirst) {
1674                 startedFromPositionFile = TRUE;
1675                 CopyBoard(filePosition, boards[0]);
1676             }
1677         }
1678         if (initialMode == AnalyzeMode) {
1679           if (appData.noChessProgram) {
1680             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1681             return;
1682           }
1683           if (appData.icsActive) {
1684             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1685             return;
1686           }
1687           AnalyzeModeEvent();
1688         } else if (initialMode == AnalyzeFile) {
1689           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1690           ShowThinkingEvent();
1691           AnalyzeFileEvent();
1692           AnalysisPeriodicEvent(1);
1693         } else if (initialMode == MachinePlaysWhite) {
1694           if (appData.noChessProgram) {
1695             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1696                               0, 2);
1697             return;
1698           }
1699           if (appData.icsActive) {
1700             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1701                               0, 2);
1702             return;
1703           }
1704           MachineWhiteEvent();
1705         } else if (initialMode == MachinePlaysBlack) {
1706           if (appData.noChessProgram) {
1707             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1708                               0, 2);
1709             return;
1710           }
1711           if (appData.icsActive) {
1712             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1713                               0, 2);
1714             return;
1715           }
1716           MachineBlackEvent();
1717         } else if (initialMode == TwoMachinesPlay) {
1718           if (appData.noChessProgram) {
1719             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1720                               0, 2);
1721             return;
1722           }
1723           if (appData.icsActive) {
1724             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1725                               0, 2);
1726             return;
1727           }
1728           TwoMachinesEvent();
1729         } else if (initialMode == EditGame) {
1730           EditGameEvent();
1731         } else if (initialMode == EditPosition) {
1732           EditPositionEvent();
1733         } else if (initialMode == Training) {
1734           if (*appData.loadGameFile == NULLCHAR) {
1735             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1736             return;
1737           }
1738           TrainingEvent();
1739         }
1740     }
1741 }
1742
1743 void
1744 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1745 {
1746     DisplayBook(current+1);
1747
1748     MoveHistorySet( movelist, first, last, current, pvInfoList );
1749
1750     EvalGraphSet( first, last, current, pvInfoList );
1751
1752     MakeEngineOutputTitle();
1753 }
1754
1755 /*
1756  * Establish will establish a contact to a remote host.port.
1757  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1758  *  used to talk to the host.
1759  * Returns 0 if okay, error code if not.
1760  */
1761 int
1762 establish ()
1763 {
1764     char buf[MSG_SIZ];
1765
1766     if (*appData.icsCommPort != NULLCHAR) {
1767         /* Talk to the host through a serial comm port */
1768         return OpenCommPort(appData.icsCommPort, &icsPR);
1769
1770     } else if (*appData.gateway != NULLCHAR) {
1771         if (*appData.remoteShell == NULLCHAR) {
1772             /* Use the rcmd protocol to run telnet program on a gateway host */
1773             snprintf(buf, sizeof(buf), "%s %s %s",
1774                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1775             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1776
1777         } else {
1778             /* Use the rsh program to run telnet program on a gateway host */
1779             if (*appData.remoteUser == NULLCHAR) {
1780                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1781                         appData.gateway, appData.telnetProgram,
1782                         appData.icsHost, appData.icsPort);
1783             } else {
1784                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1785                         appData.remoteShell, appData.gateway,
1786                         appData.remoteUser, appData.telnetProgram,
1787                         appData.icsHost, appData.icsPort);
1788             }
1789             return StartChildProcess(buf, "", &icsPR);
1790
1791         }
1792     } else if (appData.useTelnet) {
1793         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1794
1795     } else {
1796         /* TCP socket interface differs somewhat between
1797            Unix and NT; handle details in the front end.
1798            */
1799         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1800     }
1801 }
1802
1803 void
1804 EscapeExpand (char *p, char *q)
1805 {       // [HGM] initstring: routine to shape up string arguments
1806         while(*p++ = *q++) if(p[-1] == '\\')
1807             switch(*q++) {
1808                 case 'n': p[-1] = '\n'; break;
1809                 case 'r': p[-1] = '\r'; break;
1810                 case 't': p[-1] = '\t'; break;
1811                 case '\\': p[-1] = '\\'; break;
1812                 case 0: *p = 0; return;
1813                 default: p[-1] = q[-1]; break;
1814             }
1815 }
1816
1817 void
1818 show_bytes (FILE *fp, char *buf, int count)
1819 {
1820     while (count--) {
1821         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1822             fprintf(fp, "\\%03o", *buf & 0xff);
1823         } else {
1824             putc(*buf, fp);
1825         }
1826         buf++;
1827     }
1828     fflush(fp);
1829 }
1830
1831 /* Returns an errno value */
1832 int
1833 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1834 {
1835     char buf[8192], *p, *q, *buflim;
1836     int left, newcount, outcount;
1837
1838     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1839         *appData.gateway != NULLCHAR) {
1840         if (appData.debugMode) {
1841             fprintf(debugFP, ">ICS: ");
1842             show_bytes(debugFP, message, count);
1843             fprintf(debugFP, "\n");
1844         }
1845         return OutputToProcess(pr, message, count, outError);
1846     }
1847
1848     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1849     p = message;
1850     q = buf;
1851     left = count;
1852     newcount = 0;
1853     while (left) {
1854         if (q >= buflim) {
1855             if (appData.debugMode) {
1856                 fprintf(debugFP, ">ICS: ");
1857                 show_bytes(debugFP, buf, newcount);
1858                 fprintf(debugFP, "\n");
1859             }
1860             outcount = OutputToProcess(pr, buf, newcount, outError);
1861             if (outcount < newcount) return -1; /* to be sure */
1862             q = buf;
1863             newcount = 0;
1864         }
1865         if (*p == '\n') {
1866             *q++ = '\r';
1867             newcount++;
1868         } else if (((unsigned char) *p) == TN_IAC) {
1869             *q++ = (char) TN_IAC;
1870             newcount ++;
1871         }
1872         *q++ = *p++;
1873         newcount++;
1874         left--;
1875     }
1876     if (appData.debugMode) {
1877         fprintf(debugFP, ">ICS: ");
1878         show_bytes(debugFP, buf, newcount);
1879         fprintf(debugFP, "\n");
1880     }
1881     outcount = OutputToProcess(pr, buf, newcount, outError);
1882     if (outcount < newcount) return -1; /* to be sure */
1883     return count;
1884 }
1885
1886 void
1887 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1888 {
1889     int outError, outCount;
1890     static int gotEof = 0;
1891     static FILE *ini;
1892
1893     /* Pass data read from player on to ICS */
1894     if (count > 0) {
1895         gotEof = 0;
1896         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1897         if (outCount < count) {
1898             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1899         }
1900         if(have_sent_ICS_logon == 2) {
1901           if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1902             fprintf(ini, "%s", message);
1903             have_sent_ICS_logon = 3;
1904           } else
1905             have_sent_ICS_logon = 1;
1906         } else if(have_sent_ICS_logon == 3) {
1907             fprintf(ini, "%s", message);
1908             fclose(ini);
1909           have_sent_ICS_logon = 1;
1910         }
1911     } else if (count < 0) {
1912         RemoveInputSource(isr);
1913         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1914     } else if (gotEof++ > 0) {
1915         RemoveInputSource(isr);
1916         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1917     }
1918 }
1919
1920 void
1921 KeepAlive ()
1922 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1923     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1924     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1925     SendToICS("date\n");
1926     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1927 }
1928
1929 /* added routine for printf style output to ics */
1930 void
1931 ics_printf (char *format, ...)
1932 {
1933     char buffer[MSG_SIZ];
1934     va_list args;
1935
1936     va_start(args, format);
1937     vsnprintf(buffer, sizeof(buffer), format, args);
1938     buffer[sizeof(buffer)-1] = '\0';
1939     SendToICS(buffer);
1940     va_end(args);
1941 }
1942
1943 void
1944 SendToICS (char *s)
1945 {
1946     int count, outCount, outError;
1947
1948     if (icsPR == NoProc) return;
1949
1950     count = strlen(s);
1951     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1952     if (outCount < count) {
1953         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1954     }
1955 }
1956
1957 /* This is used for sending logon scripts to the ICS. Sending
1958    without a delay causes problems when using timestamp on ICC
1959    (at least on my machine). */
1960 void
1961 SendToICSDelayed (char *s, long msdelay)
1962 {
1963     int count, outCount, outError;
1964
1965     if (icsPR == NoProc) return;
1966
1967     count = strlen(s);
1968     if (appData.debugMode) {
1969         fprintf(debugFP, ">ICS: ");
1970         show_bytes(debugFP, s, count);
1971         fprintf(debugFP, "\n");
1972     }
1973     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1974                                       msdelay);
1975     if (outCount < count) {
1976         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1977     }
1978 }
1979
1980
1981 /* Remove all highlighting escape sequences in s
1982    Also deletes any suffix starting with '('
1983    */
1984 char *
1985 StripHighlightAndTitle (char *s)
1986 {
1987     static char retbuf[MSG_SIZ];
1988     char *p = retbuf;
1989
1990     while (*s != NULLCHAR) {
1991         while (*s == '\033') {
1992             while (*s != NULLCHAR && !isalpha(*s)) s++;
1993             if (*s != NULLCHAR) s++;
1994         }
1995         while (*s != NULLCHAR && *s != '\033') {
1996             if (*s == '(' || *s == '[') {
1997                 *p = NULLCHAR;
1998                 return retbuf;
1999             }
2000             *p++ = *s++;
2001         }
2002     }
2003     *p = NULLCHAR;
2004     return retbuf;
2005 }
2006
2007 /* Remove all highlighting escape sequences in s */
2008 char *
2009 StripHighlight (char *s)
2010 {
2011     static char retbuf[MSG_SIZ];
2012     char *p = retbuf;
2013
2014     while (*s != NULLCHAR) {
2015         while (*s == '\033') {
2016             while (*s != NULLCHAR && !isalpha(*s)) s++;
2017             if (*s != NULLCHAR) s++;
2018         }
2019         while (*s != NULLCHAR && *s != '\033') {
2020             *p++ = *s++;
2021         }
2022     }
2023     *p = NULLCHAR;
2024     return retbuf;
2025 }
2026
2027 char engineVariant[MSG_SIZ];
2028 char *variantNames[] = VARIANT_NAMES;
2029 char *
2030 VariantName (VariantClass v)
2031 {
2032     if(v == VariantUnknown || *engineVariant) return engineVariant;
2033     return variantNames[v];
2034 }
2035
2036
2037 /* Identify a variant from the strings the chess servers use or the
2038    PGN Variant tag names we use. */
2039 VariantClass
2040 StringToVariant (char *e)
2041 {
2042     char *p;
2043     int wnum = -1;
2044     VariantClass v = VariantNormal;
2045     int i, found = FALSE;
2046     char buf[MSG_SIZ];
2047     int len;
2048
2049     if (!e) return v;
2050
2051     /* [HGM] skip over optional board-size prefixes */
2052     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
2053         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
2054         while( *e++ != '_');
2055     }
2056
2057     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2058         v = VariantNormal;
2059         found = TRUE;
2060     } else
2061     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2062       if (StrCaseStr(e, variantNames[i])) {
2063         v = (VariantClass) i;
2064         found = TRUE;
2065         break;
2066       }
2067     }
2068
2069     if (!found) {
2070       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2071           || StrCaseStr(e, "wild/fr")
2072           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2073         v = VariantFischeRandom;
2074       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2075                  (i = 1, p = StrCaseStr(e, "w"))) {
2076         p += i;
2077         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2078         if (isdigit(*p)) {
2079           wnum = atoi(p);
2080         } else {
2081           wnum = -1;
2082         }
2083         switch (wnum) {
2084         case 0: /* FICS only, actually */
2085         case 1:
2086           /* Castling legal even if K starts on d-file */
2087           v = VariantWildCastle;
2088           break;
2089         case 2:
2090         case 3:
2091         case 4:
2092           /* Castling illegal even if K & R happen to start in
2093              normal positions. */
2094           v = VariantNoCastle;
2095           break;
2096         case 5:
2097         case 7:
2098         case 8:
2099         case 10:
2100         case 11:
2101         case 12:
2102         case 13:
2103         case 14:
2104         case 15:
2105         case 18:
2106         case 19:
2107           /* Castling legal iff K & R start in normal positions */
2108           v = VariantNormal;
2109           break;
2110         case 6:
2111         case 20:
2112         case 21:
2113           /* Special wilds for position setup; unclear what to do here */
2114           v = VariantLoadable;
2115           break;
2116         case 9:
2117           /* Bizarre ICC game */
2118           v = VariantTwoKings;
2119           break;
2120         case 16:
2121           v = VariantKriegspiel;
2122           break;
2123         case 17:
2124           v = VariantLosers;
2125           break;
2126         case 22:
2127           v = VariantFischeRandom;
2128           break;
2129         case 23:
2130           v = VariantCrazyhouse;
2131           break;
2132         case 24:
2133           v = VariantBughouse;
2134           break;
2135         case 25:
2136           v = Variant3Check;
2137           break;
2138         case 26:
2139           /* Not quite the same as FICS suicide! */
2140           v = VariantGiveaway;
2141           break;
2142         case 27:
2143           v = VariantAtomic;
2144           break;
2145         case 28:
2146           v = VariantShatranj;
2147           break;
2148
2149         /* Temporary names for future ICC types.  The name *will* change in
2150            the next xboard/WinBoard release after ICC defines it. */
2151         case 29:
2152           v = Variant29;
2153           break;
2154         case 30:
2155           v = Variant30;
2156           break;
2157         case 31:
2158           v = Variant31;
2159           break;
2160         case 32:
2161           v = Variant32;
2162           break;
2163         case 33:
2164           v = Variant33;
2165           break;
2166         case 34:
2167           v = Variant34;
2168           break;
2169         case 35:
2170           v = Variant35;
2171           break;
2172         case 36:
2173           v = Variant36;
2174           break;
2175         case 37:
2176           v = VariantShogi;
2177           break;
2178         case 38:
2179           v = VariantXiangqi;
2180           break;
2181         case 39:
2182           v = VariantCourier;
2183           break;
2184         case 40:
2185           v = VariantGothic;
2186           break;
2187         case 41:
2188           v = VariantCapablanca;
2189           break;
2190         case 42:
2191           v = VariantKnightmate;
2192           break;
2193         case 43:
2194           v = VariantFairy;
2195           break;
2196         case 44:
2197           v = VariantCylinder;
2198           break;
2199         case 45:
2200           v = VariantFalcon;
2201           break;
2202         case 46:
2203           v = VariantCapaRandom;
2204           break;
2205         case 47:
2206           v = VariantBerolina;
2207           break;
2208         case 48:
2209           v = VariantJanus;
2210           break;
2211         case 49:
2212           v = VariantSuper;
2213           break;
2214         case 50:
2215           v = VariantGreat;
2216           break;
2217         case -1:
2218           /* Found "wild" or "w" in the string but no number;
2219              must assume it's normal chess. */
2220           v = VariantNormal;
2221           break;
2222         default:
2223           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2224           if( (len >= MSG_SIZ) && appData.debugMode )
2225             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2226
2227           DisplayError(buf, 0);
2228           v = VariantUnknown;
2229           break;
2230         }
2231       }
2232     }
2233     if (appData.debugMode) {
2234       fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
2235               e, wnum, VariantName(v));
2236     }
2237     return v;
2238 }
2239
2240 static int leftover_start = 0, leftover_len = 0;
2241 char star_match[STAR_MATCH_N][MSG_SIZ];
2242
2243 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2244    advance *index beyond it, and set leftover_start to the new value of
2245    *index; else return FALSE.  If pattern contains the character '*', it
2246    matches any sequence of characters not containing '\r', '\n', or the
2247    character following the '*' (if any), and the matched sequence(s) are
2248    copied into star_match.
2249    */
2250 int
2251 looking_at ( char *buf, int *index, char *pattern)
2252 {
2253     char *bufp = &buf[*index], *patternp = pattern;
2254     int star_count = 0;
2255     char *matchp = star_match[0];
2256
2257     for (;;) {
2258         if (*patternp == NULLCHAR) {
2259             *index = leftover_start = bufp - buf;
2260             *matchp = NULLCHAR;
2261             return TRUE;
2262         }
2263         if (*bufp == NULLCHAR) return FALSE;
2264         if (*patternp == '*') {
2265             if (*bufp == *(patternp + 1)) {
2266                 *matchp = NULLCHAR;
2267                 matchp = star_match[++star_count];
2268                 patternp += 2;
2269                 bufp++;
2270                 continue;
2271             } else if (*bufp == '\n' || *bufp == '\r') {
2272                 patternp++;
2273                 if (*patternp == NULLCHAR)
2274                   continue;
2275                 else
2276                   return FALSE;
2277             } else {
2278                 *matchp++ = *bufp++;
2279                 continue;
2280             }
2281         }
2282         if (*patternp != *bufp) return FALSE;
2283         patternp++;
2284         bufp++;
2285     }
2286 }
2287
2288 void
2289 SendToPlayer (char *data, int length)
2290 {
2291     int error, outCount;
2292     outCount = OutputToProcess(NoProc, data, length, &error);
2293     if (outCount < length) {
2294         DisplayFatalError(_("Error writing to display"), error, 1);
2295     }
2296 }
2297
2298 void
2299 PackHolding (char packed[], char *holding)
2300 {
2301     char *p = holding;
2302     char *q = packed;
2303     int runlength = 0;
2304     int curr = 9999;
2305     do {
2306         if (*p == curr) {
2307             runlength++;
2308         } else {
2309             switch (runlength) {
2310               case 0:
2311                 break;
2312               case 1:
2313                 *q++ = curr;
2314                 break;
2315               case 2:
2316                 *q++ = curr;
2317                 *q++ = curr;
2318                 break;
2319               default:
2320                 sprintf(q, "%d", runlength);
2321                 while (*q) q++;
2322                 *q++ = curr;
2323                 break;
2324             }
2325             runlength = 1;
2326             curr = *p;
2327         }
2328     } while (*p++);
2329     *q = NULLCHAR;
2330 }
2331
2332 /* Telnet protocol requests from the front end */
2333 void
2334 TelnetRequest (unsigned char ddww, unsigned char option)
2335 {
2336     unsigned char msg[3];
2337     int outCount, outError;
2338
2339     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2340
2341     if (appData.debugMode) {
2342         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2343         switch (ddww) {
2344           case TN_DO:
2345             ddwwStr = "DO";
2346             break;
2347           case TN_DONT:
2348             ddwwStr = "DONT";
2349             break;
2350           case TN_WILL:
2351             ddwwStr = "WILL";
2352             break;
2353           case TN_WONT:
2354             ddwwStr = "WONT";
2355             break;
2356           default:
2357             ddwwStr = buf1;
2358             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2359             break;
2360         }
2361         switch (option) {
2362           case TN_ECHO:
2363             optionStr = "ECHO";
2364             break;
2365           default:
2366             optionStr = buf2;
2367             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2368             break;
2369         }
2370         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2371     }
2372     msg[0] = TN_IAC;
2373     msg[1] = ddww;
2374     msg[2] = option;
2375     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2376     if (outCount < 3) {
2377         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2378     }
2379 }
2380
2381 void
2382 DoEcho ()
2383 {
2384     if (!appData.icsActive) return;
2385     TelnetRequest(TN_DO, TN_ECHO);
2386 }
2387
2388 void
2389 DontEcho ()
2390 {
2391     if (!appData.icsActive) return;
2392     TelnetRequest(TN_DONT, TN_ECHO);
2393 }
2394
2395 void
2396 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2397 {
2398     /* put the holdings sent to us by the server on the board holdings area */
2399     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2400     char p;
2401     ChessSquare piece;
2402
2403     if(gameInfo.holdingsWidth < 2)  return;
2404     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2405         return; // prevent overwriting by pre-board holdings
2406
2407     if( (int)lowestPiece >= BlackPawn ) {
2408         holdingsColumn = 0;
2409         countsColumn = 1;
2410         holdingsStartRow = BOARD_HEIGHT-1;
2411         direction = -1;
2412     } else {
2413         holdingsColumn = BOARD_WIDTH-1;
2414         countsColumn = BOARD_WIDTH-2;
2415         holdingsStartRow = 0;
2416         direction = 1;
2417     }
2418
2419     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2420         board[i][holdingsColumn] = EmptySquare;
2421         board[i][countsColumn]   = (ChessSquare) 0;
2422     }
2423     while( (p=*holdings++) != NULLCHAR ) {
2424         piece = CharToPiece( ToUpper(p) );
2425         if(piece == EmptySquare) continue;
2426         /*j = (int) piece - (int) WhitePawn;*/
2427         j = PieceToNumber(piece);
2428         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2429         if(j < 0) continue;               /* should not happen */
2430         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2431         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2432         board[holdingsStartRow+j*direction][countsColumn]++;
2433     }
2434 }
2435
2436
2437 void
2438 VariantSwitch (Board board, VariantClass newVariant)
2439 {
2440    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2441    static Board oldBoard;
2442
2443    startedFromPositionFile = FALSE;
2444    if(gameInfo.variant == newVariant) return;
2445
2446    /* [HGM] This routine is called each time an assignment is made to
2447     * gameInfo.variant during a game, to make sure the board sizes
2448     * are set to match the new variant. If that means adding or deleting
2449     * holdings, we shift the playing board accordingly
2450     * This kludge is needed because in ICS observe mode, we get boards
2451     * of an ongoing game without knowing the variant, and learn about the
2452     * latter only later. This can be because of the move list we requested,
2453     * in which case the game history is refilled from the beginning anyway,
2454     * but also when receiving holdings of a crazyhouse game. In the latter
2455     * case we want to add those holdings to the already received position.
2456     */
2457
2458
2459    if (appData.debugMode) {
2460      fprintf(debugFP, "Switch board from %s to %s\n",
2461              VariantName(gameInfo.variant), VariantName(newVariant));
2462      setbuf(debugFP, NULL);
2463    }
2464    shuffleOpenings = 0;       /* [HGM] shuffle */
2465    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2466    switch(newVariant)
2467      {
2468      case VariantShogi:
2469        newWidth = 9;  newHeight = 9;
2470        gameInfo.holdingsSize = 7;
2471      case VariantBughouse:
2472      case VariantCrazyhouse:
2473        newHoldingsWidth = 2; break;
2474      case VariantGreat:
2475        newWidth = 10;
2476      case VariantSuper:
2477        newHoldingsWidth = 2;
2478        gameInfo.holdingsSize = 8;
2479        break;
2480      case VariantGothic:
2481      case VariantCapablanca:
2482      case VariantCapaRandom:
2483        newWidth = 10;
2484      default:
2485        newHoldingsWidth = gameInfo.holdingsSize = 0;
2486      };
2487
2488    if(newWidth  != gameInfo.boardWidth  ||
2489       newHeight != gameInfo.boardHeight ||
2490       newHoldingsWidth != gameInfo.holdingsWidth ) {
2491
2492      /* shift position to new playing area, if needed */
2493      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2494        for(i=0; i<BOARD_HEIGHT; i++)
2495          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2496            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2497              board[i][j];
2498        for(i=0; i<newHeight; i++) {
2499          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2500          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2501        }
2502      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2503        for(i=0; i<BOARD_HEIGHT; i++)
2504          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2505            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2506              board[i][j];
2507      }
2508      board[HOLDINGS_SET] = 0;
2509      gameInfo.boardWidth  = newWidth;
2510      gameInfo.boardHeight = newHeight;
2511      gameInfo.holdingsWidth = newHoldingsWidth;
2512      gameInfo.variant = newVariant;
2513      InitDrawingSizes(-2, 0);
2514    } else gameInfo.variant = newVariant;
2515    CopyBoard(oldBoard, board);   // remember correctly formatted board
2516      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2517    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2518 }
2519
2520 static int loggedOn = FALSE;
2521
2522 /*-- Game start info cache: --*/
2523 int gs_gamenum;
2524 char gs_kind[MSG_SIZ];
2525 static char player1Name[128] = "";
2526 static char player2Name[128] = "";
2527 static char cont_seq[] = "\n\\   ";
2528 static int player1Rating = -1;
2529 static int player2Rating = -1;
2530 /*----------------------------*/
2531
2532 ColorClass curColor = ColorNormal;
2533 int suppressKibitz = 0;
2534
2535 // [HGM] seekgraph
2536 Boolean soughtPending = FALSE;
2537 Boolean seekGraphUp;
2538 #define MAX_SEEK_ADS 200
2539 #define SQUARE 0x80
2540 char *seekAdList[MAX_SEEK_ADS];
2541 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2542 float tcList[MAX_SEEK_ADS];
2543 char colorList[MAX_SEEK_ADS];
2544 int nrOfSeekAds = 0;
2545 int minRating = 1010, maxRating = 2800;
2546 int hMargin = 10, vMargin = 20, h, w;
2547 extern int squareSize, lineGap;
2548
2549 void
2550 PlotSeekAd (int i)
2551 {
2552         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2553         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2554         if(r < minRating+100 && r >=0 ) r = minRating+100;
2555         if(r > maxRating) r = maxRating;
2556         if(tc < 1.f) tc = 1.f;
2557         if(tc > 95.f) tc = 95.f;
2558         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2559         y = ((double)r - minRating)/(maxRating - minRating)
2560             * (h-vMargin-squareSize/8-1) + vMargin;
2561         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2562         if(strstr(seekAdList[i], " u ")) color = 1;
2563         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2564            !strstr(seekAdList[i], "bullet") &&
2565            !strstr(seekAdList[i], "blitz") &&
2566            !strstr(seekAdList[i], "standard") ) color = 2;
2567         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2568         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2569 }
2570
2571 void
2572 PlotSingleSeekAd (int i)
2573 {
2574         PlotSeekAd(i);
2575 }
2576
2577 void
2578 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2579 {
2580         char buf[MSG_SIZ], *ext = "";
2581         VariantClass v = StringToVariant(type);
2582         if(strstr(type, "wild")) {
2583             ext = type + 4; // append wild number
2584             if(v == VariantFischeRandom) type = "chess960"; else
2585             if(v == VariantLoadable) type = "setup"; else
2586             type = VariantName(v);
2587         }
2588         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2589         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2590             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2591             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2592             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2593             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2594             seekNrList[nrOfSeekAds] = nr;
2595             zList[nrOfSeekAds] = 0;
2596             seekAdList[nrOfSeekAds++] = StrSave(buf);
2597             if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2598         }
2599 }
2600
2601 void
2602 EraseSeekDot (int i)
2603 {
2604     int x = xList[i], y = yList[i], d=squareSize/4, k;
2605     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2606     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2607     // now replot every dot that overlapped
2608     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2609         int xx = xList[k], yy = yList[k];
2610         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2611             DrawSeekDot(xx, yy, colorList[k]);
2612     }
2613 }
2614
2615 void
2616 RemoveSeekAd (int nr)
2617 {
2618         int i;
2619         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2620             EraseSeekDot(i);
2621             if(seekAdList[i]) free(seekAdList[i]);
2622             seekAdList[i] = seekAdList[--nrOfSeekAds];
2623             seekNrList[i] = seekNrList[nrOfSeekAds];
2624             ratingList[i] = ratingList[nrOfSeekAds];
2625             colorList[i]  = colorList[nrOfSeekAds];
2626             tcList[i] = tcList[nrOfSeekAds];
2627             xList[i]  = xList[nrOfSeekAds];
2628             yList[i]  = yList[nrOfSeekAds];
2629             zList[i]  = zList[nrOfSeekAds];
2630             seekAdList[nrOfSeekAds] = NULL;
2631             break;
2632         }
2633 }
2634
2635 Boolean
2636 MatchSoughtLine (char *line)
2637 {
2638     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2639     int nr, base, inc, u=0; char dummy;
2640
2641     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2642        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2643        (u=1) &&
2644        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2645         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2646         // match: compact and save the line
2647         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2648         return TRUE;
2649     }
2650     return FALSE;
2651 }
2652
2653 int
2654 DrawSeekGraph ()
2655 {
2656     int i;
2657     if(!seekGraphUp) return FALSE;
2658     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2659     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2660
2661     DrawSeekBackground(0, 0, w, h);
2662     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2663     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2664     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2665         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2666         yy = h-1-yy;
2667         DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2668         if(i%500 == 0) {
2669             char buf[MSG_SIZ];
2670             snprintf(buf, MSG_SIZ, "%d", i);
2671             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2672         }
2673     }
2674     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2675     for(i=1; i<100; i+=(i<10?1:5)) {
2676         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2677         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2678         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2679             char buf[MSG_SIZ];
2680             snprintf(buf, MSG_SIZ, "%d", i);
2681             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2682         }
2683     }
2684     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2685     return TRUE;
2686 }
2687
2688 int
2689 SeekGraphClick (ClickType click, int x, int y, int moving)
2690 {
2691     static int lastDown = 0, displayed = 0, lastSecond;
2692     if(y < 0) return FALSE;
2693     if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2694         (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2695         if(!seekGraphUp) return FALSE;
2696         seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2697         DrawPosition(TRUE, NULL);
2698         return TRUE;
2699     }
2700     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2701         if(click == Release || moving) return FALSE;
2702         nrOfSeekAds = 0;
2703         soughtPending = TRUE;
2704         SendToICS(ics_prefix);
2705         SendToICS("sought\n"); // should this be "sought all"?
2706     } else { // issue challenge based on clicked ad
2707         int dist = 10000; int i, closest = 0, second = 0;
2708         for(i=0; i<nrOfSeekAds; i++) {
2709             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2710             if(d < dist) { dist = d; closest = i; }
2711             second += (d - zList[i] < 120); // count in-range ads
2712             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2713         }
2714         if(dist < 120) {
2715             char buf[MSG_SIZ];
2716             second = (second > 1);
2717             if(displayed != closest || second != lastSecond) {
2718                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2719                 lastSecond = second; displayed = closest;
2720             }
2721             if(click == Press) {
2722                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2723                 lastDown = closest;
2724                 return TRUE;
2725             } // on press 'hit', only show info
2726             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2727             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2728             SendToICS(ics_prefix);
2729             SendToICS(buf);
2730             return TRUE; // let incoming board of started game pop down the graph
2731         } else if(click == Release) { // release 'miss' is ignored
2732             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2733             if(moving == 2) { // right up-click
2734                 nrOfSeekAds = 0; // refresh graph
2735                 soughtPending = TRUE;
2736                 SendToICS(ics_prefix);
2737                 SendToICS("sought\n"); // should this be "sought all"?
2738             }
2739             return TRUE;
2740         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2741         // press miss or release hit 'pop down' seek graph
2742         seekGraphUp = FALSE;
2743         DrawPosition(TRUE, NULL);
2744     }
2745     return TRUE;
2746 }
2747
2748 void
2749 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2750 {
2751 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2752 #define STARTED_NONE 0
2753 #define STARTED_MOVES 1
2754 #define STARTED_BOARD 2
2755 #define STARTED_OBSERVE 3
2756 #define STARTED_HOLDINGS 4
2757 #define STARTED_CHATTER 5
2758 #define STARTED_COMMENT 6
2759 #define STARTED_MOVES_NOHIDE 7
2760
2761     static int started = STARTED_NONE;
2762     static char parse[20000];
2763     static int parse_pos = 0;
2764     static char buf[BUF_SIZE + 1];
2765     static int firstTime = TRUE, intfSet = FALSE;
2766     static ColorClass prevColor = ColorNormal;
2767     static int savingComment = FALSE;
2768     static int cmatch = 0; // continuation sequence match
2769     char *bp;
2770     char str[MSG_SIZ];
2771     int i, oldi;
2772     int buf_len;
2773     int next_out;
2774     int tkind;
2775     int backup;    /* [DM] For zippy color lines */
2776     char *p;
2777     char talker[MSG_SIZ]; // [HGM] chat
2778     int channel;
2779
2780     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2781
2782     if (appData.debugMode) {
2783       if (!error) {
2784         fprintf(debugFP, "<ICS: ");
2785         show_bytes(debugFP, data, count);
2786         fprintf(debugFP, "\n");
2787       }
2788     }
2789
2790     if (appData.debugMode) { int f = forwardMostMove;
2791         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2792                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2793                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2794     }
2795     if (count > 0) {
2796         /* If last read ended with a partial line that we couldn't parse,
2797            prepend it to the new read and try again. */
2798         if (leftover_len > 0) {
2799             for (i=0; i<leftover_len; i++)
2800               buf[i] = buf[leftover_start + i];
2801         }
2802
2803     /* copy new characters into the buffer */
2804     bp = buf + leftover_len;
2805     buf_len=leftover_len;
2806     for (i=0; i<count; i++)
2807     {
2808         // ignore these
2809         if (data[i] == '\r')
2810             continue;
2811
2812         // join lines split by ICS?
2813         if (!appData.noJoin)
2814         {
2815             /*
2816                 Joining just consists of finding matches against the
2817                 continuation sequence, and discarding that sequence
2818                 if found instead of copying it.  So, until a match
2819                 fails, there's nothing to do since it might be the
2820                 complete sequence, and thus, something we don't want
2821                 copied.
2822             */
2823             if (data[i] == cont_seq[cmatch])
2824             {
2825                 cmatch++;
2826                 if (cmatch == strlen(cont_seq))
2827                 {
2828                     cmatch = 0; // complete match.  just reset the counter
2829
2830                     /*
2831                         it's possible for the ICS to not include the space
2832                         at the end of the last word, making our [correct]
2833                         join operation fuse two separate words.  the server
2834                         does this when the space occurs at the width setting.
2835                     */
2836                     if (!buf_len || buf[buf_len-1] != ' ')
2837                     {
2838                         *bp++ = ' ';
2839                         buf_len++;
2840                     }
2841                 }
2842                 continue;
2843             }
2844             else if (cmatch)
2845             {
2846                 /*
2847                     match failed, so we have to copy what matched before
2848                     falling through and copying this character.  In reality,
2849                     this will only ever be just the newline character, but
2850                     it doesn't hurt to be precise.
2851                 */
2852                 strncpy(bp, cont_seq, cmatch);
2853                 bp += cmatch;
2854                 buf_len += cmatch;
2855                 cmatch = 0;
2856             }
2857         }
2858
2859         // copy this char
2860         *bp++ = data[i];
2861         buf_len++;
2862     }
2863
2864         buf[buf_len] = NULLCHAR;
2865 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2866         next_out = 0;
2867         leftover_start = 0;
2868
2869         i = 0;
2870         while (i < buf_len) {
2871             /* Deal with part of the TELNET option negotiation
2872                protocol.  We refuse to do anything beyond the
2873                defaults, except that we allow the WILL ECHO option,
2874                which ICS uses to turn off password echoing when we are
2875                directly connected to it.  We reject this option
2876                if localLineEditing mode is on (always on in xboard)
2877                and we are talking to port 23, which might be a real
2878                telnet server that will try to keep WILL ECHO on permanently.
2879              */
2880             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2881                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2882                 unsigned char option;
2883                 oldi = i;
2884                 switch ((unsigned char) buf[++i]) {
2885                   case TN_WILL:
2886                     if (appData.debugMode)
2887                       fprintf(debugFP, "\n<WILL ");
2888                     switch (option = (unsigned char) buf[++i]) {
2889                       case TN_ECHO:
2890                         if (appData.debugMode)
2891                           fprintf(debugFP, "ECHO ");
2892                         /* Reply only if this is a change, according
2893                            to the protocol rules. */
2894                         if (remoteEchoOption) break;
2895                         if (appData.localLineEditing &&
2896                             atoi(appData.icsPort) == TN_PORT) {
2897                             TelnetRequest(TN_DONT, TN_ECHO);
2898                         } else {
2899                             EchoOff();
2900                             TelnetRequest(TN_DO, TN_ECHO);
2901                             remoteEchoOption = TRUE;
2902                         }
2903                         break;
2904                       default:
2905                         if (appData.debugMode)
2906                           fprintf(debugFP, "%d ", option);
2907                         /* Whatever this is, we don't want it. */
2908                         TelnetRequest(TN_DONT, option);
2909                         break;
2910                     }
2911                     break;
2912                   case TN_WONT:
2913                     if (appData.debugMode)
2914                       fprintf(debugFP, "\n<WONT ");
2915                     switch (option = (unsigned char) buf[++i]) {
2916                       case TN_ECHO:
2917                         if (appData.debugMode)
2918                           fprintf(debugFP, "ECHO ");
2919                         /* Reply only if this is a change, according
2920                            to the protocol rules. */
2921                         if (!remoteEchoOption) break;
2922                         EchoOn();
2923                         TelnetRequest(TN_DONT, TN_ECHO);
2924                         remoteEchoOption = FALSE;
2925                         break;
2926                       default:
2927                         if (appData.debugMode)
2928                           fprintf(debugFP, "%d ", (unsigned char) option);
2929                         /* Whatever this is, it must already be turned
2930                            off, because we never agree to turn on
2931                            anything non-default, so according to the
2932                            protocol rules, we don't reply. */
2933                         break;
2934                     }
2935                     break;
2936                   case TN_DO:
2937                     if (appData.debugMode)
2938                       fprintf(debugFP, "\n<DO ");
2939                     switch (option = (unsigned char) buf[++i]) {
2940                       default:
2941                         /* Whatever this is, we refuse to do it. */
2942                         if (appData.debugMode)
2943                           fprintf(debugFP, "%d ", option);
2944                         TelnetRequest(TN_WONT, option);
2945                         break;
2946                     }
2947                     break;
2948                   case TN_DONT:
2949                     if (appData.debugMode)
2950                       fprintf(debugFP, "\n<DONT ");
2951                     switch (option = (unsigned char) buf[++i]) {
2952                       default:
2953                         if (appData.debugMode)
2954                           fprintf(debugFP, "%d ", option);
2955                         /* Whatever this is, we are already not doing
2956                            it, because we never agree to do anything
2957                            non-default, so according to the protocol
2958                            rules, we don't reply. */
2959                         break;
2960                     }
2961                     break;
2962                   case TN_IAC:
2963                     if (appData.debugMode)
2964                       fprintf(debugFP, "\n<IAC ");
2965                     /* Doubled IAC; pass it through */
2966                     i--;
2967                     break;
2968                   default:
2969                     if (appData.debugMode)
2970                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2971                     /* Drop all other telnet commands on the floor */
2972                     break;
2973                 }
2974                 if (oldi > next_out)
2975                   SendToPlayer(&buf[next_out], oldi - next_out);
2976                 if (++i > next_out)
2977                   next_out = i;
2978                 continue;
2979             }
2980
2981             /* OK, this at least will *usually* work */
2982             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2983                 loggedOn = TRUE;
2984             }
2985
2986             if (loggedOn && !intfSet) {
2987                 if (ics_type == ICS_ICC) {
2988                   snprintf(str, MSG_SIZ,
2989                           "/set-quietly interface %s\n/set-quietly style 12\n",
2990                           programVersion);
2991                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2992                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2993                 } else if (ics_type == ICS_CHESSNET) {
2994                   snprintf(str, MSG_SIZ, "/style 12\n");
2995                 } else {
2996                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2997                   strcat(str, programVersion);
2998                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2999                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3000                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
3001 #ifdef WIN32
3002                   strcat(str, "$iset nohighlight 1\n");
3003 #endif
3004                   strcat(str, "$iset lock 1\n$style 12\n");
3005                 }
3006                 SendToICS(str);
3007                 NotifyFrontendLogin();
3008                 intfSet = TRUE;
3009             }
3010
3011             if (started == STARTED_COMMENT) {
3012                 /* Accumulate characters in comment */
3013                 parse[parse_pos++] = buf[i];
3014                 if (buf[i] == '\n') {
3015                     parse[parse_pos] = NULLCHAR;
3016                     if(chattingPartner>=0) {
3017                         char mess[MSG_SIZ];
3018                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
3019                         OutputChatMessage(chattingPartner, mess);
3020                         chattingPartner = -1;
3021                         next_out = i+1; // [HGM] suppress printing in ICS window
3022                     } else
3023                     if(!suppressKibitz) // [HGM] kibitz
3024                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
3025                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
3026                         int nrDigit = 0, nrAlph = 0, j;
3027                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
3028                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
3029                         parse[parse_pos] = NULLCHAR;
3030                         // try to be smart: if it does not look like search info, it should go to
3031                         // ICS interaction window after all, not to engine-output window.
3032                         for(j=0; j<parse_pos; j++) { // count letters and digits
3033                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
3034                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
3035                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
3036                         }
3037                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
3038                             int depth=0; float score;
3039                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
3040                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
3041                                 pvInfoList[forwardMostMove-1].depth = depth;
3042                                 pvInfoList[forwardMostMove-1].score = 100*score;
3043                             }
3044                             OutputKibitz(suppressKibitz, parse);
3045                         } else {
3046                             char tmp[MSG_SIZ];
3047                             if(gameMode == IcsObserving) // restore original ICS messages
3048                               /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3049                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3050                             else
3051                             /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3052                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3053                             SendToPlayer(tmp, strlen(tmp));
3054                         }
3055                         next_out = i+1; // [HGM] suppress printing in ICS window
3056                     }
3057                     started = STARTED_NONE;
3058                 } else {
3059                     /* Don't match patterns against characters in comment */
3060                     i++;
3061                     continue;
3062                 }
3063             }
3064             if (started == STARTED_CHATTER) {
3065                 if (buf[i] != '\n') {
3066                     /* Don't match patterns against characters in chatter */
3067                     i++;
3068                     continue;
3069                 }
3070                 started = STARTED_NONE;
3071                 if(suppressKibitz) next_out = i+1;
3072             }
3073
3074             /* Kludge to deal with rcmd protocol */
3075             if (firstTime && looking_at(buf, &i, "\001*")) {
3076                 DisplayFatalError(&buf[1], 0, 1);
3077                 continue;
3078             } else {
3079                 firstTime = FALSE;
3080             }
3081
3082             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3083                 ics_type = ICS_ICC;
3084                 ics_prefix = "/";
3085                 if (appData.debugMode)
3086                   fprintf(debugFP, "ics_type %d\n", ics_type);
3087                 continue;
3088             }
3089             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3090                 ics_type = ICS_FICS;
3091                 ics_prefix = "$";
3092                 if (appData.debugMode)
3093                   fprintf(debugFP, "ics_type %d\n", ics_type);
3094                 continue;
3095             }
3096             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3097                 ics_type = ICS_CHESSNET;
3098                 ics_prefix = "/";
3099                 if (appData.debugMode)
3100                   fprintf(debugFP, "ics_type %d\n", ics_type);
3101                 continue;
3102             }
3103
3104             if (!loggedOn &&
3105                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3106                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3107                  looking_at(buf, &i, "will be \"*\""))) {
3108               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3109               continue;
3110             }
3111
3112             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3113               char buf[MSG_SIZ];
3114               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3115               DisplayIcsInteractionTitle(buf);
3116               have_set_title = TRUE;
3117             }
3118
3119             /* skip finger notes */
3120             if (started == STARTED_NONE &&
3121                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3122                  (buf[i] == '1' && buf[i+1] == '0')) &&
3123                 buf[i+2] == ':' && buf[i+3] == ' ') {
3124               started = STARTED_CHATTER;
3125               i += 3;
3126               continue;
3127             }
3128
3129             oldi = i;
3130             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3131             if(appData.seekGraph) {
3132                 if(soughtPending && MatchSoughtLine(buf+i)) {
3133                     i = strstr(buf+i, "rated") - buf;
3134                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3135                     next_out = leftover_start = i;
3136                     started = STARTED_CHATTER;
3137                     suppressKibitz = TRUE;
3138                     continue;
3139                 }
3140                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3141                         && looking_at(buf, &i, "* ads displayed")) {
3142                     soughtPending = FALSE;
3143                     seekGraphUp = TRUE;
3144                     DrawSeekGraph();
3145                     continue;
3146                 }
3147                 if(appData.autoRefresh) {
3148                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3149                         int s = (ics_type == ICS_ICC); // ICC format differs
3150                         if(seekGraphUp)
3151                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3152                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3153                         looking_at(buf, &i, "*% "); // eat prompt
3154                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3155                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3156                         next_out = i; // suppress
3157                         continue;
3158                     }
3159                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3160                         char *p = star_match[0];
3161                         while(*p) {
3162                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3163                             while(*p && *p++ != ' '); // next
3164                         }
3165                         looking_at(buf, &i, "*% "); // eat prompt
3166                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3167                         next_out = i;
3168                         continue;
3169                     }
3170                 }
3171             }
3172
3173             /* skip formula vars */
3174             if (started == STARTED_NONE &&
3175                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3176               started = STARTED_CHATTER;
3177               i += 3;
3178               continue;
3179             }
3180
3181             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3182             if (appData.autoKibitz && started == STARTED_NONE &&
3183                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3184                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3185                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3186                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3187                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3188                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3189                         suppressKibitz = TRUE;
3190                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3191                         next_out = i;
3192                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3193                                 && (gameMode == IcsPlayingWhite)) ||
3194                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3195                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3196                             started = STARTED_CHATTER; // own kibitz we simply discard
3197                         else {
3198                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3199                             parse_pos = 0; parse[0] = NULLCHAR;
3200                             savingComment = TRUE;
3201                             suppressKibitz = gameMode != IcsObserving ? 2 :
3202                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3203                         }
3204                         continue;
3205                 } else
3206                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3207                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3208                          && atoi(star_match[0])) {
3209                     // suppress the acknowledgements of our own autoKibitz
3210                     char *p;
3211                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3212                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3213                     SendToPlayer(star_match[0], strlen(star_match[0]));
3214                     if(looking_at(buf, &i, "*% ")) // eat prompt
3215                         suppressKibitz = FALSE;
3216                     next_out = i;
3217                     continue;
3218                 }
3219             } // [HGM] kibitz: end of patch
3220
3221             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3222
3223             // [HGM] chat: intercept tells by users for which we have an open chat window
3224             channel = -1;
3225             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3226                                            looking_at(buf, &i, "* whispers:") ||
3227                                            looking_at(buf, &i, "* kibitzes:") ||
3228                                            looking_at(buf, &i, "* shouts:") ||
3229                                            looking_at(buf, &i, "* c-shouts:") ||
3230                                            looking_at(buf, &i, "--> * ") ||
3231                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3232                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3233                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3234                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3235                 int p;
3236                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3237                 chattingPartner = -1;
3238
3239                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3240                 for(p=0; p<MAX_CHAT; p++) {
3241                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3242                     talker[0] = '['; strcat(talker, "] ");
3243                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3244                     chattingPartner = p; break;
3245                     }
3246                 } else
3247                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3248                 for(p=0; p<MAX_CHAT; p++) {
3249                     if(!strcmp("kibitzes", chatPartner[p])) {
3250                         talker[0] = '['; strcat(talker, "] ");
3251                         chattingPartner = p; break;
3252                     }
3253                 } else
3254                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3255                 for(p=0; p<MAX_CHAT; p++) {
3256                     if(!strcmp("whispers", chatPartner[p])) {
3257                         talker[0] = '['; strcat(talker, "] ");
3258                         chattingPartner = p; break;
3259                     }
3260                 } else
3261                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3262                   if(buf[i-8] == '-' && buf[i-3] == 't')
3263                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3264                     if(!strcmp("c-shouts", chatPartner[p])) {
3265                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3266                         chattingPartner = p; break;
3267                     }
3268                   }
3269                   if(chattingPartner < 0)
3270                   for(p=0; p<MAX_CHAT; p++) {
3271                     if(!strcmp("shouts", chatPartner[p])) {
3272                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3273                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3274                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3275                         chattingPartner = p; break;
3276                     }
3277                   }
3278                 }
3279                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3280                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3281                     talker[0] = 0; Colorize(ColorTell, FALSE);
3282                     chattingPartner = p; break;
3283                 }
3284                 if(chattingPartner<0) i = oldi; else {
3285                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3286                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3287                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3288                     started = STARTED_COMMENT;
3289                     parse_pos = 0; parse[0] = NULLCHAR;
3290                     savingComment = 3 + chattingPartner; // counts as TRUE
3291                     suppressKibitz = TRUE;
3292                     continue;
3293                 }
3294             } // [HGM] chat: end of patch
3295
3296           backup = i;
3297             if (appData.zippyTalk || appData.zippyPlay) {
3298                 /* [DM] Backup address for color zippy lines */
3299 #if ZIPPY
3300                if (loggedOn == TRUE)
3301                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3302                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3303 #endif
3304             } // [DM] 'else { ' deleted
3305                 if (
3306                     /* Regular tells and says */
3307                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3308                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3309                     looking_at(buf, &i, "* says: ") ||
3310                     /* Don't color "message" or "messages" output */
3311                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3312                     looking_at(buf, &i, "*. * at *:*: ") ||
3313                     looking_at(buf, &i, "--* (*:*): ") ||
3314                     /* Message notifications (same color as tells) */
3315                     looking_at(buf, &i, "* has left a message ") ||
3316                     looking_at(buf, &i, "* just sent you a message:\n") ||
3317                     /* Whispers and kibitzes */
3318                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3319                     looking_at(buf, &i, "* kibitzes: ") ||
3320                     /* Channel tells */
3321                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3322
3323                   if (tkind == 1 && strchr(star_match[0], ':')) {
3324                       /* Avoid "tells you:" spoofs in channels */
3325                      tkind = 3;
3326                   }
3327                   if (star_match[0][0] == NULLCHAR ||
3328                       strchr(star_match[0], ' ') ||
3329                       (tkind == 3 && strchr(star_match[1], ' '))) {
3330                     /* Reject bogus matches */
3331                     i = oldi;
3332                   } else {
3333                     if (appData.colorize) {
3334                       if (oldi > next_out) {
3335                         SendToPlayer(&buf[next_out], oldi - next_out);
3336                         next_out = oldi;
3337                       }
3338                       switch (tkind) {
3339                       case 1:
3340                         Colorize(ColorTell, FALSE);
3341                         curColor = ColorTell;
3342                         break;
3343                       case 2:
3344                         Colorize(ColorKibitz, FALSE);
3345                         curColor = ColorKibitz;
3346                         break;
3347                       case 3:
3348                         p = strrchr(star_match[1], '(');
3349                         if (p == NULL) {
3350                           p = star_match[1];
3351                         } else {
3352                           p++;
3353                         }
3354                         if (atoi(p) == 1) {
3355                           Colorize(ColorChannel1, FALSE);
3356                           curColor = ColorChannel1;
3357                         } else {
3358                           Colorize(ColorChannel, FALSE);
3359                           curColor = ColorChannel;
3360                         }
3361                         break;
3362                       case 5:
3363                         curColor = ColorNormal;
3364                         break;
3365                       }
3366                     }
3367                     if (started == STARTED_NONE && appData.autoComment &&
3368                         (gameMode == IcsObserving ||
3369                          gameMode == IcsPlayingWhite ||
3370                          gameMode == IcsPlayingBlack)) {
3371                       parse_pos = i - oldi;
3372                       memcpy(parse, &buf[oldi], parse_pos);
3373                       parse[parse_pos] = NULLCHAR;
3374                       started = STARTED_COMMENT;
3375                       savingComment = TRUE;
3376                     } else {
3377                       started = STARTED_CHATTER;
3378                       savingComment = FALSE;
3379                     }
3380                     loggedOn = TRUE;
3381                     continue;
3382                   }
3383                 }
3384
3385                 if (looking_at(buf, &i, "* s-shouts: ") ||
3386                     looking_at(buf, &i, "* c-shouts: ")) {
3387                     if (appData.colorize) {
3388                         if (oldi > next_out) {
3389                             SendToPlayer(&buf[next_out], oldi - next_out);
3390                             next_out = oldi;
3391                         }
3392                         Colorize(ColorSShout, FALSE);
3393                         curColor = ColorSShout;
3394                     }
3395                     loggedOn = TRUE;
3396                     started = STARTED_CHATTER;
3397                     continue;
3398                 }
3399
3400                 if (looking_at(buf, &i, "--->")) {
3401                     loggedOn = TRUE;
3402                     continue;
3403                 }
3404
3405                 if (looking_at(buf, &i, "* shouts: ") ||
3406                     looking_at(buf, &i, "--> ")) {
3407                     if (appData.colorize) {
3408                         if (oldi > next_out) {
3409                             SendToPlayer(&buf[next_out], oldi - next_out);
3410                             next_out = oldi;
3411                         }
3412                         Colorize(ColorShout, FALSE);
3413                         curColor = ColorShout;
3414                     }
3415                     loggedOn = TRUE;
3416                     started = STARTED_CHATTER;
3417                     continue;
3418                 }
3419
3420                 if (looking_at( buf, &i, "Challenge:")) {
3421                     if (appData.colorize) {
3422                         if (oldi > next_out) {
3423                             SendToPlayer(&buf[next_out], oldi - next_out);
3424                             next_out = oldi;
3425                         }
3426                         Colorize(ColorChallenge, FALSE);
3427                         curColor = ColorChallenge;
3428                     }
3429                     loggedOn = TRUE;
3430                     continue;
3431                 }
3432
3433                 if (looking_at(buf, &i, "* offers you") ||
3434                     looking_at(buf, &i, "* offers to be") ||
3435                     looking_at(buf, &i, "* would like to") ||
3436                     looking_at(buf, &i, "* requests to") ||
3437                     looking_at(buf, &i, "Your opponent offers") ||
3438                     looking_at(buf, &i, "Your opponent requests")) {
3439
3440                     if (appData.colorize) {
3441                         if (oldi > next_out) {
3442                             SendToPlayer(&buf[next_out], oldi - next_out);
3443                             next_out = oldi;
3444                         }
3445                         Colorize(ColorRequest, FALSE);
3446                         curColor = ColorRequest;
3447                     }
3448                     continue;
3449                 }
3450
3451                 if (looking_at(buf, &i, "* (*) seeking")) {
3452                     if (appData.colorize) {
3453                         if (oldi > next_out) {
3454                             SendToPlayer(&buf[next_out], oldi - next_out);
3455                             next_out = oldi;
3456                         }
3457                         Colorize(ColorSeek, FALSE);
3458                         curColor = ColorSeek;
3459                     }
3460                     continue;
3461             }
3462
3463           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3464
3465             if (looking_at(buf, &i, "\\   ")) {
3466                 if (prevColor != ColorNormal) {
3467                     if (oldi > next_out) {
3468                         SendToPlayer(&buf[next_out], oldi - next_out);
3469                         next_out = oldi;
3470                     }
3471                     Colorize(prevColor, TRUE);
3472                     curColor = prevColor;
3473                 }
3474                 if (savingComment) {
3475                     parse_pos = i - oldi;
3476                     memcpy(parse, &buf[oldi], parse_pos);
3477                     parse[parse_pos] = NULLCHAR;
3478                     started = STARTED_COMMENT;
3479                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3480                         chattingPartner = savingComment - 3; // kludge to remember the box
3481                 } else {
3482                     started = STARTED_CHATTER;
3483                 }
3484                 continue;
3485             }
3486
3487             if (looking_at(buf, &i, "Black Strength :") ||
3488                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3489                 looking_at(buf, &i, "<10>") ||
3490                 looking_at(buf, &i, "#@#")) {
3491                 /* Wrong board style */
3492                 loggedOn = TRUE;
3493                 SendToICS(ics_prefix);
3494                 SendToICS("set style 12\n");
3495                 SendToICS(ics_prefix);
3496                 SendToICS("refresh\n");
3497                 continue;
3498             }
3499
3500             if (looking_at(buf, &i, "login:")) {
3501               if (!have_sent_ICS_logon) {
3502                 if(ICSInitScript())
3503                   have_sent_ICS_logon = 1;
3504                 else // no init script was found
3505                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3506               } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3507                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3508               }
3509                 continue;
3510             }
3511
3512             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3513                 (looking_at(buf, &i, "\n<12> ") ||
3514                  looking_at(buf, &i, "<12> "))) {
3515                 loggedOn = TRUE;
3516                 if (oldi > next_out) {
3517                     SendToPlayer(&buf[next_out], oldi - next_out);
3518                 }
3519                 next_out = i;
3520                 started = STARTED_BOARD;
3521                 parse_pos = 0;
3522                 continue;
3523             }
3524
3525             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3526                 looking_at(buf, &i, "<b1> ")) {
3527                 if (oldi > next_out) {
3528                     SendToPlayer(&buf[next_out], oldi - next_out);
3529                 }
3530                 next_out = i;
3531                 started = STARTED_HOLDINGS;
3532                 parse_pos = 0;
3533                 continue;
3534             }
3535
3536             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3537                 loggedOn = TRUE;
3538                 /* Header for a move list -- first line */
3539
3540                 switch (ics_getting_history) {
3541                   case H_FALSE:
3542                     switch (gameMode) {
3543                       case IcsIdle:
3544                       case BeginningOfGame:
3545                         /* User typed "moves" or "oldmoves" while we
3546                            were idle.  Pretend we asked for these
3547                            moves and soak them up so user can step
3548                            through them and/or save them.
3549                            */
3550                         Reset(FALSE, TRUE);
3551                         gameMode = IcsObserving;
3552                         ModeHighlight();
3553                         ics_gamenum = -1;
3554                         ics_getting_history = H_GOT_UNREQ_HEADER;
3555                         break;
3556                       case EditGame: /*?*/
3557                       case EditPosition: /*?*/
3558                         /* Should above feature work in these modes too? */
3559                         /* For now it doesn't */
3560                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3561                         break;
3562                       default:
3563                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3564                         break;
3565                     }
3566                     break;
3567                   case H_REQUESTED:
3568                     /* Is this the right one? */
3569                     if (gameInfo.white && gameInfo.black &&
3570                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3571                         strcmp(gameInfo.black, star_match[2]) == 0) {
3572                         /* All is well */
3573                         ics_getting_history = H_GOT_REQ_HEADER;
3574                     }
3575                     break;
3576                   case H_GOT_REQ_HEADER:
3577                   case H_GOT_UNREQ_HEADER:
3578                   case H_GOT_UNWANTED_HEADER:
3579                   case H_GETTING_MOVES:
3580                     /* Should not happen */
3581                     DisplayError(_("Error gathering move list: two headers"), 0);
3582                     ics_getting_history = H_FALSE;
3583                     break;
3584                 }
3585
3586                 /* Save player ratings into gameInfo if needed */
3587                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3588                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3589                     (gameInfo.whiteRating == -1 ||
3590                      gameInfo.blackRating == -1)) {
3591
3592                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3593                     gameInfo.blackRating = string_to_rating(star_match[3]);
3594                     if (appData.debugMode)
3595                       fprintf(debugFP, "Ratings from header: W %d, B %d\n",
3596                               gameInfo.whiteRating, gameInfo.blackRating);
3597                 }
3598                 continue;
3599             }
3600
3601             if (looking_at(buf, &i,
3602               "* * match, initial time: * minute*, increment: * second")) {
3603                 /* Header for a move list -- second line */
3604                 /* Initial board will follow if this is a wild game */
3605                 if (gameInfo.event != NULL) free(gameInfo.event);
3606                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3607                 gameInfo.event = StrSave(str);
3608                 /* [HGM] we switched variant. Translate boards if needed. */
3609                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3610                 continue;
3611             }
3612
3613             if (looking_at(buf, &i, "Move  ")) {
3614                 /* Beginning of a move list */
3615                 switch (ics_getting_history) {
3616                   case H_FALSE:
3617                     /* Normally should not happen */
3618                     /* Maybe user hit reset while we were parsing */
3619                     break;
3620                   case H_REQUESTED:
3621                     /* Happens if we are ignoring a move list that is not
3622                      * the one we just requested.  Common if the user
3623                      * tries to observe two games without turning off
3624                      * getMoveList */
3625                     break;
3626                   case H_GETTING_MOVES:
3627                     /* Should not happen */
3628                     DisplayError(_("Error gathering move list: nested"), 0);
3629                     ics_getting_history = H_FALSE;
3630                     break;
3631                   case H_GOT_REQ_HEADER:
3632                     ics_getting_history = H_GETTING_MOVES;
3633                     started = STARTED_MOVES;
3634                     parse_pos = 0;
3635                     if (oldi > next_out) {
3636                         SendToPlayer(&buf[next_out], oldi - next_out);
3637                     }
3638                     break;
3639                   case H_GOT_UNREQ_HEADER:
3640                     ics_getting_history = H_GETTING_MOVES;
3641                     started = STARTED_MOVES_NOHIDE;
3642                     parse_pos = 0;
3643                     break;
3644                   case H_GOT_UNWANTED_HEADER:
3645                     ics_getting_history = H_FALSE;
3646                     break;
3647                 }
3648                 continue;
3649             }
3650
3651             if (looking_at(buf, &i, "% ") ||
3652                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3653                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3654                 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3655                     soughtPending = FALSE;
3656                     seekGraphUp = TRUE;
3657                     DrawSeekGraph();
3658                 }
3659                 if(suppressKibitz) next_out = i;
3660                 savingComment = FALSE;
3661                 suppressKibitz = 0;
3662                 switch (started) {
3663                   case STARTED_MOVES:
3664                   case STARTED_MOVES_NOHIDE:
3665                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3666                     parse[parse_pos + i - oldi] = NULLCHAR;
3667                     ParseGameHistory(parse);
3668 #if ZIPPY
3669                     if (appData.zippyPlay && first.initDone) {
3670                         FeedMovesToProgram(&first, forwardMostMove);
3671                         if (gameMode == IcsPlayingWhite) {
3672                             if (WhiteOnMove(forwardMostMove)) {
3673                                 if (first.sendTime) {
3674                                   if (first.useColors) {
3675                                     SendToProgram("black\n", &first);
3676                                   }
3677                                   SendTimeRemaining(&first, TRUE);
3678                                 }
3679                                 if (first.useColors) {
3680                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3681                                 }
3682                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3683                                 first.maybeThinking = TRUE;
3684                             } else {
3685                                 if (first.usePlayother) {
3686                                   if (first.sendTime) {
3687                                     SendTimeRemaining(&first, TRUE);
3688                                   }
3689                                   SendToProgram("playother\n", &first);
3690                                   firstMove = FALSE;
3691                                 } else {
3692                                   firstMove = TRUE;
3693                                 }
3694                             }
3695                         } else if (gameMode == IcsPlayingBlack) {
3696                             if (!WhiteOnMove(forwardMostMove)) {
3697                                 if (first.sendTime) {
3698                                   if (first.useColors) {
3699                                     SendToProgram("white\n", &first);
3700                                   }
3701                                   SendTimeRemaining(&first, FALSE);
3702                                 }
3703                                 if (first.useColors) {
3704                                   SendToProgram("black\n", &first);
3705                                 }
3706                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3707                                 first.maybeThinking = TRUE;
3708                             } else {
3709                                 if (first.usePlayother) {
3710                                   if (first.sendTime) {
3711                                     SendTimeRemaining(&first, FALSE);
3712                                   }
3713                                   SendToProgram("playother\n", &first);
3714                                   firstMove = FALSE;
3715                                 } else {
3716                                   firstMove = TRUE;
3717                                 }
3718                             }
3719                         }
3720                     }
3721 #endif
3722                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3723                         /* Moves came from oldmoves or moves command
3724                            while we weren't doing anything else.
3725                            */
3726                         currentMove = forwardMostMove;
3727                         ClearHighlights();/*!!could figure this out*/
3728                         flipView = appData.flipView;
3729                         DrawPosition(TRUE, boards[currentMove]);
3730                         DisplayBothClocks();
3731                         snprintf(str, MSG_SIZ, "%s %s %s",
3732                                 gameInfo.white, _("vs."),  gameInfo.black);
3733                         DisplayTitle(str);
3734                         gameMode = IcsIdle;
3735                     } else {
3736                         /* Moves were history of an active game */
3737                         if (gameInfo.resultDetails != NULL) {
3738                             free(gameInfo.resultDetails);
3739                             gameInfo.resultDetails = NULL;
3740                         }
3741                     }
3742                     HistorySet(parseList, backwardMostMove,
3743                                forwardMostMove, currentMove-1);
3744                     DisplayMove(currentMove - 1);
3745                     if (started == STARTED_MOVES) next_out = i;
3746                     started = STARTED_NONE;
3747                     ics_getting_history = H_FALSE;
3748                     break;
3749
3750                   case STARTED_OBSERVE:
3751                     started = STARTED_NONE;
3752                     SendToICS(ics_prefix);
3753                     SendToICS("refresh\n");
3754                     break;
3755
3756                   default:
3757                     break;
3758                 }
3759                 if(bookHit) { // [HGM] book: simulate book reply
3760                     static char bookMove[MSG_SIZ]; // a bit generous?
3761
3762                     programStats.nodes = programStats.depth = programStats.time =
3763                     programStats.score = programStats.got_only_move = 0;
3764                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3765
3766                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3767                     strcat(bookMove, bookHit);
3768                     HandleMachineMove(bookMove, &first);
3769                 }
3770                 continue;
3771             }
3772
3773             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3774                  started == STARTED_HOLDINGS ||
3775                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3776                 /* Accumulate characters in move list or board */
3777                 parse[parse_pos++] = buf[i];
3778             }
3779
3780             /* Start of game messages.  Mostly we detect start of game
3781                when the first board image arrives.  On some versions
3782                of the ICS, though, we need to do a "refresh" after starting
3783                to observe in order to get the current board right away. */
3784             if (looking_at(buf, &i, "Adding game * to observation list")) {
3785                 started = STARTED_OBSERVE;
3786                 continue;
3787             }
3788
3789             /* Handle auto-observe */
3790             if (appData.autoObserve &&
3791                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3792                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3793                 char *player;
3794                 /* Choose the player that was highlighted, if any. */
3795                 if (star_match[0][0] == '\033' ||
3796                     star_match[1][0] != '\033') {
3797                     player = star_match[0];
3798                 } else {
3799                     player = star_match[2];
3800                 }
3801                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3802                         ics_prefix, StripHighlightAndTitle(player));
3803                 SendToICS(str);
3804
3805                 /* Save ratings from notify string */
3806                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3807                 player1Rating = string_to_rating(star_match[1]);
3808                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3809                 player2Rating = string_to_rating(star_match[3]);
3810
3811                 if (appData.debugMode)
3812                   fprintf(debugFP,
3813                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3814                           player1Name, player1Rating,
3815                           player2Name, player2Rating);
3816
3817                 continue;
3818             }
3819
3820             /* Deal with automatic examine mode after a game,
3821                and with IcsObserving -> IcsExamining transition */
3822             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3823                 looking_at(buf, &i, "has made you an examiner of game *")) {
3824
3825                 int gamenum = atoi(star_match[0]);
3826                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3827                     gamenum == ics_gamenum) {
3828                     /* We were already playing or observing this game;
3829                        no need to refetch history */
3830                     gameMode = IcsExamining;
3831                     if (pausing) {
3832                         pauseExamForwardMostMove = forwardMostMove;
3833                     } else if (currentMove < forwardMostMove) {
3834                         ForwardInner(forwardMostMove);
3835                     }
3836                 } else {
3837                     /* I don't think this case really can happen */
3838                     SendToICS(ics_prefix);
3839                     SendToICS("refresh\n");
3840                 }
3841                 continue;
3842             }
3843
3844             /* Error messages */
3845 //          if (ics_user_moved) {
3846             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3847                 if (looking_at(buf, &i, "Illegal move") ||
3848                     looking_at(buf, &i, "Not a legal move") ||
3849                     looking_at(buf, &i, "Your king is in check") ||
3850                     looking_at(buf, &i, "It isn't your turn") ||
3851                     looking_at(buf, &i, "It is not your move")) {
3852                     /* Illegal move */
3853                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3854                         currentMove = forwardMostMove-1;
3855                         DisplayMove(currentMove - 1); /* before DMError */
3856                         DrawPosition(FALSE, boards[currentMove]);
3857                         SwitchClocks(forwardMostMove-1); // [HGM] race
3858                         DisplayBothClocks();
3859                     }
3860                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3861                     ics_user_moved = 0;
3862                     continue;
3863                 }
3864             }
3865
3866             if (looking_at(buf, &i, "still have time") ||
3867                 looking_at(buf, &i, "not out of time") ||
3868                 looking_at(buf, &i, "either player is out of time") ||
3869                 looking_at(buf, &i, "has timeseal; checking")) {
3870                 /* We must have called his flag a little too soon */
3871                 whiteFlag = blackFlag = FALSE;
3872                 continue;
3873             }
3874
3875             if (looking_at(buf, &i, "added * seconds to") ||
3876                 looking_at(buf, &i, "seconds were added to")) {
3877                 /* Update the clocks */
3878                 SendToICS(ics_prefix);
3879                 SendToICS("refresh\n");
3880                 continue;
3881             }
3882
3883             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3884                 ics_clock_paused = TRUE;
3885                 StopClocks();
3886                 continue;
3887             }
3888
3889             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3890                 ics_clock_paused = FALSE;
3891                 StartClocks();
3892                 continue;
3893             }
3894
3895             /* Grab player ratings from the Creating: message.
3896                Note we have to check for the special case when
3897                the ICS inserts things like [white] or [black]. */
3898             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3899                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3900                 /* star_matches:
3901                    0    player 1 name (not necessarily white)
3902                    1    player 1 rating
3903                    2    empty, white, or black (IGNORED)
3904                    3    player 2 name (not necessarily black)
3905                    4    player 2 rating
3906
3907                    The names/ratings are sorted out when the game
3908                    actually starts (below).
3909                 */
3910                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3911                 player1Rating = string_to_rating(star_match[1]);
3912                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3913                 player2Rating = string_to_rating(star_match[4]);
3914
3915                 if (appData.debugMode)
3916                   fprintf(debugFP,
3917                           "Ratings from 'Creating:' %s %d, %s %d\n",
3918                           player1Name, player1Rating,
3919                           player2Name, player2Rating);
3920
3921                 continue;
3922             }
3923
3924             /* Improved generic start/end-of-game messages */
3925             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3926                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3927                 /* If tkind == 0: */
3928                 /* star_match[0] is the game number */
3929                 /*           [1] is the white player's name */
3930                 /*           [2] is the black player's name */
3931                 /* For end-of-game: */
3932                 /*           [3] is the reason for the game end */
3933                 /*           [4] is a PGN end game-token, preceded by " " */
3934                 /* For start-of-game: */
3935                 /*           [3] begins with "Creating" or "Continuing" */
3936                 /*           [4] is " *" or empty (don't care). */
3937                 int gamenum = atoi(star_match[0]);
3938                 char *whitename, *blackname, *why, *endtoken;
3939                 ChessMove endtype = EndOfFile;
3940
3941                 if (tkind == 0) {
3942                   whitename = star_match[1];
3943                   blackname = star_match[2];
3944                   why = star_match[3];
3945                   endtoken = star_match[4];
3946                 } else {
3947                   whitename = star_match[1];
3948                   blackname = star_match[3];
3949                   why = star_match[5];
3950                   endtoken = star_match[6];
3951                 }
3952
3953                 /* Game start messages */
3954                 if (strncmp(why, "Creating ", 9) == 0 ||
3955                     strncmp(why, "Continuing ", 11) == 0) {
3956                     gs_gamenum = gamenum;
3957                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3958                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
3959                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3960 #if ZIPPY
3961                     if (appData.zippyPlay) {
3962                         ZippyGameStart(whitename, blackname);
3963                     }
3964 #endif /*ZIPPY*/
3965                     partnerBoardValid = FALSE; // [HGM] bughouse
3966                     continue;
3967                 }
3968
3969                 /* Game end messages */
3970                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3971                     ics_gamenum != gamenum) {
3972                     continue;
3973                 }
3974                 while (endtoken[0] == ' ') endtoken++;
3975                 switch (endtoken[0]) {
3976                   case '*':
3977                   default:
3978                     endtype = GameUnfinished;
3979                     break;
3980                   case '0':
3981                     endtype = BlackWins;
3982                     break;
3983                   case '1':
3984                     if (endtoken[1] == '/')
3985                       endtype = GameIsDrawn;
3986                     else
3987                       endtype = WhiteWins;
3988                     break;
3989                 }
3990                 GameEnds(endtype, why, GE_ICS);
3991 #if ZIPPY
3992                 if (appData.zippyPlay && first.initDone) {
3993                     ZippyGameEnd(endtype, why);
3994                     if (first.pr == NoProc) {
3995                       /* Start the next process early so that we'll
3996                          be ready for the next challenge */
3997                       StartChessProgram(&first);
3998                     }
3999                     /* Send "new" early, in case this command takes
4000                        a long time to finish, so that we'll be ready
4001                        for the next challenge. */
4002                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
4003                     Reset(TRUE, TRUE);
4004                 }
4005 #endif /*ZIPPY*/
4006                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
4007                 continue;
4008             }
4009
4010             if (looking_at(buf, &i, "Removing game * from observation") ||
4011                 looking_at(buf, &i, "no longer observing game *") ||
4012                 looking_at(buf, &i, "Game * (*) has no examiners")) {
4013                 if (gameMode == IcsObserving &&
4014                     atoi(star_match[0]) == ics_gamenum)
4015                   {
4016                       /* icsEngineAnalyze */
4017                       if (appData.icsEngineAnalyze) {
4018                             ExitAnalyzeMode();
4019                             ModeHighlight();
4020                       }
4021                       StopClocks();
4022                       gameMode = IcsIdle;
4023                       ics_gamenum = -1;
4024                       ics_user_moved = FALSE;
4025                   }
4026                 continue;
4027             }
4028
4029             if (looking_at(buf, &i, "no longer examining game *")) {
4030                 if (gameMode == IcsExamining &&
4031                     atoi(star_match[0]) == ics_gamenum)
4032                   {
4033                       gameMode = IcsIdle;
4034                       ics_gamenum = -1;
4035                       ics_user_moved = FALSE;
4036                   }
4037                 continue;
4038             }
4039
4040             /* Advance leftover_start past any newlines we find,
4041                so only partial lines can get reparsed */
4042             if (looking_at(buf, &i, "\n")) {
4043                 prevColor = curColor;
4044                 if (curColor != ColorNormal) {
4045                     if (oldi > next_out) {
4046                         SendToPlayer(&buf[next_out], oldi - next_out);
4047                         next_out = oldi;
4048                     }
4049                     Colorize(ColorNormal, FALSE);
4050                     curColor = ColorNormal;
4051                 }
4052                 if (started == STARTED_BOARD) {
4053                     started = STARTED_NONE;
4054                     parse[parse_pos] = NULLCHAR;
4055                     ParseBoard12(parse);
4056                     ics_user_moved = 0;
4057
4058                     /* Send premove here */
4059                     if (appData.premove) {
4060                       char str[MSG_SIZ];
4061                       if (currentMove == 0 &&
4062                           gameMode == IcsPlayingWhite &&
4063                           appData.premoveWhite) {
4064                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
4065                         if (appData.debugMode)
4066                           fprintf(debugFP, "Sending premove:\n");
4067                         SendToICS(str);
4068                       } else if (currentMove == 1 &&
4069                                  gameMode == IcsPlayingBlack &&
4070                                  appData.premoveBlack) {
4071                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
4072                         if (appData.debugMode)
4073                           fprintf(debugFP, "Sending premove:\n");
4074                         SendToICS(str);
4075                       } else if (gotPremove) {
4076                         gotPremove = 0;
4077                         ClearPremoveHighlights();
4078                         if (appData.debugMode)
4079                           fprintf(debugFP, "Sending premove:\n");
4080                           UserMoveEvent(premoveFromX, premoveFromY,
4081                                         premoveToX, premoveToY,
4082                                         premovePromoChar);
4083                       }
4084                     }
4085
4086                     /* Usually suppress following prompt */
4087                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4088                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4089                         if (looking_at(buf, &i, "*% ")) {
4090                             savingComment = FALSE;
4091                             suppressKibitz = 0;
4092                         }
4093                     }
4094                     next_out = i;
4095                 } else if (started == STARTED_HOLDINGS) {
4096                     int gamenum;
4097                     char new_piece[MSG_SIZ];
4098                     started = STARTED_NONE;
4099                     parse[parse_pos] = NULLCHAR;
4100                     if (appData.debugMode)
4101                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4102                                                         parse, currentMove);
4103                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4104                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4105                         if (gameInfo.variant == VariantNormal) {
4106                           /* [HGM] We seem to switch variant during a game!
4107                            * Presumably no holdings were displayed, so we have
4108                            * to move the position two files to the right to
4109                            * create room for them!
4110                            */
4111                           VariantClass newVariant;
4112                           switch(gameInfo.boardWidth) { // base guess on board width
4113                                 case 9:  newVariant = VariantShogi; break;
4114                                 case 10: newVariant = VariantGreat; break;
4115                                 default: newVariant = VariantCrazyhouse; break;
4116                           }
4117                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4118                           /* Get a move list just to see the header, which
4119                              will tell us whether this is really bug or zh */
4120                           if (ics_getting_history == H_FALSE) {
4121                             ics_getting_history = H_REQUESTED;
4122                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4123                             SendToICS(str);
4124                           }
4125                         }
4126                         new_piece[0] = NULLCHAR;
4127                         sscanf(parse, "game %d white [%s black [%s <- %s",
4128                                &gamenum, white_holding, black_holding,
4129                                new_piece);
4130                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4131                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4132                         /* [HGM] copy holdings to board holdings area */
4133                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4134                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4135                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4136 #if ZIPPY
4137                         if (appData.zippyPlay && first.initDone) {
4138                             ZippyHoldings(white_holding, black_holding,
4139                                           new_piece);
4140                         }
4141 #endif /*ZIPPY*/
4142                         if (tinyLayout || smallLayout) {
4143                             char wh[16], bh[16];
4144                             PackHolding(wh, white_holding);
4145                             PackHolding(bh, black_holding);
4146                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4147                                     gameInfo.white, gameInfo.black);
4148                         } else {
4149                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4150                                     gameInfo.white, white_holding, _("vs."),
4151                                     gameInfo.black, black_holding);
4152                         }
4153                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4154                         DrawPosition(FALSE, boards[currentMove]);
4155                         DisplayTitle(str);
4156                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4157                         sscanf(parse, "game %d white [%s black [%s <- %s",
4158                                &gamenum, white_holding, black_holding,
4159                                new_piece);
4160                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4161                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4162                         /* [HGM] copy holdings to partner-board holdings area */
4163                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4164                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4165                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4166                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4167                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4168                       }
4169                     }
4170                     /* Suppress following prompt */
4171                     if (looking_at(buf, &i, "*% ")) {
4172                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4173                         savingComment = FALSE;
4174                         suppressKibitz = 0;
4175                     }
4176                     next_out = i;
4177                 }
4178                 continue;
4179             }
4180
4181             i++;                /* skip unparsed character and loop back */
4182         }
4183
4184         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4185 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4186 //          SendToPlayer(&buf[next_out], i - next_out);
4187             started != STARTED_HOLDINGS && leftover_start > next_out) {
4188             SendToPlayer(&buf[next_out], leftover_start - next_out);
4189             next_out = i;
4190         }
4191
4192         leftover_len = buf_len - leftover_start;
4193         /* if buffer ends with something we couldn't parse,
4194            reparse it after appending the next read */
4195
4196     } else if (count == 0) {
4197         RemoveInputSource(isr);
4198         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4199     } else {
4200         DisplayFatalError(_("Error reading from ICS"), error, 1);
4201     }
4202 }
4203
4204
4205 /* Board style 12 looks like this:
4206
4207    <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
4208
4209  * The "<12> " is stripped before it gets to this routine.  The two
4210  * trailing 0's (flip state and clock ticking) are later addition, and
4211  * some chess servers may not have them, or may have only the first.
4212  * Additional trailing fields may be added in the future.
4213  */
4214
4215 #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"
4216
4217 #define RELATION_OBSERVING_PLAYED    0
4218 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4219 #define RELATION_PLAYING_MYMOVE      1
4220 #define RELATION_PLAYING_NOTMYMOVE  -1
4221 #define RELATION_EXAMINING           2
4222 #define RELATION_ISOLATED_BOARD     -3
4223 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4224
4225 void
4226 ParseBoard12 (char *string)
4227 {
4228 #if ZIPPY
4229     int i, takeback;
4230     char *bookHit = NULL; // [HGM] book
4231 #endif
4232     GameMode newGameMode;
4233     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
4234     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
4235     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4236     char to_play, board_chars[200];
4237     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4238     char black[32], white[32];
4239     Board board;
4240     int prevMove = currentMove;
4241     int ticking = 2;
4242     ChessMove moveType;
4243     int fromX, fromY, toX, toY;
4244     char promoChar;
4245     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4246     Boolean weird = FALSE, reqFlag = FALSE;
4247
4248     fromX = fromY = toX = toY = -1;
4249
4250     newGame = FALSE;
4251
4252     if (appData.debugMode)
4253       fprintf(debugFP, "Parsing board: %s\n", string);
4254
4255     move_str[0] = NULLCHAR;
4256     elapsed_time[0] = NULLCHAR;
4257     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4258         int  i = 0, j;
4259         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4260             if(string[i] == ' ') { ranks++; files = 0; }
4261             else files++;
4262             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4263             i++;
4264         }
4265         for(j = 0; j <i; j++) board_chars[j] = string[j];
4266         board_chars[i] = '\0';
4267         string += i + 1;
4268     }
4269     n = sscanf(string, PATTERN, &to_play, &double_push,
4270                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4271                &gamenum, white, black, &relation, &basetime, &increment,
4272                &white_stren, &black_stren, &white_time, &black_time,
4273                &moveNum, str, elapsed_time, move_str, &ics_flip,
4274                &ticking);
4275
4276     if (n < 21) {
4277         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4278         DisplayError(str, 0);
4279         return;
4280     }
4281
4282     /* Convert the move number to internal form */
4283     moveNum = (moveNum - 1) * 2;
4284     if (to_play == 'B') moveNum++;
4285     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4286       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4287                         0, 1);
4288       return;
4289     }
4290
4291     switch (relation) {
4292       case RELATION_OBSERVING_PLAYED:
4293       case RELATION_OBSERVING_STATIC:
4294         if (gamenum == -1) {
4295             /* Old ICC buglet */
4296             relation = RELATION_OBSERVING_STATIC;
4297         }
4298         newGameMode = IcsObserving;
4299         break;
4300       case RELATION_PLAYING_MYMOVE:
4301       case RELATION_PLAYING_NOTMYMOVE:
4302         newGameMode =
4303           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4304             IcsPlayingWhite : IcsPlayingBlack;
4305         soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4306         break;
4307       case RELATION_EXAMINING:
4308         newGameMode = IcsExamining;
4309         break;
4310       case RELATION_ISOLATED_BOARD:
4311       default:
4312         /* Just display this board.  If user was doing something else,
4313            we will forget about it until the next board comes. */
4314         newGameMode = IcsIdle;
4315         break;
4316       case RELATION_STARTING_POSITION:
4317         newGameMode = gameMode;
4318         break;
4319     }
4320
4321     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4322         gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4323          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4324       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4325       int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4326       static int lastBgGame = -1;
4327       char *toSqr;
4328       for (k = 0; k < ranks; k++) {
4329         for (j = 0; j < files; j++)
4330           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4331         if(gameInfo.holdingsWidth > 1) {
4332              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4333              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4334         }
4335       }
4336       CopyBoard(partnerBoard, board);
4337       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4338         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4339         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4340       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4341       if(toSqr = strchr(str, '-')) {
4342         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4343         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4344       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4345       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4346       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4347       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4348       if(twoBoards) {
4349           DisplayWhiteClock(white_time*fac, to_play == 'W');
4350           DisplayBlackClock(black_time*fac, to_play != 'W');
4351           activePartner = to_play;
4352           if(gamenum != lastBgGame) {
4353               char buf[MSG_SIZ];
4354               snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4355               DisplayTitle(buf);
4356           }
4357           lastBgGame = gamenum;
4358           activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4359                       partnerUp = 0; flipView = !flipView; } // [HGM] dual
4360       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4361                  (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4362       if(!twoBoards) DisplayMessage(partnerStatus, "");
4363         partnerBoardValid = TRUE;
4364       return;
4365     }
4366
4367     if(appData.dualBoard && appData.bgObserve) {
4368         if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4369             SendToICS(ics_prefix), SendToICS("pobserve\n");
4370         else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4371             char buf[MSG_SIZ];
4372             snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4373             SendToICS(buf);
4374         }
4375     }
4376
4377     /* Modify behavior for initial board display on move listing
4378        of wild games.
4379        */
4380     switch (ics_getting_history) {
4381       case H_FALSE:
4382       case H_REQUESTED:
4383         break;
4384       case H_GOT_REQ_HEADER:
4385       case H_GOT_UNREQ_HEADER:
4386         /* This is the initial position of the current game */
4387         gamenum = ics_gamenum;
4388         moveNum = 0;            /* old ICS bug workaround */
4389         if (to_play == 'B') {
4390           startedFromSetupPosition = TRUE;
4391           blackPlaysFirst = TRUE;
4392           moveNum = 1;
4393           if (forwardMostMove == 0) forwardMostMove = 1;
4394           if (backwardMostMove == 0) backwardMostMove = 1;
4395           if (currentMove == 0) currentMove = 1;
4396         }
4397         newGameMode = gameMode;
4398         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4399         break;
4400       case H_GOT_UNWANTED_HEADER:
4401         /* This is an initial board that we don't want */
4402         return;
4403       case H_GETTING_MOVES:
4404         /* Should not happen */
4405         DisplayError(_("Error gathering move list: extra board"), 0);
4406         ics_getting_history = H_FALSE;
4407         return;
4408     }
4409
4410    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4411                                         move_str[1] == '@' && !gameInfo.holdingsWidth ||
4412                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4413      /* [HGM] We seem to have switched variant unexpectedly
4414       * Try to guess new variant from board size
4415       */
4416           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4417           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4418           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4419           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4420           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4421           if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4422           if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4423           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4424           /* Get a move list just to see the header, which
4425              will tell us whether this is really bug or zh */
4426           if (ics_getting_history == H_FALSE) {
4427             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4428             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4429             SendToICS(str);
4430           }
4431     }
4432
4433     /* Take action if this is the first board of a new game, or of a
4434        different game than is currently being displayed.  */
4435     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4436         relation == RELATION_ISOLATED_BOARD) {
4437
4438         /* Forget the old game and get the history (if any) of the new one */
4439         if (gameMode != BeginningOfGame) {
4440           Reset(TRUE, TRUE);
4441         }
4442         newGame = TRUE;
4443         if (appData.autoRaiseBoard) BoardToTop();
4444         prevMove = -3;
4445         if (gamenum == -1) {
4446             newGameMode = IcsIdle;
4447         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4448                    appData.getMoveList && !reqFlag) {
4449             /* Need to get game history */
4450             ics_getting_history = H_REQUESTED;
4451             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4452             SendToICS(str);
4453         }
4454
4455         /* Initially flip the board to have black on the bottom if playing
4456            black or if the ICS flip flag is set, but let the user change
4457            it with the Flip View button. */
4458         flipView = appData.autoFlipView ?
4459           (newGameMode == IcsPlayingBlack) || ics_flip :
4460           appData.flipView;
4461
4462         /* Done with values from previous mode; copy in new ones */
4463         gameMode = newGameMode;
4464         ModeHighlight();
4465         ics_gamenum = gamenum;
4466         if (gamenum == gs_gamenum) {
4467             int klen = strlen(gs_kind);
4468             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4469             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4470             gameInfo.event = StrSave(str);
4471         } else {
4472             gameInfo.event = StrSave("ICS game");
4473         }
4474         gameInfo.site = StrSave(appData.icsHost);
4475         gameInfo.date = PGNDate();
4476         gameInfo.round = StrSave("-");
4477         gameInfo.white = StrSave(white);
4478         gameInfo.black = StrSave(black);
4479         timeControl = basetime * 60 * 1000;
4480         timeControl_2 = 0;
4481         timeIncrement = increment * 1000;
4482         movesPerSession = 0;
4483         gameInfo.timeControl = TimeControlTagValue();
4484         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4485   if (appData.debugMode) {
4486     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4487     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4488     setbuf(debugFP, NULL);
4489   }
4490
4491         gameInfo.outOfBook = NULL;
4492
4493         /* Do we have the ratings? */
4494         if (strcmp(player1Name, white) == 0 &&
4495             strcmp(player2Name, black) == 0) {
4496             if (appData.debugMode)
4497               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4498                       player1Rating, player2Rating);
4499             gameInfo.whiteRating = player1Rating;
4500             gameInfo.blackRating = player2Rating;
4501         } else if (strcmp(player2Name, white) == 0 &&
4502                    strcmp(player1Name, black) == 0) {
4503             if (appData.debugMode)
4504               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4505                       player2Rating, player1Rating);
4506             gameInfo.whiteRating = player2Rating;
4507             gameInfo.blackRating = player1Rating;
4508         }
4509         player1Name[0] = player2Name[0] = NULLCHAR;
4510
4511         /* Silence shouts if requested */
4512         if (appData.quietPlay &&
4513             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4514             SendToICS(ics_prefix);
4515             SendToICS("set shout 0\n");
4516         }
4517     }
4518
4519     /* Deal with midgame name changes */
4520     if (!newGame) {
4521         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4522             if (gameInfo.white) free(gameInfo.white);
4523             gameInfo.white = StrSave(white);
4524         }
4525         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4526             if (gameInfo.black) free(gameInfo.black);
4527             gameInfo.black = StrSave(black);
4528         }
4529     }
4530
4531     /* Throw away game result if anything actually changes in examine mode */
4532     if (gameMode == IcsExamining && !newGame) {
4533         gameInfo.result = GameUnfinished;
4534         if (gameInfo.resultDetails != NULL) {
4535             free(gameInfo.resultDetails);
4536             gameInfo.resultDetails = NULL;
4537         }
4538     }
4539
4540     /* In pausing && IcsExamining mode, we ignore boards coming
4541        in if they are in a different variation than we are. */
4542     if (pauseExamInvalid) return;
4543     if (pausing && gameMode == IcsExamining) {
4544         if (moveNum <= pauseExamForwardMostMove) {
4545             pauseExamInvalid = TRUE;
4546             forwardMostMove = pauseExamForwardMostMove;
4547             return;
4548         }
4549     }
4550
4551   if (appData.debugMode) {
4552     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4553   }
4554     /* Parse the board */
4555     for (k = 0; k < ranks; k++) {
4556       for (j = 0; j < files; j++)
4557         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4558       if(gameInfo.holdingsWidth > 1) {
4559            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4560            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4561       }
4562     }
4563     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4564       board[5][BOARD_RGHT+1] = WhiteAngel;
4565       board[6][BOARD_RGHT+1] = WhiteMarshall;
4566       board[1][0] = BlackMarshall;
4567       board[2][0] = BlackAngel;
4568       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4569     }
4570     CopyBoard(boards[moveNum], board);
4571     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4572     if (moveNum == 0) {
4573         startedFromSetupPosition =
4574           !CompareBoards(board, initialPosition);
4575         if(startedFromSetupPosition)
4576             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4577     }
4578
4579     /* [HGM] Set castling rights. Take the outermost Rooks,
4580        to make it also work for FRC opening positions. Note that board12
4581        is really defective for later FRC positions, as it has no way to
4582        indicate which Rook can castle if they are on the same side of King.
4583        For the initial position we grant rights to the outermost Rooks,
4584        and remember thos rights, and we then copy them on positions
4585        later in an FRC game. This means WB might not recognize castlings with
4586        Rooks that have moved back to their original position as illegal,
4587        but in ICS mode that is not its job anyway.
4588     */
4589     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4590     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4591
4592         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4593             if(board[0][i] == WhiteRook) j = i;
4594         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4595         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4596             if(board[0][i] == WhiteRook) j = i;
4597         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4598         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4599             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4600         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4601         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4602             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4603         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4604
4605         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4606         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4607         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4608             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4609         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4610             if(board[BOARD_HEIGHT-1][k] == bKing)
4611                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4612         if(gameInfo.variant == VariantTwoKings) {
4613             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4614             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4615             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4616         }
4617     } else { int r;
4618         r = boards[moveNum][CASTLING][0] = initialRights[0];
4619         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4620         r = boards[moveNum][CASTLING][1] = initialRights[1];
4621         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4622         r = boards[moveNum][CASTLING][3] = initialRights[3];
4623         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4624         r = boards[moveNum][CASTLING][4] = initialRights[4];
4625         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4626         /* wildcastle kludge: always assume King has rights */
4627         r = boards[moveNum][CASTLING][2] = initialRights[2];
4628         r = boards[moveNum][CASTLING][5] = initialRights[5];
4629     }
4630     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4631     boards[moveNum][EP_STATUS] = EP_NONE;
4632     if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4633     if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4634     if(double_push !=  -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4635
4636
4637     if (ics_getting_history == H_GOT_REQ_HEADER ||
4638         ics_getting_history == H_GOT_UNREQ_HEADER) {
4639         /* This was an initial position from a move list, not
4640            the current position */
4641         return;
4642     }
4643
4644     /* Update currentMove and known move number limits */
4645     newMove = newGame || moveNum > forwardMostMove;
4646
4647     if (newGame) {
4648         forwardMostMove = backwardMostMove = currentMove = moveNum;
4649         if (gameMode == IcsExamining && moveNum == 0) {
4650           /* Workaround for ICS limitation: we are not told the wild
4651              type when starting to examine a game.  But if we ask for
4652              the move list, the move list header will tell us */
4653             ics_getting_history = H_REQUESTED;
4654             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4655             SendToICS(str);
4656         }
4657     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4658                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4659 #if ZIPPY
4660         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4661         /* [HGM] applied this also to an engine that is silently watching        */
4662         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4663             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4664             gameInfo.variant == currentlyInitializedVariant) {
4665           takeback = forwardMostMove - moveNum;
4666           for (i = 0; i < takeback; i++) {
4667             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4668             SendToProgram("undo\n", &first);
4669           }
4670         }
4671 #endif
4672
4673         forwardMostMove = moveNum;
4674         if (!pausing || currentMove > forwardMostMove)
4675           currentMove = forwardMostMove;
4676     } else {
4677         /* New part of history that is not contiguous with old part */
4678         if (pausing && gameMode == IcsExamining) {
4679             pauseExamInvalid = TRUE;
4680             forwardMostMove = pauseExamForwardMostMove;
4681             return;
4682         }
4683         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4684 #if ZIPPY
4685             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4686                 // [HGM] when we will receive the move list we now request, it will be
4687                 // fed to the engine from the first move on. So if the engine is not
4688                 // in the initial position now, bring it there.
4689                 InitChessProgram(&first, 0);
4690             }
4691 #endif
4692             ics_getting_history = H_REQUESTED;
4693             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4694             SendToICS(str);
4695         }
4696         forwardMostMove = backwardMostMove = currentMove = moveNum;
4697     }
4698
4699     /* Update the clocks */
4700     if (strchr(elapsed_time, '.')) {
4701       /* Time is in ms */
4702       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4703       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4704     } else {
4705       /* Time is in seconds */
4706       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4707       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4708     }
4709
4710
4711 #if ZIPPY
4712     if (appData.zippyPlay && newGame &&
4713         gameMode != IcsObserving && gameMode != IcsIdle &&
4714         gameMode != IcsExamining)
4715       ZippyFirstBoard(moveNum, basetime, increment);
4716 #endif
4717
4718     /* Put the move on the move list, first converting
4719        to canonical algebraic form. */
4720     if (moveNum > 0) {
4721   if (appData.debugMode) {
4722     int f = forwardMostMove;
4723     fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4724             boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4725             boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4726     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4727     fprintf(debugFP, "moveNum = %d\n", moveNum);
4728     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4729     setbuf(debugFP, NULL);
4730   }
4731         if (moveNum <= backwardMostMove) {
4732             /* We don't know what the board looked like before
4733                this move.  Punt. */
4734           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4735             strcat(parseList[moveNum - 1], " ");
4736             strcat(parseList[moveNum - 1], elapsed_time);
4737             moveList[moveNum - 1][0] = NULLCHAR;
4738         } else if (strcmp(move_str, "none") == 0) {
4739             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4740             /* Again, we don't know what the board looked like;
4741                this is really the start of the game. */
4742             parseList[moveNum - 1][0] = NULLCHAR;
4743             moveList[moveNum - 1][0] = NULLCHAR;
4744             backwardMostMove = moveNum;
4745             startedFromSetupPosition = TRUE;
4746             fromX = fromY = toX = toY = -1;
4747         } else {
4748           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4749           //                 So we parse the long-algebraic move string in stead of the SAN move
4750           int valid; char buf[MSG_SIZ], *prom;
4751
4752           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4753                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4754           // str looks something like "Q/a1-a2"; kill the slash
4755           if(str[1] == '/')
4756             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4757           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4758           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4759                 strcat(buf, prom); // long move lacks promo specification!
4760           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4761                 if(appData.debugMode)
4762                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4763                 safeStrCpy(move_str, buf, MSG_SIZ);
4764           }
4765           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4766                                 &fromX, &fromY, &toX, &toY, &promoChar)
4767                || ParseOneMove(buf, moveNum - 1, &moveType,
4768                                 &fromX, &fromY, &toX, &toY, &promoChar);
4769           // end of long SAN patch
4770           if (valid) {
4771             (void) CoordsToAlgebraic(boards[moveNum - 1],
4772                                      PosFlags(moveNum - 1),
4773                                      fromY, fromX, toY, toX, promoChar,
4774                                      parseList[moveNum-1]);
4775             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4776               case MT_NONE:
4777               case MT_STALEMATE:
4778               default:
4779                 break;
4780               case MT_CHECK:
4781                 if(gameInfo.variant != VariantShogi)
4782                     strcat(parseList[moveNum - 1], "+");
4783                 break;
4784               case MT_CHECKMATE:
4785               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4786                 strcat(parseList[moveNum - 1], "#");
4787                 break;
4788             }
4789             strcat(parseList[moveNum - 1], " ");
4790             strcat(parseList[moveNum - 1], elapsed_time);
4791             /* currentMoveString is set as a side-effect of ParseOneMove */
4792             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4793             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4794             strcat(moveList[moveNum - 1], "\n");
4795
4796             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4797                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4798               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4799                 ChessSquare old, new = boards[moveNum][k][j];
4800                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4801                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4802                   if(old == new) continue;
4803                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4804                   else if(new == WhiteWazir || new == BlackWazir) {
4805                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4806                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4807                       else boards[moveNum][k][j] = old; // preserve type of Gold
4808                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4809                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4810               }
4811           } else {
4812             /* Move from ICS was illegal!?  Punt. */
4813             if (appData.debugMode) {
4814               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4815               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4816             }
4817             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4818             strcat(parseList[moveNum - 1], " ");
4819             strcat(parseList[moveNum - 1], elapsed_time);
4820             moveList[moveNum - 1][0] = NULLCHAR;
4821             fromX = fromY = toX = toY = -1;
4822           }
4823         }
4824   if (appData.debugMode) {
4825     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4826     setbuf(debugFP, NULL);
4827   }
4828
4829 #if ZIPPY
4830         /* Send move to chess program (BEFORE animating it). */
4831         if (appData.zippyPlay && !newGame && newMove &&
4832            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4833
4834             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4835                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4836                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4837                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4838                             move_str);
4839                     DisplayError(str, 0);
4840                 } else {
4841                     if (first.sendTime) {
4842                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4843                     }
4844                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4845                     if (firstMove && !bookHit) {
4846                         firstMove = FALSE;
4847                         if (first.useColors) {
4848                           SendToProgram(gameMode == IcsPlayingWhite ?
4849                                         "white\ngo\n" :
4850                                         "black\ngo\n", &first);
4851                         } else {
4852                           SendToProgram("go\n", &first);
4853                         }
4854                         first.maybeThinking = TRUE;
4855                     }
4856                 }
4857             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4858               if (moveList[moveNum - 1][0] == NULLCHAR) {
4859                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4860                 DisplayError(str, 0);
4861               } else {
4862                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4863                 SendMoveToProgram(moveNum - 1, &first);
4864               }
4865             }
4866         }
4867 #endif
4868     }
4869
4870     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4871         /* If move comes from a remote source, animate it.  If it
4872            isn't remote, it will have already been animated. */
4873         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4874             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4875         }
4876         if (!pausing && appData.highlightLastMove) {
4877             SetHighlights(fromX, fromY, toX, toY);
4878         }
4879     }
4880
4881     /* Start the clocks */
4882     whiteFlag = blackFlag = FALSE;
4883     appData.clockMode = !(basetime == 0 && increment == 0);
4884     if (ticking == 0) {
4885       ics_clock_paused = TRUE;
4886       StopClocks();
4887     } else if (ticking == 1) {
4888       ics_clock_paused = FALSE;
4889     }
4890     if (gameMode == IcsIdle ||
4891         relation == RELATION_OBSERVING_STATIC ||
4892         relation == RELATION_EXAMINING ||
4893         ics_clock_paused)
4894       DisplayBothClocks();
4895     else
4896       StartClocks();
4897
4898     /* Display opponents and material strengths */
4899     if (gameInfo.variant != VariantBughouse &&
4900         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4901         if (tinyLayout || smallLayout) {
4902             if(gameInfo.variant == VariantNormal)
4903               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4904                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4905                     basetime, increment);
4906             else
4907               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4908                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4909                     basetime, increment, (int) gameInfo.variant);
4910         } else {
4911             if(gameInfo.variant == VariantNormal)
4912               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4913                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4914                     basetime, increment);
4915             else
4916               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
4917                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4918                     basetime, increment, VariantName(gameInfo.variant));
4919         }
4920         DisplayTitle(str);
4921   if (appData.debugMode) {
4922     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4923   }
4924     }
4925
4926
4927     /* Display the board */
4928     if (!pausing && !appData.noGUI) {
4929
4930       if (appData.premove)
4931           if (!gotPremove ||
4932              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4933              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4934               ClearPremoveHighlights();
4935
4936       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4937         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4938       DrawPosition(j, boards[currentMove]);
4939
4940       DisplayMove(moveNum - 1);
4941       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4942             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4943               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4944         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4945       }
4946     }
4947
4948     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4949 #if ZIPPY
4950     if(bookHit) { // [HGM] book: simulate book reply
4951         static char bookMove[MSG_SIZ]; // a bit generous?
4952
4953         programStats.nodes = programStats.depth = programStats.time =
4954         programStats.score = programStats.got_only_move = 0;
4955         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4956
4957         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4958         strcat(bookMove, bookHit);
4959         HandleMachineMove(bookMove, &first);
4960     }
4961 #endif
4962 }
4963
4964 void
4965 GetMoveListEvent ()
4966 {
4967     char buf[MSG_SIZ];
4968     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4969         ics_getting_history = H_REQUESTED;
4970         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4971         SendToICS(buf);
4972     }
4973 }
4974
4975 void
4976 SendToBoth (char *msg)
4977 {   // to make it easy to keep two engines in step in dual analysis
4978     SendToProgram(msg, &first);
4979     if(second.analyzing) SendToProgram(msg, &second);
4980 }
4981
4982 void
4983 AnalysisPeriodicEvent (int force)
4984 {
4985     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4986          && !force) || !appData.periodicUpdates)
4987       return;
4988
4989     /* Send . command to Crafty to collect stats */
4990     SendToBoth(".\n");
4991
4992     /* Don't send another until we get a response (this makes
4993        us stop sending to old Crafty's which don't understand
4994        the "." command (sending illegal cmds resets node count & time,
4995        which looks bad)) */
4996     programStats.ok_to_send = 0;
4997 }
4998
4999 void
5000 ics_update_width (int new_width)
5001 {
5002         ics_printf("set width %d\n", new_width);
5003 }
5004
5005 void
5006 SendMoveToProgram (int moveNum, ChessProgramState *cps)
5007 {
5008     char buf[MSG_SIZ];
5009
5010     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
5011         // null move in variant where engine does not understand it (for analysis purposes)
5012         SendBoard(cps, moveNum + 1); // send position after move in stead.
5013         return;
5014     }
5015     if (cps->useUsermove) {
5016       SendToProgram("usermove ", cps);
5017     }
5018     if (cps->useSAN) {
5019       char *space;
5020       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
5021         int len = space - parseList[moveNum];
5022         memcpy(buf, parseList[moveNum], len);
5023         buf[len++] = '\n';
5024         buf[len] = NULLCHAR;
5025       } else {
5026         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
5027       }
5028       SendToProgram(buf, cps);
5029     } else {
5030       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
5031         AlphaRank(moveList[moveNum], 4);
5032         SendToProgram(moveList[moveNum], cps);
5033         AlphaRank(moveList[moveNum], 4); // and back
5034       } else
5035       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
5036        * the engine. It would be nice to have a better way to identify castle
5037        * moves here. */
5038       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
5039                                                                          && cps->useOOCastle) {
5040         int fromX = moveList[moveNum][0] - AAA;
5041         int fromY = moveList[moveNum][1] - ONE;
5042         int toX = moveList[moveNum][2] - AAA;
5043         int toY = moveList[moveNum][3] - ONE;
5044         if((boards[moveNum][fromY][fromX] == WhiteKing
5045             && boards[moveNum][toY][toX] == WhiteRook)
5046            || (boards[moveNum][fromY][fromX] == BlackKing
5047                && boards[moveNum][toY][toX] == BlackRook)) {
5048           if(toX > fromX) SendToProgram("O-O\n", cps);
5049           else SendToProgram("O-O-O\n", cps);
5050         }
5051         else SendToProgram(moveList[moveNum], cps);
5052       } else
5053       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5054         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5055           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5056           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5057                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5058         } else if(moveList[moveNum][4] == ';') { // [HGM] lion: move is double-step over intermediate square
5059           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", moveList[moveNum][0], moveList[moveNum][1] - '0', // convert to two moves
5060                                                moveList[moveNum][5], moveList[moveNum][6] - '0',
5061                                                moveList[moveNum][5], moveList[moveNum][6] - '0',
5062                                                moveList[moveNum][2], moveList[moveNum][3] - '0');
5063         } else
5064           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5065                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5066         SendToProgram(buf, cps);
5067       }
5068       else SendToProgram(moveList[moveNum], cps);
5069       /* End of additions by Tord */
5070     }
5071
5072     /* [HGM] setting up the opening has brought engine in force mode! */
5073     /*       Send 'go' if we are in a mode where machine should play. */
5074     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5075         (gameMode == TwoMachinesPlay   ||
5076 #if ZIPPY
5077          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
5078 #endif
5079          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5080         SendToProgram("go\n", cps);
5081   if (appData.debugMode) {
5082     fprintf(debugFP, "(extra)\n");
5083   }
5084     }
5085     setboardSpoiledMachineBlack = 0;
5086 }
5087
5088 void
5089 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5090 {
5091     char user_move[MSG_SIZ];
5092     char suffix[4];
5093
5094     if(gameInfo.variant == VariantSChess && promoChar) {
5095         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5096         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5097     } else suffix[0] = NULLCHAR;
5098
5099     switch (moveType) {
5100       default:
5101         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5102                 (int)moveType, fromX, fromY, toX, toY);
5103         DisplayError(user_move + strlen("say "), 0);
5104         break;
5105       case WhiteKingSideCastle:
5106       case BlackKingSideCastle:
5107       case WhiteQueenSideCastleWild:
5108       case BlackQueenSideCastleWild:
5109       /* PUSH Fabien */
5110       case WhiteHSideCastleFR:
5111       case BlackHSideCastleFR:
5112       /* POP Fabien */
5113         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5114         break;
5115       case WhiteQueenSideCastle:
5116       case BlackQueenSideCastle:
5117       case WhiteKingSideCastleWild:
5118       case BlackKingSideCastleWild:
5119       /* PUSH Fabien */
5120       case WhiteASideCastleFR:
5121       case BlackASideCastleFR:
5122       /* POP Fabien */
5123         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5124         break;
5125       case WhiteNonPromotion:
5126       case BlackNonPromotion:
5127         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5128         break;
5129       case WhitePromotion:
5130       case BlackPromotion:
5131         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
5132            gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
5133           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5134                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5135                 PieceToChar(WhiteFerz));
5136         else if(gameInfo.variant == VariantGreat)
5137           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5138                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5139                 PieceToChar(WhiteMan));
5140         else
5141           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5142                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5143                 promoChar);
5144         break;
5145       case WhiteDrop:
5146       case BlackDrop:
5147       drop:
5148         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5149                  ToUpper(PieceToChar((ChessSquare) fromX)),
5150                  AAA + toX, ONE + toY);
5151         break;
5152       case IllegalMove:  /* could be a variant we don't quite understand */
5153         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5154       case NormalMove:
5155       case WhiteCapturesEnPassant:
5156       case BlackCapturesEnPassant:
5157         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5158                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5159         break;
5160     }
5161     SendToICS(user_move);
5162     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5163         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5164 }
5165
5166 void
5167 UploadGameEvent ()
5168 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5169     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5170     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5171     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5172       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5173       return;
5174     }
5175     if(gameMode != IcsExamining) { // is this ever not the case?
5176         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5177
5178         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5179           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5180         } else { // on FICS we must first go to general examine mode
5181           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5182         }
5183         if(gameInfo.variant != VariantNormal) {
5184             // try figure out wild number, as xboard names are not always valid on ICS
5185             for(i=1; i<=36; i++) {
5186               snprintf(buf, MSG_SIZ, "wild/%d", i);
5187                 if(StringToVariant(buf) == gameInfo.variant) break;
5188             }
5189             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5190             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5191             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5192         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5193         SendToICS(ics_prefix);
5194         SendToICS(buf);
5195         if(startedFromSetupPosition || backwardMostMove != 0) {
5196           fen = PositionToFEN(backwardMostMove, NULL, 1);
5197           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5198             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5199             SendToICS(buf);
5200           } else { // FICS: everything has to set by separate bsetup commands
5201             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5202             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5203             SendToICS(buf);
5204             if(!WhiteOnMove(backwardMostMove)) {
5205                 SendToICS("bsetup tomove black\n");
5206             }
5207             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5208             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5209             SendToICS(buf);
5210             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5211             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5212             SendToICS(buf);
5213             i = boards[backwardMostMove][EP_STATUS];
5214             if(i >= 0) { // set e.p.
5215               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5216                 SendToICS(buf);
5217             }
5218             bsetup++;
5219           }
5220         }
5221       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5222             SendToICS("bsetup done\n"); // switch to normal examining.
5223     }
5224     for(i = backwardMostMove; i<last; i++) {
5225         char buf[20];
5226         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5227         if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5228             int len = strlen(moveList[i]);
5229             snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5230             if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5231         }
5232         SendToICS(buf);
5233     }
5234     SendToICS(ics_prefix);
5235     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5236 }
5237
5238 static int killX = -1, killY = -1; // [HGM] lion: used for passing e.p. capture square to MakeMove
5239
5240 void
5241 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5242 {
5243     if (rf == DROP_RANK) {
5244       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5245       sprintf(move, "%c@%c%c\n",
5246                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5247     } else {
5248         if (promoChar == 'x' || promoChar == NULLCHAR) {
5249           sprintf(move, "%c%c%c%c\n",
5250                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5251           if(killX >= 0 && killY >= 0) sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY);
5252         } else {
5253             sprintf(move, "%c%c%c%c%c\n",
5254                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5255         }
5256     }
5257 }
5258
5259 void
5260 ProcessICSInitScript (FILE *f)
5261 {
5262     char buf[MSG_SIZ];
5263
5264     while (fgets(buf, MSG_SIZ, f)) {
5265         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5266     }
5267
5268     fclose(f);
5269 }
5270
5271
5272 static int lastX, lastY, lastLeftX, lastLeftY, selectFlag, dragging;
5273 static ClickType lastClickType;
5274
5275 void
5276 Sweep (int step)
5277 {
5278     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5279     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5280     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5281     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5282     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5283     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5284     do {
5285         promoSweep -= step;
5286         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5287         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5288         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5289         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5290         if(!step) step = -1;
5291     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5292             appData.testLegality && promoSweep == king ||
5293             IS_SHOGI(gameInfo.variant) && promoSweep != CHUPROMOTED last && last != CHUPROMOTED promoSweep && last != promoSweep);
5294     if(toX >= 0) {
5295         int victim = boards[currentMove][toY][toX];
5296         boards[currentMove][toY][toX] = promoSweep;
5297         DrawPosition(FALSE, boards[currentMove]);
5298         boards[currentMove][toY][toX] = victim;
5299     } else
5300     ChangeDragPiece(promoSweep);
5301 }
5302
5303 int
5304 PromoScroll (int x, int y)
5305 {
5306   int step = 0;
5307
5308   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5309   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5310   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5311   if(!step) return FALSE;
5312   lastX = x; lastY = y;
5313   if((promoSweep < BlackPawn) == flipView) step = -step;
5314   if(step > 0) selectFlag = 1;
5315   if(!selectFlag) Sweep(step);
5316   return FALSE;
5317 }
5318
5319 void
5320 NextPiece (int step)
5321 {
5322     ChessSquare piece = boards[currentMove][toY][toX];
5323     do {
5324         pieceSweep -= step;
5325         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5326         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5327         if(!step) step = -1;
5328     } while(PieceToChar(pieceSweep) == '.');
5329     boards[currentMove][toY][toX] = pieceSweep;
5330     DrawPosition(FALSE, boards[currentMove]);
5331     boards[currentMove][toY][toX] = piece;
5332 }
5333 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5334 void
5335 AlphaRank (char *move, int n)
5336 {
5337 //    char *p = move, c; int x, y;
5338
5339     if (appData.debugMode) {
5340         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5341     }
5342
5343     if(move[1]=='*' &&
5344        move[2]>='0' && move[2]<='9' &&
5345        move[3]>='a' && move[3]<='x'    ) {
5346         move[1] = '@';
5347         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5348         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5349     } else
5350     if(move[0]>='0' && move[0]<='9' &&
5351        move[1]>='a' && move[1]<='x' &&
5352        move[2]>='0' && move[2]<='9' &&
5353        move[3]>='a' && move[3]<='x'    ) {
5354         /* input move, Shogi -> normal */
5355         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5356         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5357         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5358         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5359     } else
5360     if(move[1]=='@' &&
5361        move[3]>='0' && move[3]<='9' &&
5362        move[2]>='a' && move[2]<='x'    ) {
5363         move[1] = '*';
5364         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5365         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5366     } else
5367     if(
5368        move[0]>='a' && move[0]<='x' &&
5369        move[3]>='0' && move[3]<='9' &&
5370        move[2]>='a' && move[2]<='x'    ) {
5371          /* output move, normal -> Shogi */
5372         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5373         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5374         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5375         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5376         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5377     }
5378     if (appData.debugMode) {
5379         fprintf(debugFP, "   out = '%s'\n", move);
5380     }
5381 }
5382
5383 char yy_textstr[8000];
5384
5385 /* Parser for moves from gnuchess, ICS, or user typein box */
5386 Boolean
5387 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5388 {
5389     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5390
5391     switch (*moveType) {
5392       case WhitePromotion:
5393       case BlackPromotion:
5394       case WhiteNonPromotion:
5395       case BlackNonPromotion:
5396       case NormalMove:
5397       case WhiteCapturesEnPassant:
5398       case BlackCapturesEnPassant:
5399       case WhiteKingSideCastle:
5400       case WhiteQueenSideCastle:
5401       case BlackKingSideCastle:
5402       case BlackQueenSideCastle:
5403       case WhiteKingSideCastleWild:
5404       case WhiteQueenSideCastleWild:
5405       case BlackKingSideCastleWild:
5406       case BlackQueenSideCastleWild:
5407       /* Code added by Tord: */
5408       case WhiteHSideCastleFR:
5409       case WhiteASideCastleFR:
5410       case BlackHSideCastleFR:
5411       case BlackASideCastleFR:
5412       /* End of code added by Tord */
5413       case IllegalMove:         /* bug or odd chess variant */
5414         *fromX = currentMoveString[0] - AAA;
5415         *fromY = currentMoveString[1] - ONE;
5416         *toX = currentMoveString[2] - AAA;
5417         *toY = currentMoveString[3] - ONE;
5418         *promoChar = currentMoveString[4];
5419         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5420             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5421     if (appData.debugMode) {
5422         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5423     }
5424             *fromX = *fromY = *toX = *toY = 0;
5425             return FALSE;
5426         }
5427         if (appData.testLegality) {
5428           return (*moveType != IllegalMove);
5429         } else {
5430           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5431                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5432         }
5433
5434       case WhiteDrop:
5435       case BlackDrop:
5436         *fromX = *moveType == WhiteDrop ?
5437           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5438           (int) CharToPiece(ToLower(currentMoveString[0]));
5439         *fromY = DROP_RANK;
5440         *toX = currentMoveString[2] - AAA;
5441         *toY = currentMoveString[3] - ONE;
5442         *promoChar = NULLCHAR;
5443         return TRUE;
5444
5445       case AmbiguousMove:
5446       case ImpossibleMove:
5447       case EndOfFile:
5448       case ElapsedTime:
5449       case Comment:
5450       case PGNTag:
5451       case NAG:
5452       case WhiteWins:
5453       case BlackWins:
5454       case GameIsDrawn:
5455       default:
5456     if (appData.debugMode) {
5457         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5458     }
5459         /* bug? */
5460         *fromX = *fromY = *toX = *toY = 0;
5461         *promoChar = NULLCHAR;
5462         return FALSE;
5463     }
5464 }
5465
5466 Boolean pushed = FALSE;
5467 char *lastParseAttempt;
5468
5469 void
5470 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5471 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5472   int fromX, fromY, toX, toY; char promoChar;
5473   ChessMove moveType;
5474   Boolean valid;
5475   int nr = 0;
5476
5477   lastParseAttempt = pv; if(!*pv) return;    // turns out we crash when we parse an empty PV
5478   if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5479     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5480     pushed = TRUE;
5481   }
5482   endPV = forwardMostMove;
5483   do {
5484     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5485     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5486     lastParseAttempt = pv;
5487     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5488     if(!valid && nr == 0 &&
5489        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5490         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5491         // Hande case where played move is different from leading PV move
5492         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5493         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5494         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5495         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5496           endPV += 2; // if position different, keep this
5497           moveList[endPV-1][0] = fromX + AAA;
5498           moveList[endPV-1][1] = fromY + ONE;
5499           moveList[endPV-1][2] = toX + AAA;
5500           moveList[endPV-1][3] = toY + ONE;
5501           parseList[endPV-1][0] = NULLCHAR;
5502           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5503         }
5504       }
5505     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5506     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5507     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5508     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5509         valid++; // allow comments in PV
5510         continue;
5511     }
5512     nr++;
5513     if(endPV+1 > framePtr) break; // no space, truncate
5514     if(!valid) break;
5515     endPV++;
5516     CopyBoard(boards[endPV], boards[endPV-1]);
5517     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5518     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5519     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5520     CoordsToAlgebraic(boards[endPV - 1],
5521                              PosFlags(endPV - 1),
5522                              fromY, fromX, toY, toX, promoChar,
5523                              parseList[endPV - 1]);
5524   } while(valid);
5525   if(atEnd == 2) return; // used hidden, for PV conversion
5526   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5527   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5528   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5529                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5530   DrawPosition(TRUE, boards[currentMove]);
5531 }
5532
5533 int
5534 MultiPV (ChessProgramState *cps)
5535 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5536         int i;
5537         for(i=0; i<cps->nrOptions; i++)
5538             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5539                 return i;
5540         return -1;
5541 }
5542
5543 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
5544
5545 Boolean
5546 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5547 {
5548         int startPV, multi, lineStart, origIndex = index;
5549         char *p, buf2[MSG_SIZ];
5550         ChessProgramState *cps = (pane ? &second : &first);
5551
5552         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5553         lastX = x; lastY = y;
5554         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5555         lineStart = startPV = index;
5556         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5557         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5558         index = startPV;
5559         do{ while(buf[index] && buf[index] != '\n') index++;
5560         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5561         buf[index] = 0;
5562         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(cps)) >= 0) {
5563                 int n = cps->option[multi].value;
5564                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5565                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5566                 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5567                 cps->option[multi].value = n;
5568                 *start = *end = 0;
5569                 return FALSE;
5570         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5571                 ExcludeClick(origIndex - lineStart);
5572                 return FALSE;
5573         }
5574         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5575         *start = startPV; *end = index-1;
5576         extendGame = (gameMode == AnalyzeMode && appData.autoExtend);
5577         return TRUE;
5578 }
5579
5580 char *
5581 PvToSAN (char *pv)
5582 {
5583         static char buf[10*MSG_SIZ];
5584         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5585         *buf = NULLCHAR;
5586         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5587         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5588         for(i = forwardMostMove; i<endPV; i++){
5589             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5590             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5591             k += strlen(buf+k);
5592         }
5593         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5594         if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5595         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5596         endPV = savedEnd;
5597         return buf;
5598 }
5599
5600 Boolean
5601 LoadPV (int x, int y)
5602 { // called on right mouse click to load PV
5603   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5604   lastX = x; lastY = y;
5605   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5606   extendGame = FALSE;
5607   return TRUE;
5608 }
5609
5610 void
5611 UnLoadPV ()
5612 {
5613   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5614   if(endPV < 0) return;
5615   if(appData.autoCopyPV) CopyFENToClipboard();
5616   endPV = -1;
5617   if(extendGame && currentMove > forwardMostMove) {
5618         Boolean saveAnimate = appData.animate;
5619         if(pushed) {
5620             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5621                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5622             } else storedGames--; // abandon shelved tail of original game
5623         }
5624         pushed = FALSE;
5625         forwardMostMove = currentMove;
5626         currentMove = oldFMM;
5627         appData.animate = FALSE;
5628         ToNrEvent(forwardMostMove);
5629         appData.animate = saveAnimate;
5630   }
5631   currentMove = forwardMostMove;
5632   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5633   ClearPremoveHighlights();
5634   DrawPosition(TRUE, boards[currentMove]);
5635 }
5636
5637 void
5638 MovePV (int x, int y, int h)
5639 { // step through PV based on mouse coordinates (called on mouse move)
5640   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5641
5642   // we must somehow check if right button is still down (might be released off board!)
5643   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5644   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5645   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5646   if(!step) return;
5647   lastX = x; lastY = y;
5648
5649   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5650   if(endPV < 0) return;
5651   if(y < margin) step = 1; else
5652   if(y > h - margin) step = -1;
5653   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5654   currentMove += step;
5655   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5656   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5657                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5658   DrawPosition(FALSE, boards[currentMove]);
5659 }
5660
5661
5662 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5663 // All positions will have equal probability, but the current method will not provide a unique
5664 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5665 #define DARK 1
5666 #define LITE 2
5667 #define ANY 3
5668
5669 int squaresLeft[4];
5670 int piecesLeft[(int)BlackPawn];
5671 int seed, nrOfShuffles;
5672
5673 void
5674 GetPositionNumber ()
5675 {       // sets global variable seed
5676         int i;
5677
5678         seed = appData.defaultFrcPosition;
5679         if(seed < 0) { // randomize based on time for negative FRC position numbers
5680                 for(i=0; i<50; i++) seed += random();
5681                 seed = random() ^ random() >> 8 ^ random() << 8;
5682                 if(seed<0) seed = -seed;
5683         }
5684 }
5685
5686 int
5687 put (Board board, int pieceType, int rank, int n, int shade)
5688 // put the piece on the (n-1)-th empty squares of the given shade
5689 {
5690         int i;
5691
5692         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5693                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5694                         board[rank][i] = (ChessSquare) pieceType;
5695                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5696                         squaresLeft[ANY]--;
5697                         piecesLeft[pieceType]--;
5698                         return i;
5699                 }
5700         }
5701         return -1;
5702 }
5703
5704
5705 void
5706 AddOnePiece (Board board, int pieceType, int rank, int shade)
5707 // calculate where the next piece goes, (any empty square), and put it there
5708 {
5709         int i;
5710
5711         i = seed % squaresLeft[shade];
5712         nrOfShuffles *= squaresLeft[shade];
5713         seed /= squaresLeft[shade];
5714         put(board, pieceType, rank, i, shade);
5715 }
5716
5717 void
5718 AddTwoPieces (Board board, int pieceType, int rank)
5719 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5720 {
5721         int i, n=squaresLeft[ANY], j=n-1, k;
5722
5723         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5724         i = seed % k;  // pick one
5725         nrOfShuffles *= k;
5726         seed /= k;
5727         while(i >= j) i -= j--;
5728         j = n - 1 - j; i += j;
5729         put(board, pieceType, rank, j, ANY);
5730         put(board, pieceType, rank, i, ANY);
5731 }
5732
5733 void
5734 SetUpShuffle (Board board, int number)
5735 {
5736         int i, p, first=1;
5737
5738         GetPositionNumber(); nrOfShuffles = 1;
5739
5740         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5741         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5742         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5743
5744         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5745
5746         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5747             p = (int) board[0][i];
5748             if(p < (int) BlackPawn) piecesLeft[p] ++;
5749             board[0][i] = EmptySquare;
5750         }
5751
5752         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5753             // shuffles restricted to allow normal castling put KRR first
5754             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5755                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5756             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5757                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5758             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5759                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5760             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5761                 put(board, WhiteRook, 0, 0, ANY);
5762             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5763         }
5764
5765         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5766             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5767             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5768                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5769                 while(piecesLeft[p] >= 2) {
5770                     AddOnePiece(board, p, 0, LITE);
5771                     AddOnePiece(board, p, 0, DARK);
5772                 }
5773                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5774             }
5775
5776         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5777             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5778             // but we leave King and Rooks for last, to possibly obey FRC restriction
5779             if(p == (int)WhiteRook) continue;
5780             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5781             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5782         }
5783
5784         // now everything is placed, except perhaps King (Unicorn) and Rooks
5785
5786         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5787             // Last King gets castling rights
5788             while(piecesLeft[(int)WhiteUnicorn]) {
5789                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5790                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5791             }
5792
5793             while(piecesLeft[(int)WhiteKing]) {
5794                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5795                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5796             }
5797
5798
5799         } else {
5800             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5801             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5802         }
5803
5804         // Only Rooks can be left; simply place them all
5805         while(piecesLeft[(int)WhiteRook]) {
5806                 i = put(board, WhiteRook, 0, 0, ANY);
5807                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5808                         if(first) {
5809                                 first=0;
5810                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5811                         }
5812                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5813                 }
5814         }
5815         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5816             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5817         }
5818
5819         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5820 }
5821
5822 int
5823 SetCharTable (char *table, const char * map)
5824 /* [HGM] moved here from winboard.c because of its general usefulness */
5825 /*       Basically a safe strcpy that uses the last character as King */
5826 {
5827     int result = FALSE; int NrPieces;
5828
5829     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5830                     && NrPieces >= 12 && !(NrPieces&1)) {
5831         int i; /* [HGM] Accept even length from 12 to 34 */
5832
5833         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5834         for( i=0; i<NrPieces/2-1; i++ ) {
5835             table[i] = map[i];
5836             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5837         }
5838         table[(int) WhiteKing]  = map[NrPieces/2-1];
5839         table[(int) BlackKing]  = map[NrPieces-1];
5840
5841         result = TRUE;
5842     }
5843
5844     return result;
5845 }
5846
5847 void
5848 Prelude (Board board)
5849 {       // [HGM] superchess: random selection of exo-pieces
5850         int i, j, k; ChessSquare p;
5851         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5852
5853         GetPositionNumber(); // use FRC position number
5854
5855         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5856             SetCharTable(pieceToChar, appData.pieceToCharTable);
5857             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5858                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5859         }
5860
5861         j = seed%4;                 seed /= 4;
5862         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5863         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5864         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5865         j = seed%3 + (seed%3 >= j); seed /= 3;
5866         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5867         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5868         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5869         j = seed%3;                 seed /= 3;
5870         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5871         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5872         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5873         j = seed%2 + (seed%2 >= j); seed /= 2;
5874         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5875         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5876         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5877         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5878         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5879         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5880         put(board, exoPieces[0],    0, 0, ANY);
5881         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5882 }
5883
5884 void
5885 InitPosition (int redraw)
5886 {
5887     ChessSquare (* pieces)[BOARD_FILES];
5888     int i, j, pawnRow=1, pieceRows=1, overrule,
5889     oldx = gameInfo.boardWidth,
5890     oldy = gameInfo.boardHeight,
5891     oldh = gameInfo.holdingsWidth;
5892     static int oldv;
5893
5894     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5895
5896     /* [AS] Initialize pv info list [HGM] and game status */
5897     {
5898         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5899             pvInfoList[i].depth = 0;
5900             boards[i][EP_STATUS] = EP_NONE;
5901             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5902         }
5903
5904         initialRulePlies = 0; /* 50-move counter start */
5905
5906         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5907         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5908     }
5909
5910
5911     /* [HGM] logic here is completely changed. In stead of full positions */
5912     /* the initialized data only consist of the two backranks. The switch */
5913     /* selects which one we will use, which is than copied to the Board   */
5914     /* initialPosition, which for the rest is initialized by Pawns and    */
5915     /* empty squares. This initial position is then copied to boards[0],  */
5916     /* possibly after shuffling, so that it remains available.            */
5917
5918     gameInfo.holdingsWidth = 0; /* default board sizes */
5919     gameInfo.boardWidth    = 8;
5920     gameInfo.boardHeight   = 8;
5921     gameInfo.holdingsSize  = 0;
5922     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5923     for(i=0; i<BOARD_FILES-2; i++)
5924       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5925     initialPosition[EP_STATUS] = EP_NONE;
5926     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5927     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5928          SetCharTable(pieceNickName, appData.pieceNickNames);
5929     else SetCharTable(pieceNickName, "............");
5930     pieces = FIDEArray;
5931
5932     switch (gameInfo.variant) {
5933     case VariantFischeRandom:
5934       shuffleOpenings = TRUE;
5935     default:
5936       break;
5937     case VariantShatranj:
5938       pieces = ShatranjArray;
5939       nrCastlingRights = 0;
5940       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5941       break;
5942     case VariantMakruk:
5943       pieces = makrukArray;
5944       nrCastlingRights = 0;
5945       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5946       break;
5947     case VariantASEAN:
5948       pieces = aseanArray;
5949       nrCastlingRights = 0;
5950       SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk");
5951       break;
5952     case VariantTwoKings:
5953       pieces = twoKingsArray;
5954       break;
5955     case VariantGrand:
5956       pieces = GrandArray;
5957       nrCastlingRights = 0;
5958       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5959       gameInfo.boardWidth = 10;
5960       gameInfo.boardHeight = 10;
5961       gameInfo.holdingsSize = 7;
5962       break;
5963     case VariantCapaRandom:
5964       shuffleOpenings = TRUE;
5965     case VariantCapablanca:
5966       pieces = CapablancaArray;
5967       gameInfo.boardWidth = 10;
5968       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5969       break;
5970     case VariantGothic:
5971       pieces = GothicArray;
5972       gameInfo.boardWidth = 10;
5973       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5974       break;
5975     case VariantSChess:
5976       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5977       gameInfo.holdingsSize = 7;
5978       for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
5979       break;
5980     case VariantJanus:
5981       pieces = JanusArray;
5982       gameInfo.boardWidth = 10;
5983       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5984       nrCastlingRights = 6;
5985         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5986         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5987         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5988         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5989         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5990         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5991       break;
5992     case VariantFalcon:
5993       pieces = FalconArray;
5994       gameInfo.boardWidth = 10;
5995       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5996       break;
5997     case VariantXiangqi:
5998       pieces = XiangqiArray;
5999       gameInfo.boardWidth  = 9;
6000       gameInfo.boardHeight = 10;
6001       nrCastlingRights = 0;
6002       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
6003       break;
6004     case VariantShogi:
6005       pieces = ShogiArray;
6006       gameInfo.boardWidth  = 9;
6007       gameInfo.boardHeight = 9;
6008       gameInfo.holdingsSize = 7;
6009       nrCastlingRights = 0;
6010       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
6011       break;
6012     case VariantChu:
6013       pieces = ChuArray; pieceRows = 3;
6014       gameInfo.boardWidth  = 12;
6015       gameInfo.boardHeight = 12;
6016       nrCastlingRights = 0;
6017       SetCharTable(pieceToChar, "P.BRQSEXOGCATHD.VMLIFN+.++.++++++++++.+++++K"
6018                                 "p.brqsexogcathd.vmlifn+.++.++++++++++.+++++k");
6019       break;
6020     case VariantCourier:
6021       pieces = CourierArray;
6022       gameInfo.boardWidth  = 12;
6023       nrCastlingRights = 0;
6024       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
6025       break;
6026     case VariantKnightmate:
6027       pieces = KnightmateArray;
6028       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
6029       break;
6030     case VariantSpartan:
6031       pieces = SpartanArray;
6032       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
6033       break;
6034     case VariantFairy:
6035       pieces = fairyArray;
6036       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
6037       break;
6038     case VariantGreat:
6039       pieces = GreatArray;
6040       gameInfo.boardWidth = 10;
6041       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
6042       gameInfo.holdingsSize = 8;
6043       break;
6044     case VariantSuper:
6045       pieces = FIDEArray;
6046       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
6047       gameInfo.holdingsSize = 8;
6048       startedFromSetupPosition = TRUE;
6049       break;
6050     case VariantCrazyhouse:
6051     case VariantBughouse:
6052       pieces = FIDEArray;
6053       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
6054       gameInfo.holdingsSize = 5;
6055       break;
6056     case VariantWildCastle:
6057       pieces = FIDEArray;
6058       /* !!?shuffle with kings guaranteed to be on d or e file */
6059       shuffleOpenings = 1;
6060       break;
6061     case VariantNoCastle:
6062       pieces = FIDEArray;
6063       nrCastlingRights = 0;
6064       /* !!?unconstrained back-rank shuffle */
6065       shuffleOpenings = 1;
6066       break;
6067     }
6068
6069     overrule = 0;
6070     if(appData.NrFiles >= 0) {
6071         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6072         gameInfo.boardWidth = appData.NrFiles;
6073     }
6074     if(appData.NrRanks >= 0) {
6075         gameInfo.boardHeight = appData.NrRanks;
6076     }
6077     if(appData.holdingsSize >= 0) {
6078         i = appData.holdingsSize;
6079         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6080         gameInfo.holdingsSize = i;
6081     }
6082     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6083     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6084         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6085
6086     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6087     if(pawnRow < 1) pawnRow = 1;
6088     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN || gameInfo.variant == VariantGrand) pawnRow = 2;
6089     if(gameInfo.variant == VariantChu) pawnRow = 3;
6090
6091     /* User pieceToChar list overrules defaults */
6092     if(appData.pieceToCharTable != NULL)
6093         SetCharTable(pieceToChar, appData.pieceToCharTable);
6094
6095     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6096
6097         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6098             s = (ChessSquare) 0; /* account holding counts in guard band */
6099         for( i=0; i<BOARD_HEIGHT; i++ )
6100             initialPosition[i][j] = s;
6101
6102         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6103         initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
6104         initialPosition[pawnRow][j] = WhitePawn;
6105         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6106         if(gameInfo.variant == VariantXiangqi) {
6107             if(j&1) {
6108                 initialPosition[pawnRow][j] =
6109                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6110                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6111                    initialPosition[2][j] = WhiteCannon;
6112                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6113                 }
6114             }
6115         }
6116         if(gameInfo.variant == VariantChu) {
6117              if(j == (BOARD_WIDTH-2)/3 || j == BOARD_WIDTH - (BOARD_WIDTH+1)/3)
6118                initialPosition[pawnRow+1][j] = WhiteCobra,
6119                initialPosition[BOARD_HEIGHT-pawnRow-2][j] = BlackCobra;
6120              for(i=1; i<pieceRows; i++) {
6121                initialPosition[i][j] = pieces[2*i][j-gameInfo.holdingsWidth];
6122                initialPosition[BOARD_HEIGHT-1-i][j] =  pieces[2*i+1][j-gameInfo.holdingsWidth];
6123              }
6124         }
6125         if(gameInfo.variant == VariantGrand) {
6126             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6127                initialPosition[0][j] = WhiteRook;
6128                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6129             }
6130         }
6131         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] =  pieces[1][j-gameInfo.holdingsWidth];
6132     }
6133     if( (gameInfo.variant == VariantShogi) && !overrule ) {
6134
6135             j=BOARD_LEFT+1;
6136             initialPosition[1][j] = WhiteBishop;
6137             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6138             j=BOARD_RGHT-2;
6139             initialPosition[1][j] = WhiteRook;
6140             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6141     }
6142
6143     if( nrCastlingRights == -1) {
6144         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6145         /*       This sets default castling rights from none to normal corners   */
6146         /* Variants with other castling rights must set them themselves above    */
6147         nrCastlingRights = 6;
6148
6149         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6150         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6151         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6152         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6153         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6154         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6155      }
6156
6157      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6158      if(gameInfo.variant == VariantGreat) { // promotion commoners
6159         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6160         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6161         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6162         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6163      }
6164      if( gameInfo.variant == VariantSChess ) {
6165       initialPosition[1][0] = BlackMarshall;
6166       initialPosition[2][0] = BlackAngel;
6167       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6168       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6169       initialPosition[1][1] = initialPosition[2][1] =
6170       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6171      }
6172   if (appData.debugMode) {
6173     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6174   }
6175     if(shuffleOpenings) {
6176         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6177         startedFromSetupPosition = TRUE;
6178     }
6179     if(startedFromPositionFile) {
6180       /* [HGM] loadPos: use PositionFile for every new game */
6181       CopyBoard(initialPosition, filePosition);
6182       for(i=0; i<nrCastlingRights; i++)
6183           initialRights[i] = filePosition[CASTLING][i];
6184       startedFromSetupPosition = TRUE;
6185     }
6186
6187     CopyBoard(boards[0], initialPosition);
6188
6189     if(oldx != gameInfo.boardWidth ||
6190        oldy != gameInfo.boardHeight ||
6191        oldv != gameInfo.variant ||
6192        oldh != gameInfo.holdingsWidth
6193                                          )
6194             InitDrawingSizes(-2 ,0);
6195
6196     oldv = gameInfo.variant;
6197     if (redraw)
6198       DrawPosition(TRUE, boards[currentMove]);
6199 }
6200
6201 void
6202 SendBoard (ChessProgramState *cps, int moveNum)
6203 {
6204     char message[MSG_SIZ];
6205
6206     if (cps->useSetboard) {
6207       char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6208       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6209       SendToProgram(message, cps);
6210       free(fen);
6211
6212     } else {
6213       ChessSquare *bp;
6214       int i, j, left=0, right=BOARD_WIDTH;
6215       /* Kludge to set black to move, avoiding the troublesome and now
6216        * deprecated "black" command.
6217        */
6218       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6219         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6220
6221       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6222
6223       SendToProgram("edit\n", cps);
6224       SendToProgram("#\n", cps);
6225       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6226         bp = &boards[moveNum][i][left];
6227         for (j = left; j < right; j++, bp++) {
6228           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6229           if ((int) *bp < (int) BlackPawn) {
6230             if(j == BOARD_RGHT+1)
6231                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6232             else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6233             if(message[0] == '+' || message[0] == '~') {
6234               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6235                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6236                         AAA + j, ONE + i);
6237             }
6238             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6239                 message[1] = BOARD_RGHT   - 1 - j + '1';
6240                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6241             }
6242             SendToProgram(message, cps);
6243           }
6244         }
6245       }
6246
6247       SendToProgram("c\n", cps);
6248       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6249         bp = &boards[moveNum][i][left];
6250         for (j = left; j < right; j++, bp++) {
6251           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6252           if (((int) *bp != (int) EmptySquare)
6253               && ((int) *bp >= (int) BlackPawn)) {
6254             if(j == BOARD_LEFT-2)
6255                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6256             else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6257                     AAA + j, ONE + i);
6258             if(message[0] == '+' || message[0] == '~') {
6259               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6260                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6261                         AAA + j, ONE + i);
6262             }
6263             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6264                 message[1] = BOARD_RGHT   - 1 - j + '1';
6265                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6266             }
6267             SendToProgram(message, cps);
6268           }
6269         }
6270       }
6271
6272       SendToProgram(".\n", cps);
6273     }
6274     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6275 }
6276
6277 char exclusionHeader[MSG_SIZ];
6278 int exCnt, excludePtr;
6279 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6280 static Exclusion excluTab[200];
6281 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6282
6283 static void
6284 WriteMap (int s)
6285 {
6286     int j;
6287     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6288     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6289 }
6290
6291 static void
6292 ClearMap ()
6293 {
6294     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6295     excludePtr = 24; exCnt = 0;
6296     WriteMap(0);
6297 }
6298
6299 static void
6300 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6301 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6302     char buf[2*MOVE_LEN], *p;
6303     Exclusion *e = excluTab;
6304     int i;
6305     for(i=0; i<exCnt; i++)
6306         if(e[i].ff == fromX && e[i].fr == fromY &&
6307            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6308     if(i == exCnt) { // was not in exclude list; add it
6309         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6310         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6311             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6312             return; // abort
6313         }
6314         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6315         excludePtr++; e[i].mark = excludePtr++;
6316         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6317         exCnt++;
6318     }
6319     exclusionHeader[e[i].mark] = state;
6320 }
6321
6322 static int
6323 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6324 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6325     char buf[MSG_SIZ];
6326     int j, k;
6327     ChessMove moveType;
6328     if((signed char)promoChar == -1) { // kludge to indicate best move
6329         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6330             return 1; // if unparsable, abort
6331     }
6332     // update exclusion map (resolving toggle by consulting existing state)
6333     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6334     j = k%8; k >>= 3;
6335     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6336     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6337          excludeMap[k] |=   1<<j;
6338     else excludeMap[k] &= ~(1<<j);
6339     // update header
6340     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6341     // inform engine
6342     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6343     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6344     SendToBoth(buf);
6345     return (state == '+');
6346 }
6347
6348 static void
6349 ExcludeClick (int index)
6350 {
6351     int i, j;
6352     Exclusion *e = excluTab;
6353     if(index < 25) { // none, best or tail clicked
6354         if(index < 13) { // none: include all
6355             WriteMap(0); // clear map
6356             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6357             SendToBoth("include all\n"); // and inform engine
6358         } else if(index > 18) { // tail
6359             if(exclusionHeader[19] == '-') { // tail was excluded
6360                 SendToBoth("include all\n");
6361                 WriteMap(0); // clear map completely
6362                 // now re-exclude selected moves
6363                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6364                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6365             } else { // tail was included or in mixed state
6366                 SendToBoth("exclude all\n");
6367                 WriteMap(0xFF); // fill map completely
6368                 // now re-include selected moves
6369                 j = 0; // count them
6370                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6371                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6372                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6373             }
6374         } else { // best
6375             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6376         }
6377     } else {
6378         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6379             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6380             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6381             break;
6382         }
6383     }
6384 }
6385
6386 ChessSquare
6387 DefaultPromoChoice (int white)
6388 {
6389     ChessSquare result;
6390     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6391        gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
6392         result = WhiteFerz; // no choice
6393     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6394         result= WhiteKing; // in Suicide Q is the last thing we want
6395     else if(gameInfo.variant == VariantSpartan)
6396         result = white ? WhiteQueen : WhiteAngel;
6397     else result = WhiteQueen;
6398     if(!white) result = WHITE_TO_BLACK result;
6399     return result;
6400 }
6401
6402 static int autoQueen; // [HGM] oneclick
6403
6404 int
6405 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6406 {
6407     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6408     /* [HGM] add Shogi promotions */
6409     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6410     ChessSquare piece;
6411     ChessMove moveType;
6412     Boolean premove;
6413
6414     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6415     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6416
6417     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6418       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6419         return FALSE;
6420
6421     piece = boards[currentMove][fromY][fromX];
6422     if(gameInfo.variant == VariantChu) {
6423         int p = piece >= BlackPawn ? BLACK_TO_WHITE piece : piece;
6424         promotionZoneSize = BOARD_HEIGHT/3;
6425         highestPromotingPiece = (p >= WhiteLion || PieceToChar(piece + 22) == '.') ? WhitePawn : WhiteKing;
6426     } else if(gameInfo.variant == VariantShogi) {
6427         promotionZoneSize = BOARD_HEIGHT/3;
6428         highestPromotingPiece = (int)WhiteAlfil;
6429     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6430         promotionZoneSize = 3;
6431     }
6432
6433     // Treat Lance as Pawn when it is not representing Amazon
6434     if(gameInfo.variant != VariantSuper) {
6435         if(piece == WhiteLance) piece = WhitePawn; else
6436         if(piece == BlackLance) piece = BlackPawn;
6437     }
6438
6439     // next weed out all moves that do not touch the promotion zone at all
6440     if((int)piece >= BlackPawn) {
6441         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6442              return FALSE;
6443         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6444     } else {
6445         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6446            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6447     }
6448
6449     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6450
6451     // weed out mandatory Shogi promotions
6452     if(gameInfo.variant == VariantShogi) {
6453         if(piece >= BlackPawn) {
6454             if(toY == 0 && piece == BlackPawn ||
6455                toY == 0 && piece == BlackQueen ||
6456                toY <= 1 && piece == BlackKnight) {
6457                 *promoChoice = '+';
6458                 return FALSE;
6459             }
6460         } else {
6461             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6462                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6463                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6464                 *promoChoice = '+';
6465                 return FALSE;
6466             }
6467         }
6468     }
6469
6470     // weed out obviously illegal Pawn moves
6471     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6472         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6473         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6474         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6475         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6476         // note we are not allowed to test for valid (non-)capture, due to premove
6477     }
6478
6479     // we either have a choice what to promote to, or (in Shogi) whether to promote
6480     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6481        gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN) {
6482         *promoChoice = PieceToChar(BlackFerz);  // no choice
6483         return FALSE;
6484     }
6485     // no sense asking what we must promote to if it is going to explode...
6486     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6487         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6488         return FALSE;
6489     }
6490     // give caller the default choice even if we will not make it
6491     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6492     if(IS_SHOGI(gameInfo.variant)) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6493     if(        sweepSelect && gameInfo.variant != VariantGreat
6494                            && gameInfo.variant != VariantGrand
6495                            && gameInfo.variant != VariantSuper) return FALSE;
6496     if(autoQueen) return FALSE; // predetermined
6497
6498     // suppress promotion popup on illegal moves that are not premoves
6499     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6500               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6501     if(appData.testLegality && !premove) {
6502         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6503                         fromY, fromX, toY, toX, IS_SHOGI(gameInfo.variant) ? '+' : NULLCHAR);
6504         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6505             return FALSE;
6506     }
6507
6508     return TRUE;
6509 }
6510
6511 int
6512 InPalace (int row, int column)
6513 {   /* [HGM] for Xiangqi */
6514     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6515          column < (BOARD_WIDTH + 4)/2 &&
6516          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6517     return FALSE;
6518 }
6519
6520 int
6521 PieceForSquare (int x, int y)
6522 {
6523   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6524      return -1;
6525   else
6526      return boards[currentMove][y][x];
6527 }
6528
6529 int
6530 OKToStartUserMove (int x, int y)
6531 {
6532     ChessSquare from_piece;
6533     int white_piece;
6534
6535     if (matchMode) return FALSE;
6536     if (gameMode == EditPosition) return TRUE;
6537
6538     if (x >= 0 && y >= 0)
6539       from_piece = boards[currentMove][y][x];
6540     else
6541       from_piece = EmptySquare;
6542
6543     if (from_piece == EmptySquare) return FALSE;
6544
6545     white_piece = (int)from_piece >= (int)WhitePawn &&
6546       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6547
6548     switch (gameMode) {
6549       case AnalyzeFile:
6550       case TwoMachinesPlay:
6551       case EndOfGame:
6552         return FALSE;
6553
6554       case IcsObserving:
6555       case IcsIdle:
6556         return FALSE;
6557
6558       case MachinePlaysWhite:
6559       case IcsPlayingBlack:
6560         if (appData.zippyPlay) return FALSE;
6561         if (white_piece) {
6562             DisplayMoveError(_("You are playing Black"));
6563             return FALSE;
6564         }
6565         break;
6566
6567       case MachinePlaysBlack:
6568       case IcsPlayingWhite:
6569         if (appData.zippyPlay) return FALSE;
6570         if (!white_piece) {
6571             DisplayMoveError(_("You are playing White"));
6572             return FALSE;
6573         }
6574         break;
6575
6576       case PlayFromGameFile:
6577             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6578       case EditGame:
6579         if (!white_piece && WhiteOnMove(currentMove)) {
6580             DisplayMoveError(_("It is White's turn"));
6581             return FALSE;
6582         }
6583         if (white_piece && !WhiteOnMove(currentMove)) {
6584             DisplayMoveError(_("It is Black's turn"));
6585             return FALSE;
6586         }
6587         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6588             /* Editing correspondence game history */
6589             /* Could disallow this or prompt for confirmation */
6590             cmailOldMove = -1;
6591         }
6592         break;
6593
6594       case BeginningOfGame:
6595         if (appData.icsActive) return FALSE;
6596         if (!appData.noChessProgram) {
6597             if (!white_piece) {
6598                 DisplayMoveError(_("You are playing White"));
6599                 return FALSE;
6600             }
6601         }
6602         break;
6603
6604       case Training:
6605         if (!white_piece && WhiteOnMove(currentMove)) {
6606             DisplayMoveError(_("It is White's turn"));
6607             return FALSE;
6608         }
6609         if (white_piece && !WhiteOnMove(currentMove)) {
6610             DisplayMoveError(_("It is Black's turn"));
6611             return FALSE;
6612         }
6613         break;
6614
6615       default:
6616       case IcsExamining:
6617         break;
6618     }
6619     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6620         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6621         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6622         && gameMode != AnalyzeFile && gameMode != Training) {
6623         DisplayMoveError(_("Displayed position is not current"));
6624         return FALSE;
6625     }
6626     return TRUE;
6627 }
6628
6629 Boolean
6630 OnlyMove (int *x, int *y, Boolean captures)
6631 {
6632     DisambiguateClosure cl;
6633     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6634     switch(gameMode) {
6635       case MachinePlaysBlack:
6636       case IcsPlayingWhite:
6637       case BeginningOfGame:
6638         if(!WhiteOnMove(currentMove)) return FALSE;
6639         break;
6640       case MachinePlaysWhite:
6641       case IcsPlayingBlack:
6642         if(WhiteOnMove(currentMove)) return FALSE;
6643         break;
6644       case EditGame:
6645         break;
6646       default:
6647         return FALSE;
6648     }
6649     cl.pieceIn = EmptySquare;
6650     cl.rfIn = *y;
6651     cl.ffIn = *x;
6652     cl.rtIn = -1;
6653     cl.ftIn = -1;
6654     cl.promoCharIn = NULLCHAR;
6655     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6656     if( cl.kind == NormalMove ||
6657         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6658         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6659         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6660       fromX = cl.ff;
6661       fromY = cl.rf;
6662       *x = cl.ft;
6663       *y = cl.rt;
6664       return TRUE;
6665     }
6666     if(cl.kind != ImpossibleMove) return FALSE;
6667     cl.pieceIn = EmptySquare;
6668     cl.rfIn = -1;
6669     cl.ffIn = -1;
6670     cl.rtIn = *y;
6671     cl.ftIn = *x;
6672     cl.promoCharIn = NULLCHAR;
6673     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6674     if( cl.kind == NormalMove ||
6675         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6676         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6677         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6678       fromX = cl.ff;
6679       fromY = cl.rf;
6680       *x = cl.ft;
6681       *y = cl.rt;
6682       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6683       return TRUE;
6684     }
6685     return FALSE;
6686 }
6687
6688 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6689 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6690 int lastLoadGameUseList = FALSE;
6691 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6692 ChessMove lastLoadGameStart = EndOfFile;
6693 int doubleClick;
6694
6695 void
6696 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6697 {
6698     ChessMove moveType;
6699     ChessSquare pup;
6700     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6701
6702     /* Check if the user is playing in turn.  This is complicated because we
6703        let the user "pick up" a piece before it is his turn.  So the piece he
6704        tried to pick up may have been captured by the time he puts it down!
6705        Therefore we use the color the user is supposed to be playing in this
6706        test, not the color of the piece that is currently on the starting
6707        square---except in EditGame mode, where the user is playing both
6708        sides; fortunately there the capture race can't happen.  (It can
6709        now happen in IcsExamining mode, but that's just too bad.  The user
6710        will get a somewhat confusing message in that case.)
6711        */
6712
6713     switch (gameMode) {
6714       case AnalyzeFile:
6715       case TwoMachinesPlay:
6716       case EndOfGame:
6717       case IcsObserving:
6718       case IcsIdle:
6719         /* We switched into a game mode where moves are not accepted,
6720            perhaps while the mouse button was down. */
6721         return;
6722
6723       case MachinePlaysWhite:
6724         /* User is moving for Black */
6725         if (WhiteOnMove(currentMove)) {
6726             DisplayMoveError(_("It is White's turn"));
6727             return;
6728         }
6729         break;
6730
6731       case MachinePlaysBlack:
6732         /* User is moving for White */
6733         if (!WhiteOnMove(currentMove)) {
6734             DisplayMoveError(_("It is Black's turn"));
6735             return;
6736         }
6737         break;
6738
6739       case PlayFromGameFile:
6740             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6741       case EditGame:
6742       case IcsExamining:
6743       case BeginningOfGame:
6744       case AnalyzeMode:
6745       case Training:
6746         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6747         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6748             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6749             /* User is moving for Black */
6750             if (WhiteOnMove(currentMove)) {
6751                 DisplayMoveError(_("It is White's turn"));
6752                 return;
6753             }
6754         } else {
6755             /* User is moving for White */
6756             if (!WhiteOnMove(currentMove)) {
6757                 DisplayMoveError(_("It is Black's turn"));
6758                 return;
6759             }
6760         }
6761         break;
6762
6763       case IcsPlayingBlack:
6764         /* User is moving for Black */
6765         if (WhiteOnMove(currentMove)) {
6766             if (!appData.premove) {
6767                 DisplayMoveError(_("It is White's turn"));
6768             } else if (toX >= 0 && toY >= 0) {
6769                 premoveToX = toX;
6770                 premoveToY = toY;
6771                 premoveFromX = fromX;
6772                 premoveFromY = fromY;
6773                 premovePromoChar = promoChar;
6774                 gotPremove = 1;
6775                 if (appData.debugMode)
6776                     fprintf(debugFP, "Got premove: fromX %d,"
6777                             "fromY %d, toX %d, toY %d\n",
6778                             fromX, fromY, toX, toY);
6779             }
6780             return;
6781         }
6782         break;
6783
6784       case IcsPlayingWhite:
6785         /* User is moving for White */
6786         if (!WhiteOnMove(currentMove)) {
6787             if (!appData.premove) {
6788                 DisplayMoveError(_("It is Black's turn"));
6789             } else if (toX >= 0 && toY >= 0) {
6790                 premoveToX = toX;
6791                 premoveToY = toY;
6792                 premoveFromX = fromX;
6793                 premoveFromY = fromY;
6794                 premovePromoChar = promoChar;
6795                 gotPremove = 1;
6796                 if (appData.debugMode)
6797                     fprintf(debugFP, "Got premove: fromX %d,"
6798                             "fromY %d, toX %d, toY %d\n",
6799                             fromX, fromY, toX, toY);
6800             }
6801             return;
6802         }
6803         break;
6804
6805       default:
6806         break;
6807
6808       case EditPosition:
6809         /* EditPosition, empty square, or different color piece;
6810            click-click move is possible */
6811         if (toX == -2 || toY == -2) {
6812             boards[0][fromY][fromX] = EmptySquare;
6813             DrawPosition(FALSE, boards[currentMove]);
6814             return;
6815         } else if (toX >= 0 && toY >= 0) {
6816             boards[0][toY][toX] = boards[0][fromY][fromX];
6817             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6818                 if(boards[0][fromY][0] != EmptySquare) {
6819                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6820                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6821                 }
6822             } else
6823             if(fromX == BOARD_RGHT+1) {
6824                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6825                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6826                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6827                 }
6828             } else
6829             boards[0][fromY][fromX] = gatingPiece;
6830             DrawPosition(FALSE, boards[currentMove]);
6831             return;
6832         }
6833         return;
6834     }
6835
6836     if(toX < 0 || toY < 0) return;
6837     pup = boards[currentMove][toY][toX];
6838
6839     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6840     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6841          if( pup != EmptySquare ) return;
6842          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6843            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6844                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6845            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6846            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6847            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6848            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6849          fromY = DROP_RANK;
6850     }
6851
6852     /* [HGM] always test for legality, to get promotion info */
6853     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6854                                          fromY, fromX, toY, toX, promoChar);
6855
6856     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6857
6858     /* [HGM] but possibly ignore an IllegalMove result */
6859     if (appData.testLegality) {
6860         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6861             DisplayMoveError(_("Illegal move"));
6862             return;
6863         }
6864     }
6865
6866     if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
6867         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
6868              ClearPremoveHighlights(); // was included
6869         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
6870         return;
6871     }
6872
6873     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6874 }
6875
6876 /* Common tail of UserMoveEvent and DropMenuEvent */
6877 int
6878 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
6879 {
6880     char *bookHit = 0;
6881
6882     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6883         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6884         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6885         if(WhiteOnMove(currentMove)) {
6886             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6887         } else {
6888             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6889         }
6890     }
6891
6892     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6893        move type in caller when we know the move is a legal promotion */
6894     if(moveType == NormalMove && promoChar)
6895         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6896
6897     /* [HGM] <popupFix> The following if has been moved here from
6898        UserMoveEvent(). Because it seemed to belong here (why not allow
6899        piece drops in training games?), and because it can only be
6900        performed after it is known to what we promote. */
6901     if (gameMode == Training) {
6902       /* compare the move played on the board to the next move in the
6903        * game. If they match, display the move and the opponent's response.
6904        * If they don't match, display an error message.
6905        */
6906       int saveAnimate;
6907       Board testBoard;
6908       CopyBoard(testBoard, boards[currentMove]);
6909       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6910
6911       if (CompareBoards(testBoard, boards[currentMove+1])) {
6912         ForwardInner(currentMove+1);
6913
6914         /* Autoplay the opponent's response.
6915          * if appData.animate was TRUE when Training mode was entered,
6916          * the response will be animated.
6917          */
6918         saveAnimate = appData.animate;
6919         appData.animate = animateTraining;
6920         ForwardInner(currentMove+1);
6921         appData.animate = saveAnimate;
6922
6923         /* check for the end of the game */
6924         if (currentMove >= forwardMostMove) {
6925           gameMode = PlayFromGameFile;
6926           ModeHighlight();
6927           SetTrainingModeOff();
6928           DisplayInformation(_("End of game"));
6929         }
6930       } else {
6931         DisplayError(_("Incorrect move"), 0);
6932       }
6933       return 1;
6934     }
6935
6936   /* Ok, now we know that the move is good, so we can kill
6937      the previous line in Analysis Mode */
6938   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6939                                 && currentMove < forwardMostMove) {
6940     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6941     else forwardMostMove = currentMove;
6942   }
6943
6944   ClearMap();
6945
6946   /* If we need the chess program but it's dead, restart it */
6947   ResurrectChessProgram();
6948
6949   /* A user move restarts a paused game*/
6950   if (pausing)
6951     PauseEvent();
6952
6953   thinkOutput[0] = NULLCHAR;
6954
6955   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6956
6957   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6958     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6959     return 1;
6960   }
6961
6962   if (gameMode == BeginningOfGame) {
6963     if (appData.noChessProgram) {
6964       gameMode = EditGame;
6965       SetGameInfo();
6966     } else {
6967       char buf[MSG_SIZ];
6968       gameMode = MachinePlaysBlack;
6969       StartClocks();
6970       SetGameInfo();
6971       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
6972       DisplayTitle(buf);
6973       if (first.sendName) {
6974         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6975         SendToProgram(buf, &first);
6976       }
6977       StartClocks();
6978     }
6979     ModeHighlight();
6980   }
6981
6982   /* Relay move to ICS or chess engine */
6983   if (appData.icsActive) {
6984     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6985         gameMode == IcsExamining) {
6986       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6987         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6988         SendToICS("draw ");
6989         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6990       }
6991       // also send plain move, in case ICS does not understand atomic claims
6992       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6993       ics_user_moved = 1;
6994     }
6995   } else {
6996     if (first.sendTime && (gameMode == BeginningOfGame ||
6997                            gameMode == MachinePlaysWhite ||
6998                            gameMode == MachinePlaysBlack)) {
6999       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
7000     }
7001     if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
7002          // [HGM] book: if program might be playing, let it use book
7003         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
7004         first.maybeThinking = TRUE;
7005     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
7006         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
7007         SendBoard(&first, currentMove+1);
7008         if(second.analyzing) {
7009             if(!second.useSetboard) SendToProgram("undo\n", &second);
7010             SendBoard(&second, currentMove+1);
7011         }
7012     } else {
7013         SendMoveToProgram(forwardMostMove-1, &first);
7014         if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
7015     }
7016     if (currentMove == cmailOldMove + 1) {
7017       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
7018     }
7019   }
7020
7021   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7022
7023   switch (gameMode) {
7024   case EditGame:
7025     if(appData.testLegality)
7026     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
7027     case MT_NONE:
7028     case MT_CHECK:
7029       break;
7030     case MT_CHECKMATE:
7031     case MT_STAINMATE:
7032       if (WhiteOnMove(currentMove)) {
7033         GameEnds(BlackWins, "Black mates", GE_PLAYER);
7034       } else {
7035         GameEnds(WhiteWins, "White mates", GE_PLAYER);
7036       }
7037       break;
7038     case MT_STALEMATE:
7039       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
7040       break;
7041     }
7042     break;
7043
7044   case MachinePlaysBlack:
7045   case MachinePlaysWhite:
7046     /* disable certain menu options while machine is thinking */
7047     SetMachineThinkingEnables();
7048     break;
7049
7050   default:
7051     break;
7052   }
7053
7054   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7055   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7056
7057   if(bookHit) { // [HGM] book: simulate book reply
7058         static char bookMove[MSG_SIZ]; // a bit generous?
7059
7060         programStats.nodes = programStats.depth = programStats.time =
7061         programStats.score = programStats.got_only_move = 0;
7062         sprintf(programStats.movelist, "%s (xbook)", bookHit);
7063
7064         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7065         strcat(bookMove, bookHit);
7066         HandleMachineMove(bookMove, &first);
7067   }
7068   return 1;
7069 }
7070
7071 void
7072 MarkByFEN(char *fen)
7073 {
7074         int r, f;
7075         if(!appData.markers || !appData.highlightDragging) return;
7076         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 0;
7077         r=BOARD_HEIGHT-1; f=BOARD_LEFT;
7078         while(*fen) {
7079             int s = 0;
7080             marker[r][f] = 0;
7081             if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7082             if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 1; else
7083             if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7084             if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7085             if(*fen == 'T') marker[r][f++] = 0; else
7086             if(*fen == 'Y') marker[r][f++] = 1; else
7087             if(*fen == 'G') marker[r][f++] = 3; else
7088             if(*fen == 'B') marker[r][f++] = 4; else
7089             if(*fen == 'C') marker[r][f++] = 5; else
7090             if(*fen == 'M') marker[r][f++] = 6; else
7091             if(*fen == 'W') marker[r][f++] = 7; else
7092             if(*fen == 'D') marker[r][f++] = 8; else
7093             if(*fen == 'R') marker[r][f++] = 2; else {
7094                 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7095               f += s; fen -= s>0;
7096             }
7097             while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7098             if(r < 0) break;
7099             fen++;
7100         }
7101         DrawPosition(TRUE, NULL);
7102 }
7103
7104 void
7105 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7106 {
7107     typedef char Markers[BOARD_RANKS][BOARD_FILES];
7108     Markers *m = (Markers *) closure;
7109     if(rf == fromY && ff == fromX)
7110         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7111                          || kind == WhiteCapturesEnPassant
7112                          || kind == BlackCapturesEnPassant);
7113     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
7114 }
7115
7116 void
7117 MarkTargetSquares (int clear)
7118 {
7119   int x, y, sum=0;
7120   if(clear) { // no reason to ever suppress clearing
7121     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = 0;
7122     if(!sum) return; // nothing was cleared,no redraw needed
7123   } else {
7124     int capt = 0;
7125     if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7126        !appData.testLegality || gameMode == EditPosition) return;
7127     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7128     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7129       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7130       if(capt)
7131       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7132     }
7133   }
7134   DrawPosition(FALSE, NULL);
7135 }
7136
7137 int
7138 Explode (Board board, int fromX, int fromY, int toX, int toY)
7139 {
7140     if(gameInfo.variant == VariantAtomic &&
7141        (board[toY][toX] != EmptySquare ||                     // capture?
7142         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
7143                          board[fromY][fromX] == BlackPawn   )
7144       )) {
7145         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7146         return TRUE;
7147     }
7148     return FALSE;
7149 }
7150
7151 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7152
7153 int
7154 CanPromote (ChessSquare piece, int y)
7155 {
7156         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7157         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7158         if(IS_SHOGI(gameInfo.variant)          || gameInfo.variant == VariantXiangqi ||
7159            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7160            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7161          gameInfo.variant == VariantMakruk   || gameInfo.variant == VariantASEAN) return FALSE;
7162         return (piece == BlackPawn && y == 1 ||
7163                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
7164                 piece == BlackLance && y == 1 ||
7165                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
7166 }
7167
7168 void
7169 HoverEvent (int xPix, int yPix, int x, int y)
7170 {
7171         static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7172         static int oldX = -1, oldY = -1, oldFromX = -1, oldFromY = -1;
7173         int r, f;
7174         if(dragging == 2) DragPieceMove(xPix, yPix); // [HGM] lion: drag without button for second leg
7175         if(!first.highlight) return;
7176         if(fromX != oldFromX || fromY != oldFromY)  oldX = oldY = -1; // kludge to fake entry on from-click
7177         if(x == oldX && y == oldY) return; // only do something if we enter new square
7178         oldFromX = fromX; oldFromY = fromY;
7179         if(oldX == -1 && oldY == -1 && x == fromX && y == fromY) // record markings after from-change
7180           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7181             baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7182         else if(oldX != x || oldY != y) {
7183           // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7184           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7185             marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7186           if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7187             char buf[MSG_SIZ];
7188             snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7189             SendToProgram(buf, &first);
7190           }
7191           oldX = x; oldY = y;
7192 //        SetHighlights(fromX, fromY, x, y);
7193         }
7194 }
7195
7196 void ReportClick(char *action, int x, int y)
7197 {
7198         char buf[MSG_SIZ]; // Inform engine of what user does
7199         int r, f;
7200         if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7201           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 1, marker[r][f] = 0;
7202         if(!first.highlight || gameMode == EditPosition) return;
7203         snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7204         SendToProgram(buf, &first);
7205 }
7206
7207 void
7208 LeftClick (ClickType clickType, int xPix, int yPix)
7209 {
7210     int x, y;
7211     Boolean saveAnimate;
7212     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7213     char promoChoice = NULLCHAR;
7214     ChessSquare piece;
7215     static TimeMark lastClickTime, prevClickTime;
7216
7217     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7218
7219     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7220
7221     if (clickType == Press) ErrorPopDown();
7222     lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7223
7224     x = EventToSquare(xPix, BOARD_WIDTH);
7225     y = EventToSquare(yPix, BOARD_HEIGHT);
7226     if (!flipView && y >= 0) {
7227         y = BOARD_HEIGHT - 1 - y;
7228     }
7229     if (flipView && x >= 0) {
7230         x = BOARD_WIDTH - 1 - x;
7231     }
7232
7233     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7234         defaultPromoChoice = promoSweep;
7235         promoSweep = EmptySquare;   // terminate sweep
7236         promoDefaultAltered = TRUE;
7237         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7238     }
7239
7240     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7241         if(clickType == Release) return; // ignore upclick of click-click destination
7242         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7243         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7244         if(gameInfo.holdingsWidth &&
7245                 (WhiteOnMove(currentMove)
7246                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7247                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7248             // click in right holdings, for determining promotion piece
7249             ChessSquare p = boards[currentMove][y][x];
7250             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7251             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7252             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7253                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7254                 fromX = fromY = -1;
7255                 return;
7256             }
7257         }
7258         DrawPosition(FALSE, boards[currentMove]);
7259         return;
7260     }
7261
7262     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7263     if(clickType == Press
7264             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7265               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7266               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7267         return;
7268
7269     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7270         // could be static click on premove from-square: abort premove
7271         gotPremove = 0;
7272         ClearPremoveHighlights();
7273     }
7274
7275     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7276         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7277
7278     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7279         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7280                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7281         defaultPromoChoice = DefaultPromoChoice(side);
7282     }
7283
7284     autoQueen = appData.alwaysPromoteToQueen;
7285
7286     if (fromX == -1) {
7287       int originalY = y;
7288       gatingPiece = EmptySquare;
7289       if (clickType != Press) {
7290         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7291             DragPieceEnd(xPix, yPix); dragging = 0;
7292             DrawPosition(FALSE, NULL);
7293         }
7294         return;
7295       }
7296       doubleClick = FALSE;
7297       if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7298         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7299       }
7300       fromX = x; fromY = y; toX = toY = -1;
7301       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7302          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7303          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7304             /* First square */
7305             if (OKToStartUserMove(fromX, fromY)) {
7306                 second = 0;
7307                 ReportClick("lift", x, y);
7308                 MarkTargetSquares(0);
7309                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7310                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7311                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7312                     promoSweep = defaultPromoChoice;
7313                     selectFlag = 0; lastX = xPix; lastY = yPix;
7314                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7315                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7316                 }
7317                 if (appData.highlightDragging) {
7318                     SetHighlights(fromX, fromY, -1, -1);
7319                 } else {
7320                     ClearHighlights();
7321                 }
7322             } else fromX = fromY = -1;
7323             return;
7324         }
7325     }
7326
7327     /* fromX != -1 */
7328     if (clickType == Press && gameMode != EditPosition && killX < 0) {
7329         ChessSquare fromP;
7330         ChessSquare toP;
7331         int frc;
7332
7333         // ignore off-board to clicks
7334         if(y < 0 || x < 0) return;
7335
7336         /* Check if clicking again on the same color piece */
7337         fromP = boards[currentMove][fromY][fromX];
7338         toP = boards[currentMove][y][x];
7339         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
7340         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
7341              WhitePawn <= toP && toP <= WhiteKing &&
7342              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7343              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7344             (BlackPawn <= fromP && fromP <= BlackKing &&
7345              BlackPawn <= toP && toP <= BlackKing &&
7346              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7347              !(fromP == BlackKing && toP == BlackRook && frc))) {
7348             /* Clicked again on same color piece -- changed his mind */
7349             second = (x == fromX && y == fromY);
7350             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7351                 second = FALSE; // first double-click rather than scond click
7352                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7353             }
7354             promoDefaultAltered = FALSE;
7355             MarkTargetSquares(1);
7356            if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7357             if (appData.highlightDragging) {
7358                 SetHighlights(x, y, -1, -1);
7359             } else {
7360                 ClearHighlights();
7361             }
7362             if (OKToStartUserMove(x, y)) {
7363                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7364                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7365                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7366                  gatingPiece = boards[currentMove][fromY][fromX];
7367                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7368                 fromX = x;
7369                 fromY = y; dragging = 1;
7370                 ReportClick("lift", x, y);
7371                 MarkTargetSquares(0);
7372                 DragPieceBegin(xPix, yPix, FALSE);
7373                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7374                     promoSweep = defaultPromoChoice;
7375                     selectFlag = 0; lastX = xPix; lastY = yPix;
7376                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7377                 }
7378             }
7379            }
7380            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7381            second = FALSE;
7382         }
7383         // ignore clicks on holdings
7384         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7385     }
7386
7387     if (clickType == Release && x == fromX && y == fromY && killX < 0) {
7388         DragPieceEnd(xPix, yPix); dragging = 0;
7389         if(clearFlag) {
7390             // a deferred attempt to click-click move an empty square on top of a piece
7391             boards[currentMove][y][x] = EmptySquare;
7392             ClearHighlights();
7393             DrawPosition(FALSE, boards[currentMove]);
7394             fromX = fromY = -1; clearFlag = 0;
7395             return;
7396         }
7397         if (appData.animateDragging) {
7398             /* Undo animation damage if any */
7399             DrawPosition(FALSE, NULL);
7400         }
7401         if (second || sweepSelecting) {
7402             /* Second up/down in same square; just abort move */
7403             if(sweepSelecting) DrawPosition(FALSE, boards[currentMove]);
7404             second = sweepSelecting = 0;
7405             fromX = fromY = -1;
7406             gatingPiece = EmptySquare;
7407             MarkTargetSquares(1);
7408             ClearHighlights();
7409             gotPremove = 0;
7410             ClearPremoveHighlights();
7411         } else {
7412             /* First upclick in same square; start click-click mode */
7413             SetHighlights(x, y, -1, -1);
7414         }
7415         return;
7416     }
7417
7418     clearFlag = 0;
7419
7420     if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] && (x != killX || y != killY) && !sweepSelecting) {
7421         if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7422         DisplayMessage(_("only marked squares are legal"),"");
7423         DrawPosition(TRUE, NULL);
7424         return; // ignore to-click
7425     }
7426
7427     /* we now have a different from- and (possibly off-board) to-square */
7428     /* Completed move */
7429     if(!sweepSelecting) {
7430         toX = x;
7431         toY = y;
7432     }
7433
7434     saveAnimate = appData.animate;
7435     if (clickType == Press) {
7436         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7437             // must be Edit Position mode with empty-square selected
7438             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7439             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7440             return;
7441         }
7442         if(dragging == 2) {  // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job
7443             dragging = 1;
7444             return;
7445         }
7446         if(x == killX && y == killY) {              // second click on this square, which was selected as first-leg target
7447             killX = killY = -1;                     // this informs us no second leg is coming, so treat as to-click without intermediate
7448         } else
7449         if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
7450         if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7451           if(appData.sweepSelect) {
7452             ChessSquare piece = boards[currentMove][fromY][fromX];
7453             promoSweep = defaultPromoChoice;
7454             if(PieceToChar(CHUPROMOTED piece) == '+') promoSweep = CHUPROMOTED piece;
7455             selectFlag = 0; lastX = xPix; lastY = yPix;
7456             Sweep(0); // Pawn that is going to promote: preview promotion piece
7457             sweepSelecting = 1;
7458             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7459             MarkTargetSquares(1);
7460           }
7461           return; // promo popup appears on up-click
7462         }
7463         /* Finish clickclick move */
7464         if (appData.animate || appData.highlightLastMove) {
7465             SetHighlights(fromX, fromY, toX, toY);
7466         } else {
7467             ClearHighlights();
7468         }
7469     } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
7470         sweepSelecting = 0;
7471         if (appData.animate || appData.highlightLastMove) {
7472             SetHighlights(fromX, fromY, toX, toY);
7473         } else {
7474             ClearHighlights();
7475         }
7476     } else {
7477 #if 0
7478 // [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
7479         /* Finish drag move */
7480         if (appData.highlightLastMove) {
7481             SetHighlights(fromX, fromY, toX, toY);
7482         } else {
7483             ClearHighlights();
7484         }
7485 #endif
7486         if(!dragging || marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
7487           dragging *= 2;            // flag button-less dragging if we are dragging
7488           MarkTargetSquares(1);
7489           if(x == killX && y == killY) killX = killY = -1; else {
7490             killX = x; killY = y;     //remeber this square as intermediate
7491             ReportClick("put", x, y); // and inform engine
7492             ReportClick("lift", x, y);
7493             return;
7494           }
7495         }
7496         DragPieceEnd(xPix, yPix); dragging = 0;
7497         /* Don't animate move and drag both */
7498         appData.animate = FALSE;
7499     }
7500
7501     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7502     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7503         ChessSquare piece = boards[currentMove][fromY][fromX];
7504         if(gameMode == EditPosition && piece != EmptySquare &&
7505            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7506             int n;
7507
7508             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7509                 n = PieceToNumber(piece - (int)BlackPawn);
7510                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7511                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7512                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7513             } else
7514             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7515                 n = PieceToNumber(piece);
7516                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7517                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7518                 boards[currentMove][n][BOARD_WIDTH-2]++;
7519             }
7520             boards[currentMove][fromY][fromX] = EmptySquare;
7521         }
7522         ClearHighlights();
7523         fromX = fromY = -1;
7524         MarkTargetSquares(1);
7525         DrawPosition(TRUE, boards[currentMove]);
7526         return;
7527     }
7528
7529     // off-board moves should not be highlighted
7530     if(x < 0 || y < 0) ClearHighlights();
7531     else ReportClick("put", x, y);
7532
7533     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7534
7535     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7536         SetHighlights(fromX, fromY, toX, toY);
7537         MarkTargetSquares(1);
7538         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7539             // [HGM] super: promotion to captured piece selected from holdings
7540             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7541             promotionChoice = TRUE;
7542             // kludge follows to temporarily execute move on display, without promoting yet
7543             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7544             boards[currentMove][toY][toX] = p;
7545             DrawPosition(FALSE, boards[currentMove]);
7546             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7547             boards[currentMove][toY][toX] = q;
7548             DisplayMessage("Click in holdings to choose piece", "");
7549             return;
7550         }
7551         PromotionPopUp();
7552     } else {
7553         int oldMove = currentMove;
7554         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7555         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7556         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7557         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7558            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7559             DrawPosition(TRUE, boards[currentMove]);
7560         MarkTargetSquares(1);
7561         fromX = fromY = -1;
7562     }
7563     appData.animate = saveAnimate;
7564     if (appData.animate || appData.animateDragging) {
7565         /* Undo animation damage if needed */
7566         DrawPosition(FALSE, NULL);
7567     }
7568 }
7569
7570 int
7571 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7572 {   // front-end-free part taken out of PieceMenuPopup
7573     int whichMenu; int xSqr, ySqr;
7574
7575     if(seekGraphUp) { // [HGM] seekgraph
7576         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7577         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7578         return -2;
7579     }
7580
7581     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7582          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7583         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7584         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7585         if(action == Press)   {
7586             originalFlip = flipView;
7587             flipView = !flipView; // temporarily flip board to see game from partners perspective
7588             DrawPosition(TRUE, partnerBoard);
7589             DisplayMessage(partnerStatus, "");
7590             partnerUp = TRUE;
7591         } else if(action == Release) {
7592             flipView = originalFlip;
7593             DrawPosition(TRUE, boards[currentMove]);
7594             partnerUp = FALSE;
7595         }
7596         return -2;
7597     }
7598
7599     xSqr = EventToSquare(x, BOARD_WIDTH);
7600     ySqr = EventToSquare(y, BOARD_HEIGHT);
7601     if (action == Release) {
7602         if(pieceSweep != EmptySquare) {
7603             EditPositionMenuEvent(pieceSweep, toX, toY);
7604             pieceSweep = EmptySquare;
7605         } else UnLoadPV(); // [HGM] pv
7606     }
7607     if (action != Press) return -2; // return code to be ignored
7608     switch (gameMode) {
7609       case IcsExamining:
7610         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7611       case EditPosition:
7612         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7613         if (xSqr < 0 || ySqr < 0) return -1;
7614         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7615         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7616         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7617         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7618         NextPiece(0);
7619         return 2; // grab
7620       case IcsObserving:
7621         if(!appData.icsEngineAnalyze) return -1;
7622       case IcsPlayingWhite:
7623       case IcsPlayingBlack:
7624         if(!appData.zippyPlay) goto noZip;
7625       case AnalyzeMode:
7626       case AnalyzeFile:
7627       case MachinePlaysWhite:
7628       case MachinePlaysBlack:
7629       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7630         if (!appData.dropMenu) {
7631           LoadPV(x, y);
7632           return 2; // flag front-end to grab mouse events
7633         }
7634         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7635            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7636       case EditGame:
7637       noZip:
7638         if (xSqr < 0 || ySqr < 0) return -1;
7639         if (!appData.dropMenu || appData.testLegality &&
7640             gameInfo.variant != VariantBughouse &&
7641             gameInfo.variant != VariantCrazyhouse) return -1;
7642         whichMenu = 1; // drop menu
7643         break;
7644       default:
7645         return -1;
7646     }
7647
7648     if (((*fromX = xSqr) < 0) ||
7649         ((*fromY = ySqr) < 0)) {
7650         *fromX = *fromY = -1;
7651         return -1;
7652     }
7653     if (flipView)
7654       *fromX = BOARD_WIDTH - 1 - *fromX;
7655     else
7656       *fromY = BOARD_HEIGHT - 1 - *fromY;
7657
7658     return whichMenu;
7659 }
7660
7661 void
7662 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7663 {
7664 //    char * hint = lastHint;
7665     FrontEndProgramStats stats;
7666
7667     stats.which = cps == &first ? 0 : 1;
7668     stats.depth = cpstats->depth;
7669     stats.nodes = cpstats->nodes;
7670     stats.score = cpstats->score;
7671     stats.time = cpstats->time;
7672     stats.pv = cpstats->movelist;
7673     stats.hint = lastHint;
7674     stats.an_move_index = 0;
7675     stats.an_move_count = 0;
7676
7677     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7678         stats.hint = cpstats->move_name;
7679         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7680         stats.an_move_count = cpstats->nr_moves;
7681     }
7682
7683     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
7684
7685     SetProgramStats( &stats );
7686 }
7687
7688 void
7689 ClearEngineOutputPane (int which)
7690 {
7691     static FrontEndProgramStats dummyStats;
7692     dummyStats.which = which;
7693     dummyStats.pv = "#";
7694     SetProgramStats( &dummyStats );
7695 }
7696
7697 #define MAXPLAYERS 500
7698
7699 char *
7700 TourneyStandings (int display)
7701 {
7702     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7703     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7704     char result, *p, *names[MAXPLAYERS];
7705
7706     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7707         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7708     names[0] = p = strdup(appData.participants);
7709     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7710
7711     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7712
7713     while(result = appData.results[nr]) {
7714         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7715         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7716         wScore = bScore = 0;
7717         switch(result) {
7718           case '+': wScore = 2; break;
7719           case '-': bScore = 2; break;
7720           case '=': wScore = bScore = 1; break;
7721           case ' ':
7722           case '*': return strdup("busy"); // tourney not finished
7723         }
7724         score[w] += wScore;
7725         score[b] += bScore;
7726         games[w]++;
7727         games[b]++;
7728         nr++;
7729     }
7730     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7731     for(w=0; w<nPlayers; w++) {
7732         bScore = -1;
7733         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7734         ranking[w] = b; points[w] = bScore; score[b] = -2;
7735     }
7736     p = malloc(nPlayers*34+1);
7737     for(w=0; w<nPlayers && w<display; w++)
7738         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7739     free(names[0]);
7740     return p;
7741 }
7742
7743 void
7744 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7745 {       // count all piece types
7746         int p, f, r;
7747         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7748         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7749         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7750                 p = board[r][f];
7751                 pCnt[p]++;
7752                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7753                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7754                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7755                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7756                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7757                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7758         }
7759 }
7760
7761 int
7762 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7763 {
7764         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7765         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7766
7767         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7768         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7769         if(myPawns == 2 && nMine == 3) // KPP
7770             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7771         if(myPawns == 1 && nMine == 2) // KP
7772             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7773         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7774             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7775         if(myPawns) return FALSE;
7776         if(pCnt[WhiteRook+side])
7777             return pCnt[BlackRook-side] ||
7778                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7779                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7780                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7781         if(pCnt[WhiteCannon+side]) {
7782             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7783             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7784         }
7785         if(pCnt[WhiteKnight+side])
7786             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7787         return FALSE;
7788 }
7789
7790 int
7791 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7792 {
7793         VariantClass v = gameInfo.variant;
7794
7795         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7796         if(v == VariantShatranj) return TRUE; // always winnable through baring
7797         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7798         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7799
7800         if(v == VariantXiangqi) {
7801                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7802
7803                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7804                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7805                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7806                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7807                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7808                 if(stale) // we have at least one last-rank P plus perhaps C
7809                     return majors // KPKX
7810                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7811                 else // KCA*E*
7812                     return pCnt[WhiteFerz+side] // KCAK
7813                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7814                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7815                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7816
7817         } else if(v == VariantKnightmate) {
7818                 if(nMine == 1) return FALSE;
7819                 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
7820         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7821                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7822
7823                 if(nMine == 1) return FALSE; // bare King
7824                 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
7825                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7826                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7827                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7828                 if(pCnt[WhiteKnight+side])
7829                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7830                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7831                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7832                 if(nBishops)
7833                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7834                 if(pCnt[WhiteAlfil+side])
7835                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7836                 if(pCnt[WhiteWazir+side])
7837                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7838         }
7839
7840         return TRUE;
7841 }
7842
7843 int
7844 CompareWithRights (Board b1, Board b2)
7845 {
7846     int rights = 0;
7847     if(!CompareBoards(b1, b2)) return FALSE;
7848     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7849     /* compare castling rights */
7850     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7851            rights++; /* King lost rights, while rook still had them */
7852     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7853         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7854            rights++; /* but at least one rook lost them */
7855     }
7856     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7857            rights++;
7858     if( b1[CASTLING][5] != NoRights ) {
7859         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7860            rights++;
7861     }
7862     return rights == 0;
7863 }
7864
7865 int
7866 Adjudicate (ChessProgramState *cps)
7867 {       // [HGM] some adjudications useful with buggy engines
7868         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7869         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7870         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7871         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7872         int k, drop, count = 0; static int bare = 1;
7873         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7874         Boolean canAdjudicate = !appData.icsActive;
7875
7876         // most tests only when we understand the game, i.e. legality-checking on
7877             if( appData.testLegality )
7878             {   /* [HGM] Some more adjudications for obstinate engines */
7879                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7880                 static int moveCount = 6;
7881                 ChessMove result;
7882                 char *reason = NULL;
7883
7884                 /* Count what is on board. */
7885                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7886
7887                 /* Some material-based adjudications that have to be made before stalemate test */
7888                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7889                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7890                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7891                      if(canAdjudicate && appData.checkMates) {
7892                          if(engineOpponent)
7893                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7894                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7895                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7896                          return 1;
7897                      }
7898                 }
7899
7900                 /* Bare King in Shatranj (loses) or Losers (wins) */
7901                 if( nrW == 1 || nrB == 1) {
7902                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7903                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7904                      if(canAdjudicate && appData.checkMates) {
7905                          if(engineOpponent)
7906                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7907                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7908                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7909                          return 1;
7910                      }
7911                   } else
7912                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7913                   {    /* bare King */
7914                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7915                         if(canAdjudicate && appData.checkMates) {
7916                             /* but only adjudicate if adjudication enabled */
7917                             if(engineOpponent)
7918                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7919                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7920                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7921                             return 1;
7922                         }
7923                   }
7924                 } else bare = 1;
7925
7926
7927             // don't wait for engine to announce game end if we can judge ourselves
7928             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7929               case MT_CHECK:
7930                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7931                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7932                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7933                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7934                             checkCnt++;
7935                         if(checkCnt >= 2) {
7936                             reason = "Xboard adjudication: 3rd check";
7937                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7938                             break;
7939                         }
7940                     }
7941                 }
7942               case MT_NONE:
7943               default:
7944                 break;
7945               case MT_STALEMATE:
7946               case MT_STAINMATE:
7947                 reason = "Xboard adjudication: Stalemate";
7948                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7949                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7950                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7951                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7952                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7953                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7954                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7955                                                                         EP_CHECKMATE : EP_WINS);
7956                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
7957                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7958                 }
7959                 break;
7960               case MT_CHECKMATE:
7961                 reason = "Xboard adjudication: Checkmate";
7962                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7963                 if(gameInfo.variant == VariantShogi) {
7964                     if(forwardMostMove > backwardMostMove
7965                        && moveList[forwardMostMove-1][1] == '@'
7966                        && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
7967                         reason = "XBoard adjudication: pawn-drop mate";
7968                         boards[forwardMostMove][EP_STATUS] = EP_WINS;
7969                     }
7970                 }
7971                 break;
7972             }
7973
7974                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7975                     case EP_STALEMATE:
7976                         result = GameIsDrawn; break;
7977                     case EP_CHECKMATE:
7978                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7979                     case EP_WINS:
7980                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7981                     default:
7982                         result = EndOfFile;
7983                 }
7984                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7985                     if(engineOpponent)
7986                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7987                     GameEnds( result, reason, GE_XBOARD );
7988                     return 1;
7989                 }
7990
7991                 /* Next absolutely insufficient mating material. */
7992                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7993                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7994                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7995
7996                      /* always flag draws, for judging claims */
7997                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7998
7999                      if(canAdjudicate && appData.materialDraws) {
8000                          /* but only adjudicate them if adjudication enabled */
8001                          if(engineOpponent) {
8002                            SendToProgram("force\n", engineOpponent); // suppress reply
8003                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
8004                          }
8005                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
8006                          return 1;
8007                      }
8008                 }
8009
8010                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
8011                 if(gameInfo.variant == VariantXiangqi ?
8012                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
8013                  : nrW + nrB == 4 &&
8014                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
8015                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
8016                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
8017                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
8018                    ) ) {
8019                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
8020                      {    /* if the first 3 moves do not show a tactical win, declare draw */
8021                           if(engineOpponent) {
8022                             SendToProgram("force\n", engineOpponent); // suppress reply
8023                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8024                           }
8025                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
8026                           return 1;
8027                      }
8028                 } else moveCount = 6;
8029             }
8030
8031         // Repetition draws and 50-move rule can be applied independently of legality testing
8032
8033                 /* Check for rep-draws */
8034                 count = 0;
8035                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
8036                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
8037                 for(k = forwardMostMove-2;
8038                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
8039                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
8040                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
8041                     k-=2)
8042                 {   int rights=0;
8043                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
8044                         /* compare castling rights */
8045                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8046                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8047                                 rights++; /* King lost rights, while rook still had them */
8048                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8049                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8050                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8051                                    rights++; /* but at least one rook lost them */
8052                         }
8053                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8054                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8055                                 rights++;
8056                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8057                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8058                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8059                                    rights++;
8060                         }
8061                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8062                             && appData.drawRepeats > 1) {
8063                              /* adjudicate after user-specified nr of repeats */
8064                              int result = GameIsDrawn;
8065                              char *details = "XBoard adjudication: repetition draw";
8066                              if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8067                                 // [HGM] xiangqi: check for forbidden perpetuals
8068                                 int m, ourPerpetual = 1, hisPerpetual = 1;
8069                                 for(m=forwardMostMove; m>k; m-=2) {
8070                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8071                                         ourPerpetual = 0; // the current mover did not always check
8072                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8073                                         hisPerpetual = 0; // the opponent did not always check
8074                                 }
8075                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8076                                                                         ourPerpetual, hisPerpetual);
8077                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8078                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8079                                     details = "Xboard adjudication: perpetual checking";
8080                                 } else
8081                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8082                                     break; // (or we would have caught him before). Abort repetition-checking loop.
8083                                 } else
8084                                 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8085                                     if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8086                                         result = BlackWins;
8087                                         details = "Xboard adjudication: repetition";
8088                                     }
8089                                 } else // it must be XQ
8090                                 // Now check for perpetual chases
8091                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8092                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
8093                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8094                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8095                                         static char resdet[MSG_SIZ];
8096                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8097                                         details = resdet;
8098                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8099                                     } else
8100                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
8101                                         break; // Abort repetition-checking loop.
8102                                 }
8103                                 // if neither of us is checking or chasing all the time, or both are, it is draw
8104                              }
8105                              if(engineOpponent) {
8106                                SendToProgram("force\n", engineOpponent); // suppress reply
8107                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8108                              }
8109                              GameEnds( result, details, GE_XBOARD );
8110                              return 1;
8111                         }
8112                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8113                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8114                     }
8115                 }
8116
8117                 /* Now we test for 50-move draws. Determine ply count */
8118                 count = forwardMostMove;
8119                 /* look for last irreversble move */
8120                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8121                     count--;
8122                 /* if we hit starting position, add initial plies */
8123                 if( count == backwardMostMove )
8124                     count -= initialRulePlies;
8125                 count = forwardMostMove - count;
8126                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8127                         // adjust reversible move counter for checks in Xiangqi
8128                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
8129                         if(i < backwardMostMove) i = backwardMostMove;
8130                         while(i <= forwardMostMove) {
8131                                 lastCheck = inCheck; // check evasion does not count
8132                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8133                                 if(inCheck || lastCheck) count--; // check does not count
8134                                 i++;
8135                         }
8136                 }
8137                 if( count >= 100)
8138                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8139                          /* this is used to judge if draw claims are legal */
8140                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8141                          if(engineOpponent) {
8142                            SendToProgram("force\n", engineOpponent); // suppress reply
8143                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8144                          }
8145                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8146                          return 1;
8147                 }
8148
8149                 /* if draw offer is pending, treat it as a draw claim
8150                  * when draw condition present, to allow engines a way to
8151                  * claim draws before making their move to avoid a race
8152                  * condition occurring after their move
8153                  */
8154                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8155                          char *p = NULL;
8156                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8157                              p = "Draw claim: 50-move rule";
8158                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8159                              p = "Draw claim: 3-fold repetition";
8160                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8161                              p = "Draw claim: insufficient mating material";
8162                          if( p != NULL && canAdjudicate) {
8163                              if(engineOpponent) {
8164                                SendToProgram("force\n", engineOpponent); // suppress reply
8165                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8166                              }
8167                              GameEnds( GameIsDrawn, p, GE_XBOARD );
8168                              return 1;
8169                          }
8170                 }
8171
8172                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8173                     if(engineOpponent) {
8174                       SendToProgram("force\n", engineOpponent); // suppress reply
8175                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8176                     }
8177                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8178                     return 1;
8179                 }
8180         return 0;
8181 }
8182
8183 char *
8184 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8185 {   // [HGM] book: this routine intercepts moves to simulate book replies
8186     char *bookHit = NULL;
8187
8188     //first determine if the incoming move brings opponent into his book
8189     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8190         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8191     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8192     if(bookHit != NULL && !cps->bookSuspend) {
8193         // make sure opponent is not going to reply after receiving move to book position
8194         SendToProgram("force\n", cps);
8195         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8196     }
8197     if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8198     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8199     // now arrange restart after book miss
8200     if(bookHit) {
8201         // after a book hit we never send 'go', and the code after the call to this routine
8202         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8203         char buf[MSG_SIZ], *move = bookHit;
8204         if(cps->useSAN) {
8205             int fromX, fromY, toX, toY;
8206             char promoChar;
8207             ChessMove moveType;
8208             move = buf + 30;
8209             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8210                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8211                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8212                                     PosFlags(forwardMostMove),
8213                                     fromY, fromX, toY, toX, promoChar, move);
8214             } else {
8215                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8216                 bookHit = NULL;
8217             }
8218         }
8219         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8220         SendToProgram(buf, cps);
8221         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8222     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8223         SendToProgram("go\n", cps);
8224         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8225     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8226         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8227             SendToProgram("go\n", cps);
8228         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8229     }
8230     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8231 }
8232
8233 int
8234 LoadError (char *errmess, ChessProgramState *cps)
8235 {   // unloads engine and switches back to -ncp mode if it was first
8236     if(cps->initDone) return FALSE;
8237     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8238     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8239     cps->pr = NoProc;
8240     if(cps == &first) {
8241         appData.noChessProgram = TRUE;
8242         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8243         gameMode = BeginningOfGame; ModeHighlight();
8244         SetNCPMode();
8245     }
8246     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8247     DisplayMessage("", ""); // erase waiting message
8248     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8249     return TRUE;
8250 }
8251
8252 char *savedMessage;
8253 ChessProgramState *savedState;
8254 void
8255 DeferredBookMove (void)
8256 {
8257         if(savedState->lastPing != savedState->lastPong)
8258                     ScheduleDelayedEvent(DeferredBookMove, 10);
8259         else
8260         HandleMachineMove(savedMessage, savedState);
8261 }
8262
8263 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8264 static ChessProgramState *stalledEngine;
8265 static char stashedInputMove[MSG_SIZ];
8266
8267 void
8268 HandleMachineMove (char *message, ChessProgramState *cps)
8269 {
8270     static char firstLeg[20];
8271     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8272     char realname[MSG_SIZ];
8273     int fromX, fromY, toX, toY;
8274     ChessMove moveType;
8275     char promoChar;
8276     char *p, *pv=buf1;
8277     int machineWhite, oldError;
8278     char *bookHit;
8279
8280     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8281         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8282         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8283             DisplayError(_("Invalid pairing from pairing engine"), 0);
8284             return;
8285         }
8286         pairingReceived = 1;
8287         NextMatchGame();
8288         return; // Skim the pairing messages here.
8289     }
8290
8291     oldError = cps->userError; cps->userError = 0;
8292
8293 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8294     /*
8295      * Kludge to ignore BEL characters
8296      */
8297     while (*message == '\007') message++;
8298
8299     /*
8300      * [HGM] engine debug message: ignore lines starting with '#' character
8301      */
8302     if(cps->debug && *message == '#') return;
8303
8304     /*
8305      * Look for book output
8306      */
8307     if (cps == &first && bookRequested) {
8308         if (message[0] == '\t' || message[0] == ' ') {
8309             /* Part of the book output is here; append it */
8310             strcat(bookOutput, message);
8311             strcat(bookOutput, "  \n");
8312             return;
8313         } else if (bookOutput[0] != NULLCHAR) {
8314             /* All of book output has arrived; display it */
8315             char *p = bookOutput;
8316             while (*p != NULLCHAR) {
8317                 if (*p == '\t') *p = ' ';
8318                 p++;
8319             }
8320             DisplayInformation(bookOutput);
8321             bookRequested = FALSE;
8322             /* Fall through to parse the current output */
8323         }
8324     }
8325
8326     /*
8327      * Look for machine move.
8328      */
8329     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8330         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8331     {
8332         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8333             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8334             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8335             stalledEngine = cps;
8336             if(appData.ponderNextMove) { // bring opponent out of ponder
8337                 if(gameMode == TwoMachinesPlay) {
8338                     if(cps->other->pause)
8339                         PauseEngine(cps->other);
8340                     else
8341                         SendToProgram("easy\n", cps->other);
8342                 }
8343             }
8344             StopClocks();
8345             return;
8346         }
8347
8348         /* This method is only useful on engines that support ping */
8349         if (cps->lastPing != cps->lastPong) {
8350           if (gameMode == BeginningOfGame) {
8351             /* Extra move from before last new; ignore */
8352             if (appData.debugMode) {
8353                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8354             }
8355           } else {
8356             if (appData.debugMode) {
8357                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8358                         cps->which, gameMode);
8359             }
8360
8361             SendToProgram("undo\n", cps);
8362           }
8363           return;
8364         }
8365
8366         switch (gameMode) {
8367           case BeginningOfGame:
8368             /* Extra move from before last reset; ignore */
8369             if (appData.debugMode) {
8370                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8371             }
8372             return;
8373
8374           case EndOfGame:
8375           case IcsIdle:
8376           default:
8377             /* Extra move after we tried to stop.  The mode test is
8378                not a reliable way of detecting this problem, but it's
8379                the best we can do on engines that don't support ping.
8380             */
8381             if (appData.debugMode) {
8382                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8383                         cps->which, gameMode);
8384             }
8385             SendToProgram("undo\n", cps);
8386             return;
8387
8388           case MachinePlaysWhite:
8389           case IcsPlayingWhite:
8390             machineWhite = TRUE;
8391             break;
8392
8393           case MachinePlaysBlack:
8394           case IcsPlayingBlack:
8395             machineWhite = FALSE;
8396             break;
8397
8398           case TwoMachinesPlay:
8399             machineWhite = (cps->twoMachinesColor[0] == 'w');
8400             break;
8401         }
8402         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8403             if (appData.debugMode) {
8404                 fprintf(debugFP,
8405                         "Ignoring move out of turn by %s, gameMode %d"
8406                         ", forwardMost %d\n",
8407                         cps->which, gameMode, forwardMostMove);
8408             }
8409             return;
8410         }
8411
8412         if(cps->alphaRank) AlphaRank(machineMove, 4);
8413
8414         // [HGM] lion: (some very limited) support for Alien protocol
8415         killX = killY = -1;
8416         if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
8417             safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
8418             return;
8419         } else if(firstLeg[0]) { // there was a previous leg;
8420             // only support case where same piece makes two step (and don't even test that!)
8421             char buf[20], *p = machineMove+1, *q = buf+1, f;
8422             safeStrCpy(buf, machineMove, 20);
8423             while(isdigit(*q)) q++; // find start of to-square
8424             safeStrCpy(machineMove, firstLeg, 20);
8425             while(isdigit(*p)) p++;
8426             safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
8427             sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
8428             firstLeg[0] = NULLCHAR;
8429         }
8430
8431         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8432                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8433             /* Machine move could not be parsed; ignore it. */
8434           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8435                     machineMove, _(cps->which));
8436             DisplayMoveError(buf1);
8437             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
8438                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
8439             if (gameMode == TwoMachinesPlay) {
8440               GameEnds(machineWhite ? BlackWins : WhiteWins,
8441                        buf1, GE_XBOARD);
8442             }
8443             return;
8444         }
8445
8446         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8447         /* So we have to redo legality test with true e.p. status here,  */
8448         /* to make sure an illegal e.p. capture does not slip through,   */
8449         /* to cause a forfeit on a justified illegal-move complaint      */
8450         /* of the opponent.                                              */
8451         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8452            ChessMove moveType;
8453            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8454                              fromY, fromX, toY, toX, promoChar);
8455             if(moveType == IllegalMove) {
8456               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8457                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8458                 GameEnds(machineWhite ? BlackWins : WhiteWins,
8459                            buf1, GE_XBOARD);
8460                 return;
8461            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
8462            /* [HGM] Kludge to handle engines that send FRC-style castling
8463               when they shouldn't (like TSCP-Gothic) */
8464            switch(moveType) {
8465              case WhiteASideCastleFR:
8466              case BlackASideCastleFR:
8467                toX+=2;
8468                currentMoveString[2]++;
8469                break;
8470              case WhiteHSideCastleFR:
8471              case BlackHSideCastleFR:
8472                toX--;
8473                currentMoveString[2]--;
8474                break;
8475              default: ; // nothing to do, but suppresses warning of pedantic compilers
8476            }
8477         }
8478         hintRequested = FALSE;
8479         lastHint[0] = NULLCHAR;
8480         bookRequested = FALSE;
8481         /* Program may be pondering now */
8482         cps->maybeThinking = TRUE;
8483         if (cps->sendTime == 2) cps->sendTime = 1;
8484         if (cps->offeredDraw) cps->offeredDraw--;
8485
8486         /* [AS] Save move info*/
8487         pvInfoList[ forwardMostMove ].score = programStats.score;
8488         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8489         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8490
8491         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8492
8493         /* Test suites abort the 'game' after one move */
8494         if(*appData.finger) {
8495            static FILE *f;
8496            char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8497            if(!f) f = fopen(appData.finger, "w");
8498            if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8499            else { DisplayFatalError("Bad output file", errno, 0); return; }
8500            free(fen);
8501            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8502         }
8503
8504         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8505         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8506             int count = 0;
8507
8508             while( count < adjudicateLossPlies ) {
8509                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8510
8511                 if( count & 1 ) {
8512                     score = -score; /* Flip score for winning side */
8513                 }
8514
8515                 if( score > adjudicateLossThreshold ) {
8516                     break;
8517                 }
8518
8519                 count++;
8520             }
8521
8522             if( count >= adjudicateLossPlies ) {
8523                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8524
8525                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8526                     "Xboard adjudication",
8527                     GE_XBOARD );
8528
8529                 return;
8530             }
8531         }
8532
8533         if(Adjudicate(cps)) {
8534             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8535             return; // [HGM] adjudicate: for all automatic game ends
8536         }
8537
8538 #if ZIPPY
8539         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8540             first.initDone) {
8541           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8542                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8543                 SendToICS("draw ");
8544                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8545           }
8546           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8547           ics_user_moved = 1;
8548           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8549                 char buf[3*MSG_SIZ];
8550
8551                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8552                         programStats.score / 100.,
8553                         programStats.depth,
8554                         programStats.time / 100.,
8555                         (unsigned int)programStats.nodes,
8556                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8557                         programStats.movelist);
8558                 SendToICS(buf);
8559 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8560           }
8561         }
8562 #endif
8563
8564         /* [AS] Clear stats for next move */
8565         ClearProgramStats();
8566         thinkOutput[0] = NULLCHAR;
8567         hiddenThinkOutputState = 0;
8568
8569         bookHit = NULL;
8570         if (gameMode == TwoMachinesPlay) {
8571             /* [HGM] relaying draw offers moved to after reception of move */
8572             /* and interpreting offer as claim if it brings draw condition */
8573             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8574                 SendToProgram("draw\n", cps->other);
8575             }
8576             if (cps->other->sendTime) {
8577                 SendTimeRemaining(cps->other,
8578                                   cps->other->twoMachinesColor[0] == 'w');
8579             }
8580             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8581             if (firstMove && !bookHit) {
8582                 firstMove = FALSE;
8583                 if (cps->other->useColors) {
8584                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8585                 }
8586                 SendToProgram("go\n", cps->other);
8587             }
8588             cps->other->maybeThinking = TRUE;
8589         }
8590
8591         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8592
8593         if (!pausing && appData.ringBellAfterMoves) {
8594             RingBell();
8595         }
8596
8597         /*
8598          * Reenable menu items that were disabled while
8599          * machine was thinking
8600          */
8601         if (gameMode != TwoMachinesPlay)
8602             SetUserThinkingEnables();
8603
8604         // [HGM] book: after book hit opponent has received move and is now in force mode
8605         // force the book reply into it, and then fake that it outputted this move by jumping
8606         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8607         if(bookHit) {
8608                 static char bookMove[MSG_SIZ]; // a bit generous?
8609
8610                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8611                 strcat(bookMove, bookHit);
8612                 message = bookMove;
8613                 cps = cps->other;
8614                 programStats.nodes = programStats.depth = programStats.time =
8615                 programStats.score = programStats.got_only_move = 0;
8616                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8617
8618                 if(cps->lastPing != cps->lastPong) {
8619                     savedMessage = message; // args for deferred call
8620                     savedState = cps;
8621                     ScheduleDelayedEvent(DeferredBookMove, 10);
8622                     return;
8623                 }
8624                 goto FakeBookMove;
8625         }
8626
8627         return;
8628     }
8629
8630     /* Set special modes for chess engines.  Later something general
8631      *  could be added here; for now there is just one kludge feature,
8632      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8633      *  when "xboard" is given as an interactive command.
8634      */
8635     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8636         cps->useSigint = FALSE;
8637         cps->useSigterm = FALSE;
8638     }
8639     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8640       ParseFeatures(message+8, cps);
8641       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8642     }
8643
8644     if (!strncmp(message, "setup ", 6) && 
8645         (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown || NonStandardBoardSize())
8646                                         ) { // [HGM] allow first engine to define opening position
8647       int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
8648       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8649       *buf = NULLCHAR;
8650       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8651       if(startedFromSetupPosition) return;
8652       dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
8653       if(dummy >= 3) {
8654         while(message[s] && message[s++] != ' ');
8655         if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
8656            dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
8657             appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
8658             if(dummy == 4) gameInfo.variant = StringToVariant(varName);     // parent variant
8659           InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
8660           if(*buf) SetCharTable(pieceToChar, buf); // do again, for it was spoiled by InitPosition
8661         }
8662       }
8663       ParseFEN(boards[0], &dummy, message+s, FALSE);
8664       DrawPosition(TRUE, boards[0]);
8665       startedFromSetupPosition = TRUE;
8666       return;
8667     }
8668     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8669      * want this, I was asked to put it in, and obliged.
8670      */
8671     if (!strncmp(message, "setboard ", 9)) {
8672         Board initial_position;
8673
8674         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8675
8676         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
8677             DisplayError(_("Bad FEN received from engine"), 0);
8678             return ;
8679         } else {
8680            Reset(TRUE, FALSE);
8681            CopyBoard(boards[0], initial_position);
8682            initialRulePlies = FENrulePlies;
8683            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8684            else gameMode = MachinePlaysBlack;
8685            DrawPosition(FALSE, boards[currentMove]);
8686         }
8687         return;
8688     }
8689
8690     /*
8691      * Look for communication commands
8692      */
8693     if (!strncmp(message, "telluser ", 9)) {
8694         if(message[9] == '\\' && message[10] == '\\')
8695             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8696         PlayTellSound();
8697         DisplayNote(message + 9);
8698         return;
8699     }
8700     if (!strncmp(message, "tellusererror ", 14)) {
8701         cps->userError = 1;
8702         if(message[14] == '\\' && message[15] == '\\')
8703             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8704         PlayTellSound();
8705         DisplayError(message + 14, 0);
8706         return;
8707     }
8708     if (!strncmp(message, "tellopponent ", 13)) {
8709       if (appData.icsActive) {
8710         if (loggedOn) {
8711           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8712           SendToICS(buf1);
8713         }
8714       } else {
8715         DisplayNote(message + 13);
8716       }
8717       return;
8718     }
8719     if (!strncmp(message, "tellothers ", 11)) {
8720       if (appData.icsActive) {
8721         if (loggedOn) {
8722           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8723           SendToICS(buf1);
8724         }
8725       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
8726       return;
8727     }
8728     if (!strncmp(message, "tellall ", 8)) {
8729       if (appData.icsActive) {
8730         if (loggedOn) {
8731           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8732           SendToICS(buf1);
8733         }
8734       } else {
8735         DisplayNote(message + 8);
8736       }
8737       return;
8738     }
8739     if (strncmp(message, "warning", 7) == 0) {
8740         /* Undocumented feature, use tellusererror in new code */
8741         DisplayError(message, 0);
8742         return;
8743     }
8744     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8745         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8746         strcat(realname, " query");
8747         AskQuestion(realname, buf2, buf1, cps->pr);
8748         return;
8749     }
8750     /* Commands from the engine directly to ICS.  We don't allow these to be
8751      *  sent until we are logged on. Crafty kibitzes have been known to
8752      *  interfere with the login process.
8753      */
8754     if (loggedOn) {
8755         if (!strncmp(message, "tellics ", 8)) {
8756             SendToICS(message + 8);
8757             SendToICS("\n");
8758             return;
8759         }
8760         if (!strncmp(message, "tellicsnoalias ", 15)) {
8761             SendToICS(ics_prefix);
8762             SendToICS(message + 15);
8763             SendToICS("\n");
8764             return;
8765         }
8766         /* The following are for backward compatibility only */
8767         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8768             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8769             SendToICS(ics_prefix);
8770             SendToICS(message);
8771             SendToICS("\n");
8772             return;
8773         }
8774     }
8775     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8776         return;
8777     }
8778     if(!strncmp(message, "highlight ", 10)) {
8779         if(appData.testLegality && appData.markers) return;
8780         MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
8781         return;
8782     }
8783     if(!strncmp(message, "click ", 6)) {
8784         char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
8785         if(appData.testLegality || !appData.oneClick) return;
8786         sscanf(message+6, "%c%d%c", &f, &y, &c);
8787         x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
8788         if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
8789         x = x*squareSize + (x+1)*lineGap + squareSize/2;
8790         y = y*squareSize + (y+1)*lineGap + squareSize/2;
8791         f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
8792         if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
8793             LeftClick(Release, lastLeftX, lastLeftY);
8794         controlKey  = (c == ',');
8795         LeftClick(Press, x, y);
8796         LeftClick(Release, x, y);
8797         first.highlight = f;
8798         return;
8799     }
8800     /*
8801      * If the move is illegal, cancel it and redraw the board.
8802      * Also deal with other error cases.  Matching is rather loose
8803      * here to accommodate engines written before the spec.
8804      */
8805     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8806         strncmp(message, "Error", 5) == 0) {
8807         if (StrStr(message, "name") ||
8808             StrStr(message, "rating") || StrStr(message, "?") ||
8809             StrStr(message, "result") || StrStr(message, "board") ||
8810             StrStr(message, "bk") || StrStr(message, "computer") ||
8811             StrStr(message, "variant") || StrStr(message, "hint") ||
8812             StrStr(message, "random") || StrStr(message, "depth") ||
8813             StrStr(message, "accepted")) {
8814             return;
8815         }
8816         if (StrStr(message, "protover")) {
8817           /* Program is responding to input, so it's apparently done
8818              initializing, and this error message indicates it is
8819              protocol version 1.  So we don't need to wait any longer
8820              for it to initialize and send feature commands. */
8821           FeatureDone(cps, 1);
8822           cps->protocolVersion = 1;
8823           return;
8824         }
8825         cps->maybeThinking = FALSE;
8826
8827         if (StrStr(message, "draw")) {
8828             /* Program doesn't have "draw" command */
8829             cps->sendDrawOffers = 0;
8830             return;
8831         }
8832         if (cps->sendTime != 1 &&
8833             (StrStr(message, "time") || StrStr(message, "otim"))) {
8834           /* Program apparently doesn't have "time" or "otim" command */
8835           cps->sendTime = 0;
8836           return;
8837         }
8838         if (StrStr(message, "analyze")) {
8839             cps->analysisSupport = FALSE;
8840             cps->analyzing = FALSE;
8841 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8842             EditGameEvent(); // [HGM] try to preserve loaded game
8843             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8844             DisplayError(buf2, 0);
8845             return;
8846         }
8847         if (StrStr(message, "(no matching move)st")) {
8848           /* Special kludge for GNU Chess 4 only */
8849           cps->stKludge = TRUE;
8850           SendTimeControl(cps, movesPerSession, timeControl,
8851                           timeIncrement, appData.searchDepth,
8852                           searchTime);
8853           return;
8854         }
8855         if (StrStr(message, "(no matching move)sd")) {
8856           /* Special kludge for GNU Chess 4 only */
8857           cps->sdKludge = TRUE;
8858           SendTimeControl(cps, movesPerSession, timeControl,
8859                           timeIncrement, appData.searchDepth,
8860                           searchTime);
8861           return;
8862         }
8863         if (!StrStr(message, "llegal")) {
8864             return;
8865         }
8866         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8867             gameMode == IcsIdle) return;
8868         if (forwardMostMove <= backwardMostMove) return;
8869         if (pausing) PauseEvent();
8870       if(appData.forceIllegal) {
8871             // [HGM] illegal: machine refused move; force position after move into it
8872           SendToProgram("force\n", cps);
8873           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8874                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8875                 // when black is to move, while there might be nothing on a2 or black
8876                 // might already have the move. So send the board as if white has the move.
8877                 // But first we must change the stm of the engine, as it refused the last move
8878                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8879                 if(WhiteOnMove(forwardMostMove)) {
8880                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8881                     SendBoard(cps, forwardMostMove); // kludgeless board
8882                 } else {
8883                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8884                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8885                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8886                 }
8887           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8888             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8889                  gameMode == TwoMachinesPlay)
8890               SendToProgram("go\n", cps);
8891             return;
8892       } else
8893         if (gameMode == PlayFromGameFile) {
8894             /* Stop reading this game file */
8895             gameMode = EditGame;
8896             ModeHighlight();
8897         }
8898         /* [HGM] illegal-move claim should forfeit game when Xboard */
8899         /* only passes fully legal moves                            */
8900         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8901             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8902                                 "False illegal-move claim", GE_XBOARD );
8903             return; // do not take back move we tested as valid
8904         }
8905         currentMove = forwardMostMove-1;
8906         DisplayMove(currentMove-1); /* before DisplayMoveError */
8907         SwitchClocks(forwardMostMove-1); // [HGM] race
8908         DisplayBothClocks();
8909         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8910                 parseList[currentMove], _(cps->which));
8911         DisplayMoveError(buf1);
8912         DrawPosition(FALSE, boards[currentMove]);
8913
8914         SetUserThinkingEnables();
8915         return;
8916     }
8917     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8918         /* Program has a broken "time" command that
8919            outputs a string not ending in newline.
8920            Don't use it. */
8921         cps->sendTime = 0;
8922     }
8923
8924     /*
8925      * If chess program startup fails, exit with an error message.
8926      * Attempts to recover here are futile. [HGM] Well, we try anyway
8927      */
8928     if ((StrStr(message, "unknown host") != NULL)
8929         || (StrStr(message, "No remote directory") != NULL)
8930         || (StrStr(message, "not found") != NULL)
8931         || (StrStr(message, "No such file") != NULL)
8932         || (StrStr(message, "can't alloc") != NULL)
8933         || (StrStr(message, "Permission denied") != NULL)) {
8934
8935         cps->maybeThinking = FALSE;
8936         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8937                 _(cps->which), cps->program, cps->host, message);
8938         RemoveInputSource(cps->isr);
8939         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8940             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
8941             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
8942         }
8943         return;
8944     }
8945
8946     /*
8947      * Look for hint output
8948      */
8949     if (sscanf(message, "Hint: %s", buf1) == 1) {
8950         if (cps == &first && hintRequested) {
8951             hintRequested = FALSE;
8952             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8953                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8954                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8955                                     PosFlags(forwardMostMove),
8956                                     fromY, fromX, toY, toX, promoChar, buf1);
8957                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8958                 DisplayInformation(buf2);
8959             } else {
8960                 /* Hint move could not be parsed!? */
8961               snprintf(buf2, sizeof(buf2),
8962                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8963                         buf1, _(cps->which));
8964                 DisplayError(buf2, 0);
8965             }
8966         } else {
8967           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8968         }
8969         return;
8970     }
8971
8972     /*
8973      * Ignore other messages if game is not in progress
8974      */
8975     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8976         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8977
8978     /*
8979      * look for win, lose, draw, or draw offer
8980      */
8981     if (strncmp(message, "1-0", 3) == 0) {
8982         char *p, *q, *r = "";
8983         p = strchr(message, '{');
8984         if (p) {
8985             q = strchr(p, '}');
8986             if (q) {
8987                 *q = NULLCHAR;
8988                 r = p + 1;
8989             }
8990         }
8991         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8992         return;
8993     } else if (strncmp(message, "0-1", 3) == 0) {
8994         char *p, *q, *r = "";
8995         p = strchr(message, '{');
8996         if (p) {
8997             q = strchr(p, '}');
8998             if (q) {
8999                 *q = NULLCHAR;
9000                 r = p + 1;
9001             }
9002         }
9003         /* Kludge for Arasan 4.1 bug */
9004         if (strcmp(r, "Black resigns") == 0) {
9005             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
9006             return;
9007         }
9008         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
9009         return;
9010     } else if (strncmp(message, "1/2", 3) == 0) {
9011         char *p, *q, *r = "";
9012         p = strchr(message, '{');
9013         if (p) {
9014             q = strchr(p, '}');
9015             if (q) {
9016                 *q = NULLCHAR;
9017                 r = p + 1;
9018             }
9019         }
9020
9021         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9022         return;
9023
9024     } else if (strncmp(message, "White resign", 12) == 0) {
9025         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9026         return;
9027     } else if (strncmp(message, "Black resign", 12) == 0) {
9028         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9029         return;
9030     } else if (strncmp(message, "White matches", 13) == 0 ||
9031                strncmp(message, "Black matches", 13) == 0   ) {
9032         /* [HGM] ignore GNUShogi noises */
9033         return;
9034     } else if (strncmp(message, "White", 5) == 0 &&
9035                message[5] != '(' &&
9036                StrStr(message, "Black") == NULL) {
9037         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9038         return;
9039     } else if (strncmp(message, "Black", 5) == 0 &&
9040                message[5] != '(') {
9041         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9042         return;
9043     } else if (strcmp(message, "resign") == 0 ||
9044                strcmp(message, "computer resigns") == 0) {
9045         switch (gameMode) {
9046           case MachinePlaysBlack:
9047           case IcsPlayingBlack:
9048             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9049             break;
9050           case MachinePlaysWhite:
9051           case IcsPlayingWhite:
9052             GameEnds(BlackWins, "White resigns", GE_ENGINE);
9053             break;
9054           case TwoMachinesPlay:
9055             if (cps->twoMachinesColor[0] == 'w')
9056               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9057             else
9058               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9059             break;
9060           default:
9061             /* can't happen */
9062             break;
9063         }
9064         return;
9065     } else if (strncmp(message, "opponent mates", 14) == 0) {
9066         switch (gameMode) {
9067           case MachinePlaysBlack:
9068           case IcsPlayingBlack:
9069             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9070             break;
9071           case MachinePlaysWhite:
9072           case IcsPlayingWhite:
9073             GameEnds(BlackWins, "Black mates", GE_ENGINE);
9074             break;
9075           case TwoMachinesPlay:
9076             if (cps->twoMachinesColor[0] == 'w')
9077               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9078             else
9079               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9080             break;
9081           default:
9082             /* can't happen */
9083             break;
9084         }
9085         return;
9086     } else if (strncmp(message, "computer mates", 14) == 0) {
9087         switch (gameMode) {
9088           case MachinePlaysBlack:
9089           case IcsPlayingBlack:
9090             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9091             break;
9092           case MachinePlaysWhite:
9093           case IcsPlayingWhite:
9094             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9095             break;
9096           case TwoMachinesPlay:
9097             if (cps->twoMachinesColor[0] == 'w')
9098               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9099             else
9100               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9101             break;
9102           default:
9103             /* can't happen */
9104             break;
9105         }
9106         return;
9107     } else if (strncmp(message, "checkmate", 9) == 0) {
9108         if (WhiteOnMove(forwardMostMove)) {
9109             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9110         } else {
9111             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9112         }
9113         return;
9114     } else if (strstr(message, "Draw") != NULL ||
9115                strstr(message, "game is a draw") != NULL) {
9116         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9117         return;
9118     } else if (strstr(message, "offer") != NULL &&
9119                strstr(message, "draw") != NULL) {
9120 #if ZIPPY
9121         if (appData.zippyPlay && first.initDone) {
9122             /* Relay offer to ICS */
9123             SendToICS(ics_prefix);
9124             SendToICS("draw\n");
9125         }
9126 #endif
9127         cps->offeredDraw = 2; /* valid until this engine moves twice */
9128         if (gameMode == TwoMachinesPlay) {
9129             if (cps->other->offeredDraw) {
9130                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9131             /* [HGM] in two-machine mode we delay relaying draw offer      */
9132             /* until after we also have move, to see if it is really claim */
9133             }
9134         } else if (gameMode == MachinePlaysWhite ||
9135                    gameMode == MachinePlaysBlack) {
9136           if (userOfferedDraw) {
9137             DisplayInformation(_("Machine accepts your draw offer"));
9138             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9139           } else {
9140             DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9141           }
9142         }
9143     }
9144
9145
9146     /*
9147      * Look for thinking output
9148      */
9149     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9150           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9151                                 ) {
9152         int plylev, mvleft, mvtot, curscore, time;
9153         char mvname[MOVE_LEN];
9154         u64 nodes; // [DM]
9155         char plyext;
9156         int ignore = FALSE;
9157         int prefixHint = FALSE;
9158         mvname[0] = NULLCHAR;
9159
9160         switch (gameMode) {
9161           case MachinePlaysBlack:
9162           case IcsPlayingBlack:
9163             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9164             break;
9165           case MachinePlaysWhite:
9166           case IcsPlayingWhite:
9167             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9168             break;
9169           case AnalyzeMode:
9170           case AnalyzeFile:
9171             break;
9172           case IcsObserving: /* [DM] icsEngineAnalyze */
9173             if (!appData.icsEngineAnalyze) ignore = TRUE;
9174             break;
9175           case TwoMachinesPlay:
9176             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9177                 ignore = TRUE;
9178             }
9179             break;
9180           default:
9181             ignore = TRUE;
9182             break;
9183         }
9184
9185         if (!ignore) {
9186             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9187             buf1[0] = NULLCHAR;
9188             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9189                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9190
9191                 if (plyext != ' ' && plyext != '\t') {
9192                     time *= 100;
9193                 }
9194
9195                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9196                 if( cps->scoreIsAbsolute &&
9197                     ( gameMode == MachinePlaysBlack ||
9198                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9199                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
9200                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9201                      !WhiteOnMove(currentMove)
9202                     ) )
9203                 {
9204                     curscore = -curscore;
9205                 }
9206
9207                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9208
9209                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9210                         char buf[MSG_SIZ];
9211                         FILE *f;
9212                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9213                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9214                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9215                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9216                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
9217                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9218                                 fclose(f);
9219                         }
9220                         else
9221                           /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9222                           DisplayError(_("failed writing PV"), 0);
9223                 }
9224
9225                 tempStats.depth = plylev;
9226                 tempStats.nodes = nodes;
9227                 tempStats.time = time;
9228                 tempStats.score = curscore;
9229                 tempStats.got_only_move = 0;
9230
9231                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9232                         int ticklen;
9233
9234                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
9235                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9236                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9237                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9238                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9239                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9240                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9241                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9242                 }
9243
9244                 /* Buffer overflow protection */
9245                 if (pv[0] != NULLCHAR) {
9246                     if (strlen(pv) >= sizeof(tempStats.movelist)
9247                         && appData.debugMode) {
9248                         fprintf(debugFP,
9249                                 "PV is too long; using the first %u bytes.\n",
9250                                 (unsigned) sizeof(tempStats.movelist) - 1);
9251                     }
9252
9253                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9254                 } else {
9255                     sprintf(tempStats.movelist, " no PV\n");
9256                 }
9257
9258                 if (tempStats.seen_stat) {
9259                     tempStats.ok_to_send = 1;
9260                 }
9261
9262                 if (strchr(tempStats.movelist, '(') != NULL) {
9263                     tempStats.line_is_book = 1;
9264                     tempStats.nr_moves = 0;
9265                     tempStats.moves_left = 0;
9266                 } else {
9267                     tempStats.line_is_book = 0;
9268                 }
9269
9270                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9271                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9272
9273                 SendProgramStatsToFrontend( cps, &tempStats );
9274
9275                 /*
9276                     [AS] Protect the thinkOutput buffer from overflow... this
9277                     is only useful if buf1 hasn't overflowed first!
9278                 */
9279                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
9280                          plylev,
9281                          (gameMode == TwoMachinesPlay ?
9282                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9283                          ((double) curscore) / 100.0,
9284                          prefixHint ? lastHint : "",
9285                          prefixHint ? " " : "" );
9286
9287                 if( buf1[0] != NULLCHAR ) {
9288                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9289
9290                     if( strlen(pv) > max_len ) {
9291                         if( appData.debugMode) {
9292                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9293                         }
9294                         pv[max_len+1] = '\0';
9295                     }
9296
9297                     strcat( thinkOutput, pv);
9298                 }
9299
9300                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9301                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9302                     DisplayMove(currentMove - 1);
9303                 }
9304                 return;
9305
9306             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9307                 /* crafty (9.25+) says "(only move) <move>"
9308                  * if there is only 1 legal move
9309                  */
9310                 sscanf(p, "(only move) %s", buf1);
9311                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9312                 sprintf(programStats.movelist, "%s (only move)", buf1);
9313                 programStats.depth = 1;
9314                 programStats.nr_moves = 1;
9315                 programStats.moves_left = 1;
9316                 programStats.nodes = 1;
9317                 programStats.time = 1;
9318                 programStats.got_only_move = 1;
9319
9320                 /* Not really, but we also use this member to
9321                    mean "line isn't going to change" (Crafty
9322                    isn't searching, so stats won't change) */
9323                 programStats.line_is_book = 1;
9324
9325                 SendProgramStatsToFrontend( cps, &programStats );
9326
9327                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9328                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9329                     DisplayMove(currentMove - 1);
9330                 }
9331                 return;
9332             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9333                               &time, &nodes, &plylev, &mvleft,
9334                               &mvtot, mvname) >= 5) {
9335                 /* The stat01: line is from Crafty (9.29+) in response
9336                    to the "." command */
9337                 programStats.seen_stat = 1;
9338                 cps->maybeThinking = TRUE;
9339
9340                 if (programStats.got_only_move || !appData.periodicUpdates)
9341                   return;
9342
9343                 programStats.depth = plylev;
9344                 programStats.time = time;
9345                 programStats.nodes = nodes;
9346                 programStats.moves_left = mvleft;
9347                 programStats.nr_moves = mvtot;
9348                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9349                 programStats.ok_to_send = 1;
9350                 programStats.movelist[0] = '\0';
9351
9352                 SendProgramStatsToFrontend( cps, &programStats );
9353
9354                 return;
9355
9356             } else if (strncmp(message,"++",2) == 0) {
9357                 /* Crafty 9.29+ outputs this */
9358                 programStats.got_fail = 2;
9359                 return;
9360
9361             } else if (strncmp(message,"--",2) == 0) {
9362                 /* Crafty 9.29+ outputs this */
9363                 programStats.got_fail = 1;
9364                 return;
9365
9366             } else if (thinkOutput[0] != NULLCHAR &&
9367                        strncmp(message, "    ", 4) == 0) {
9368                 unsigned message_len;
9369
9370                 p = message;
9371                 while (*p && *p == ' ') p++;
9372
9373                 message_len = strlen( p );
9374
9375                 /* [AS] Avoid buffer overflow */
9376                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9377                     strcat(thinkOutput, " ");
9378                     strcat(thinkOutput, p);
9379                 }
9380
9381                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9382                     strcat(programStats.movelist, " ");
9383                     strcat(programStats.movelist, p);
9384                 }
9385
9386                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9387                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9388                     DisplayMove(currentMove - 1);
9389                 }
9390                 return;
9391             }
9392         }
9393         else {
9394             buf1[0] = NULLCHAR;
9395
9396             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9397                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9398             {
9399                 ChessProgramStats cpstats;
9400
9401                 if (plyext != ' ' && plyext != '\t') {
9402                     time *= 100;
9403                 }
9404
9405                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9406                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9407                     curscore = -curscore;
9408                 }
9409
9410                 cpstats.depth = plylev;
9411                 cpstats.nodes = nodes;
9412                 cpstats.time = time;
9413                 cpstats.score = curscore;
9414                 cpstats.got_only_move = 0;
9415                 cpstats.movelist[0] = '\0';
9416
9417                 if (buf1[0] != NULLCHAR) {
9418                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9419                 }
9420
9421                 cpstats.ok_to_send = 0;
9422                 cpstats.line_is_book = 0;
9423                 cpstats.nr_moves = 0;
9424                 cpstats.moves_left = 0;
9425
9426                 SendProgramStatsToFrontend( cps, &cpstats );
9427             }
9428         }
9429     }
9430 }
9431
9432
9433 /* Parse a game score from the character string "game", and
9434    record it as the history of the current game.  The game
9435    score is NOT assumed to start from the standard position.
9436    The display is not updated in any way.
9437    */
9438 void
9439 ParseGameHistory (char *game)
9440 {
9441     ChessMove moveType;
9442     int fromX, fromY, toX, toY, boardIndex;
9443     char promoChar;
9444     char *p, *q;
9445     char buf[MSG_SIZ];
9446
9447     if (appData.debugMode)
9448       fprintf(debugFP, "Parsing game history: %s\n", game);
9449
9450     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9451     gameInfo.site = StrSave(appData.icsHost);
9452     gameInfo.date = PGNDate();
9453     gameInfo.round = StrSave("-");
9454
9455     /* Parse out names of players */
9456     while (*game == ' ') game++;
9457     p = buf;
9458     while (*game != ' ') *p++ = *game++;
9459     *p = NULLCHAR;
9460     gameInfo.white = StrSave(buf);
9461     while (*game == ' ') game++;
9462     p = buf;
9463     while (*game != ' ' && *game != '\n') *p++ = *game++;
9464     *p = NULLCHAR;
9465     gameInfo.black = StrSave(buf);
9466
9467     /* Parse moves */
9468     boardIndex = blackPlaysFirst ? 1 : 0;
9469     yynewstr(game);
9470     for (;;) {
9471         yyboardindex = boardIndex;
9472         moveType = (ChessMove) Myylex();
9473         switch (moveType) {
9474           case IllegalMove:             /* maybe suicide chess, etc. */
9475   if (appData.debugMode) {
9476     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9477     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9478     setbuf(debugFP, NULL);
9479   }
9480           case WhitePromotion:
9481           case BlackPromotion:
9482           case WhiteNonPromotion:
9483           case BlackNonPromotion:
9484           case NormalMove:
9485           case WhiteCapturesEnPassant:
9486           case BlackCapturesEnPassant:
9487           case WhiteKingSideCastle:
9488           case WhiteQueenSideCastle:
9489           case BlackKingSideCastle:
9490           case BlackQueenSideCastle:
9491           case WhiteKingSideCastleWild:
9492           case WhiteQueenSideCastleWild:
9493           case BlackKingSideCastleWild:
9494           case BlackQueenSideCastleWild:
9495           /* PUSH Fabien */
9496           case WhiteHSideCastleFR:
9497           case WhiteASideCastleFR:
9498           case BlackHSideCastleFR:
9499           case BlackASideCastleFR:
9500           /* POP Fabien */
9501             fromX = currentMoveString[0] - AAA;
9502             fromY = currentMoveString[1] - ONE;
9503             toX = currentMoveString[2] - AAA;
9504             toY = currentMoveString[3] - ONE;
9505             promoChar = currentMoveString[4];
9506             break;
9507           case WhiteDrop:
9508           case BlackDrop:
9509             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9510             fromX = moveType == WhiteDrop ?
9511               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9512             (int) CharToPiece(ToLower(currentMoveString[0]));
9513             fromY = DROP_RANK;
9514             toX = currentMoveString[2] - AAA;
9515             toY = currentMoveString[3] - ONE;
9516             promoChar = NULLCHAR;
9517             break;
9518           case AmbiguousMove:
9519             /* bug? */
9520             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9521   if (appData.debugMode) {
9522     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9523     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9524     setbuf(debugFP, NULL);
9525   }
9526             DisplayError(buf, 0);
9527             return;
9528           case ImpossibleMove:
9529             /* bug? */
9530             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9531   if (appData.debugMode) {
9532     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9533     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9534     setbuf(debugFP, NULL);
9535   }
9536             DisplayError(buf, 0);
9537             return;
9538           case EndOfFile:
9539             if (boardIndex < backwardMostMove) {
9540                 /* Oops, gap.  How did that happen? */
9541                 DisplayError(_("Gap in move list"), 0);
9542                 return;
9543             }
9544             backwardMostMove =  blackPlaysFirst ? 1 : 0;
9545             if (boardIndex > forwardMostMove) {
9546                 forwardMostMove = boardIndex;
9547             }
9548             return;
9549           case ElapsedTime:
9550             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9551                 strcat(parseList[boardIndex-1], " ");
9552                 strcat(parseList[boardIndex-1], yy_text);
9553             }
9554             continue;
9555           case Comment:
9556           case PGNTag:
9557           case NAG:
9558           default:
9559             /* ignore */
9560             continue;
9561           case WhiteWins:
9562           case BlackWins:
9563           case GameIsDrawn:
9564           case GameUnfinished:
9565             if (gameMode == IcsExamining) {
9566                 if (boardIndex < backwardMostMove) {
9567                     /* Oops, gap.  How did that happen? */
9568                     return;
9569                 }
9570                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9571                 return;
9572             }
9573             gameInfo.result = moveType;
9574             p = strchr(yy_text, '{');
9575             if (p == NULL) p = strchr(yy_text, '(');
9576             if (p == NULL) {
9577                 p = yy_text;
9578                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9579             } else {
9580                 q = strchr(p, *p == '{' ? '}' : ')');
9581                 if (q != NULL) *q = NULLCHAR;
9582                 p++;
9583             }
9584             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9585             gameInfo.resultDetails = StrSave(p);
9586             continue;
9587         }
9588         if (boardIndex >= forwardMostMove &&
9589             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9590             backwardMostMove = blackPlaysFirst ? 1 : 0;
9591             return;
9592         }
9593         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9594                                  fromY, fromX, toY, toX, promoChar,
9595                                  parseList[boardIndex]);
9596         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9597         /* currentMoveString is set as a side-effect of yylex */
9598         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9599         strcat(moveList[boardIndex], "\n");
9600         boardIndex++;
9601         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9602         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9603           case MT_NONE:
9604           case MT_STALEMATE:
9605           default:
9606             break;
9607           case MT_CHECK:
9608             if(gameInfo.variant != VariantShogi)
9609                 strcat(parseList[boardIndex - 1], "+");
9610             break;
9611           case MT_CHECKMATE:
9612           case MT_STAINMATE:
9613             strcat(parseList[boardIndex - 1], "#");
9614             break;
9615         }
9616     }
9617 }
9618
9619
9620 /* Apply a move to the given board  */
9621 void
9622 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9623 {
9624   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9625   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9626
9627     /* [HGM] compute & store e.p. status and castling rights for new position */
9628     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9629
9630       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9631       oldEP = (signed char)board[EP_STATUS];
9632       board[EP_STATUS] = EP_NONE;
9633
9634   if (fromY == DROP_RANK) {
9635         /* must be first */
9636         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9637             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9638             return;
9639         }
9640         piece = board[toY][toX] = (ChessSquare) fromX;
9641   } else {
9642       int i;
9643
9644       if( killX >= 0 && killY >= 0 ) // [HGM] lion: Lion trampled over something
9645            board[killY][killX] = EmptySquare,
9646            board[EP_STATUS] = EP_CAPTURE;
9647
9648       if( board[toY][toX] != EmptySquare )
9649            board[EP_STATUS] = EP_CAPTURE;
9650
9651       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9652            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9653                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9654       } else
9655       if( board[fromY][fromX] == WhitePawn ) {
9656            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9657                board[EP_STATUS] = EP_PAWN_MOVE;
9658            if( toY-fromY==2) {
9659                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9660                         gameInfo.variant != VariantBerolina || toX < fromX)
9661                       board[EP_STATUS] = toX | berolina;
9662                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9663                         gameInfo.variant != VariantBerolina || toX > fromX)
9664                       board[EP_STATUS] = toX;
9665            }
9666       } else
9667       if( board[fromY][fromX] == BlackPawn ) {
9668            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9669                board[EP_STATUS] = EP_PAWN_MOVE;
9670            if( toY-fromY== -2) {
9671                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9672                         gameInfo.variant != VariantBerolina || toX < fromX)
9673                       board[EP_STATUS] = toX | berolina;
9674                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9675                         gameInfo.variant != VariantBerolina || toX > fromX)
9676                       board[EP_STATUS] = toX;
9677            }
9678        }
9679
9680        for(i=0; i<nrCastlingRights; i++) {
9681            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9682               board[CASTLING][i] == toX   && castlingRank[i] == toY
9683              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9684        }
9685
9686        if(gameInfo.variant == VariantSChess) { // update virginity
9687            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
9688            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
9689            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
9690            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
9691        }
9692
9693      if (fromX == toX && fromY == toY) return;
9694
9695      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9696      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9697      if(gameInfo.variant == VariantKnightmate)
9698          king += (int) WhiteUnicorn - (int) WhiteKing;
9699
9700     /* Code added by Tord: */
9701     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9702     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9703         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9704       board[fromY][fromX] = EmptySquare;
9705       board[toY][toX] = EmptySquare;
9706       if((toX > fromX) != (piece == WhiteRook)) {
9707         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9708       } else {
9709         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9710       }
9711     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9712                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9713       board[fromY][fromX] = EmptySquare;
9714       board[toY][toX] = EmptySquare;
9715       if((toX > fromX) != (piece == BlackRook)) {
9716         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9717       } else {
9718         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9719       }
9720     /* End of code added by Tord */
9721
9722     } else if (board[fromY][fromX] == king
9723         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9724         && toY == fromY && toX > fromX+1) {
9725         board[fromY][fromX] = EmptySquare;
9726         board[toY][toX] = king;
9727         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9728         board[fromY][BOARD_RGHT-1] = EmptySquare;
9729     } else if (board[fromY][fromX] == king
9730         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9731                && toY == fromY && toX < fromX-1) {
9732         board[fromY][fromX] = EmptySquare;
9733         board[toY][toX] = king;
9734         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9735         board[fromY][BOARD_LEFT] = EmptySquare;
9736     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9737                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9738                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9739                ) {
9740         /* white pawn promotion */
9741         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9742         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9743             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9744         board[fromY][fromX] = EmptySquare;
9745     } else if ((fromY >= BOARD_HEIGHT>>1)
9746                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9747                && (toX != fromX)
9748                && gameInfo.variant != VariantXiangqi
9749                && gameInfo.variant != VariantBerolina
9750                && (board[fromY][fromX] == WhitePawn)
9751                && (board[toY][toX] == EmptySquare)) {
9752         board[fromY][fromX] = EmptySquare;
9753         board[toY][toX] = WhitePawn;
9754         captured = board[toY - 1][toX];
9755         board[toY - 1][toX] = EmptySquare;
9756     } else if ((fromY == BOARD_HEIGHT-4)
9757                && (toX == fromX)
9758                && gameInfo.variant == VariantBerolina
9759                && (board[fromY][fromX] == WhitePawn)
9760                && (board[toY][toX] == EmptySquare)) {
9761         board[fromY][fromX] = EmptySquare;
9762         board[toY][toX] = WhitePawn;
9763         if(oldEP & EP_BEROLIN_A) {
9764                 captured = board[fromY][fromX-1];
9765                 board[fromY][fromX-1] = EmptySquare;
9766         }else{  captured = board[fromY][fromX+1];
9767                 board[fromY][fromX+1] = EmptySquare;
9768         }
9769     } else if (board[fromY][fromX] == king
9770         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9771                && toY == fromY && toX > fromX+1) {
9772         board[fromY][fromX] = EmptySquare;
9773         board[toY][toX] = king;
9774         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9775         board[fromY][BOARD_RGHT-1] = EmptySquare;
9776     } else if (board[fromY][fromX] == king
9777         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9778                && toY == fromY && toX < fromX-1) {
9779         board[fromY][fromX] = EmptySquare;
9780         board[toY][toX] = king;
9781         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9782         board[fromY][BOARD_LEFT] = EmptySquare;
9783     } else if (fromY == 7 && fromX == 3
9784                && board[fromY][fromX] == BlackKing
9785                && toY == 7 && toX == 5) {
9786         board[fromY][fromX] = EmptySquare;
9787         board[toY][toX] = BlackKing;
9788         board[fromY][7] = EmptySquare;
9789         board[toY][4] = BlackRook;
9790     } else if (fromY == 7 && fromX == 3
9791                && board[fromY][fromX] == BlackKing
9792                && toY == 7 && toX == 1) {
9793         board[fromY][fromX] = EmptySquare;
9794         board[toY][toX] = BlackKing;
9795         board[fromY][0] = EmptySquare;
9796         board[toY][2] = BlackRook;
9797     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9798                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9799                && toY < promoRank && promoChar
9800                ) {
9801         /* black pawn promotion */
9802         board[toY][toX] = CharToPiece(ToLower(promoChar));
9803         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9804             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9805         board[fromY][fromX] = EmptySquare;
9806     } else if ((fromY < BOARD_HEIGHT>>1)
9807                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9808                && (toX != fromX)
9809                && gameInfo.variant != VariantXiangqi
9810                && gameInfo.variant != VariantBerolina
9811                && (board[fromY][fromX] == BlackPawn)
9812                && (board[toY][toX] == EmptySquare)) {
9813         board[fromY][fromX] = EmptySquare;
9814         board[toY][toX] = BlackPawn;
9815         captured = board[toY + 1][toX];
9816         board[toY + 1][toX] = EmptySquare;
9817     } else if ((fromY == 3)
9818                && (toX == fromX)
9819                && gameInfo.variant == VariantBerolina
9820                && (board[fromY][fromX] == BlackPawn)
9821                && (board[toY][toX] == EmptySquare)) {
9822         board[fromY][fromX] = EmptySquare;
9823         board[toY][toX] = BlackPawn;
9824         if(oldEP & EP_BEROLIN_A) {
9825                 captured = board[fromY][fromX-1];
9826                 board[fromY][fromX-1] = EmptySquare;
9827         }else{  captured = board[fromY][fromX+1];
9828                 board[fromY][fromX+1] = EmptySquare;
9829         }
9830     } else {
9831         ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
9832         board[fromY][fromX] = EmptySquare;
9833         board[toY][toX] = piece;
9834     }
9835   }
9836
9837     if (gameInfo.holdingsWidth != 0) {
9838
9839       /* !!A lot more code needs to be written to support holdings  */
9840       /* [HGM] OK, so I have written it. Holdings are stored in the */
9841       /* penultimate board files, so they are automaticlly stored   */
9842       /* in the game history.                                       */
9843       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9844                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9845         /* Delete from holdings, by decreasing count */
9846         /* and erasing image if necessary            */
9847         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9848         if(p < (int) BlackPawn) { /* white drop */
9849              p -= (int)WhitePawn;
9850                  p = PieceToNumber((ChessSquare)p);
9851              if(p >= gameInfo.holdingsSize) p = 0;
9852              if(--board[p][BOARD_WIDTH-2] <= 0)
9853                   board[p][BOARD_WIDTH-1] = EmptySquare;
9854              if((int)board[p][BOARD_WIDTH-2] < 0)
9855                         board[p][BOARD_WIDTH-2] = 0;
9856         } else {                  /* black drop */
9857              p -= (int)BlackPawn;
9858                  p = PieceToNumber((ChessSquare)p);
9859              if(p >= gameInfo.holdingsSize) p = 0;
9860              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9861                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9862              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9863                         board[BOARD_HEIGHT-1-p][1] = 0;
9864         }
9865       }
9866       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9867           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9868         /* [HGM] holdings: Add to holdings, if holdings exist */
9869         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9870                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9871                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9872         }
9873         p = (int) captured;
9874         if (p >= (int) BlackPawn) {
9875           p -= (int)BlackPawn;
9876           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9877                   /* in Shogi restore piece to its original  first */
9878                   captured = (ChessSquare) (DEMOTED captured);
9879                   p = DEMOTED p;
9880           }
9881           p = PieceToNumber((ChessSquare)p);
9882           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9883           board[p][BOARD_WIDTH-2]++;
9884           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9885         } else {
9886           p -= (int)WhitePawn;
9887           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9888                   captured = (ChessSquare) (DEMOTED captured);
9889                   p = DEMOTED p;
9890           }
9891           p = PieceToNumber((ChessSquare)p);
9892           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9893           board[BOARD_HEIGHT-1-p][1]++;
9894           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9895         }
9896       }
9897     } else if (gameInfo.variant == VariantAtomic) {
9898       if (captured != EmptySquare) {
9899         int y, x;
9900         for (y = toY-1; y <= toY+1; y++) {
9901           for (x = toX-1; x <= toX+1; x++) {
9902             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9903                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9904               board[y][x] = EmptySquare;
9905             }
9906           }
9907         }
9908         board[toY][toX] = EmptySquare;
9909       }
9910     }
9911     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9912         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9913     } else
9914     if(promoChar == '+') {
9915         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
9916         board[toY][toX] = (ChessSquare) (CHUPROMOTED piece);
9917     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9918         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9919         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
9920            && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
9921         board[toY][toX] = newPiece;
9922     }
9923     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9924                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9925         // [HGM] superchess: take promotion piece out of holdings
9926         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9927         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9928             if(!--board[k][BOARD_WIDTH-2])
9929                 board[k][BOARD_WIDTH-1] = EmptySquare;
9930         } else {
9931             if(!--board[BOARD_HEIGHT-1-k][1])
9932                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9933         }
9934     }
9935
9936 }
9937
9938 /* Updates forwardMostMove */
9939 void
9940 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
9941 {
9942     int x = toX, y = toY;
9943     char *s = parseList[forwardMostMove];
9944     ChessSquare p = boards[forwardMostMove][toY][toX];
9945 //    forwardMostMove++; // [HGM] bare: moved downstream
9946
9947     if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
9948     (void) CoordsToAlgebraic(boards[forwardMostMove],
9949                              PosFlags(forwardMostMove),
9950                              fromY, fromX, y, x, promoChar,
9951                              s);
9952     if(killX >= 0 && killY >= 0)
9953         sprintf(s + strlen(s), "%c%c%d", p == EmptySquare || toX == fromX && toY == fromY ? '-' : 'x', toX + AAA, toY + ONE - '0');
9954
9955     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9956         int timeLeft; static int lastLoadFlag=0; int king, piece;
9957         piece = boards[forwardMostMove][fromY][fromX];
9958         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9959         if(gameInfo.variant == VariantKnightmate)
9960             king += (int) WhiteUnicorn - (int) WhiteKing;
9961         if(forwardMostMove == 0) {
9962             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9963                 fprintf(serverMoves, "%s;", UserName());
9964             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9965                 fprintf(serverMoves, "%s;", second.tidy);
9966             fprintf(serverMoves, "%s;", first.tidy);
9967             if(gameMode == MachinePlaysWhite)
9968                 fprintf(serverMoves, "%s;", UserName());
9969             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9970                 fprintf(serverMoves, "%s;", second.tidy);
9971         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9972         lastLoadFlag = loadFlag;
9973         // print base move
9974         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9975         // print castling suffix
9976         if( toY == fromY && piece == king ) {
9977             if(toX-fromX > 1)
9978                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9979             if(fromX-toX >1)
9980                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9981         }
9982         // e.p. suffix
9983         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9984              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9985              boards[forwardMostMove][toY][toX] == EmptySquare
9986              && fromX != toX && fromY != toY)
9987                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9988         // promotion suffix
9989         if(promoChar != NULLCHAR) {
9990             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
9991                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
9992                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
9993             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
9994         }
9995         if(!loadFlag) {
9996                 char buf[MOVE_LEN*2], *p; int len;
9997             fprintf(serverMoves, "/%d/%d",
9998                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9999             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10000             else                      timeLeft = blackTimeRemaining/1000;
10001             fprintf(serverMoves, "/%d", timeLeft);
10002                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10003                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10004                 if(p = strchr(buf, '=')) *p = NULLCHAR;
10005                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10006             fprintf(serverMoves, "/%s", buf);
10007         }
10008         fflush(serverMoves);
10009     }
10010
10011     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10012         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10013       return;
10014     }
10015     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10016     if (commentList[forwardMostMove+1] != NULL) {
10017         free(commentList[forwardMostMove+1]);
10018         commentList[forwardMostMove+1] = NULL;
10019     }
10020     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10021     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10022     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10023     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10024     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10025     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10026     adjustedClock = FALSE;
10027     gameInfo.result = GameUnfinished;
10028     if (gameInfo.resultDetails != NULL) {
10029         free(gameInfo.resultDetails);
10030         gameInfo.resultDetails = NULL;
10031     }
10032     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10033                               moveList[forwardMostMove - 1]);
10034     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10035       case MT_NONE:
10036       case MT_STALEMATE:
10037       default:
10038         break;
10039       case MT_CHECK:
10040         if(gameInfo.variant != VariantShogi)
10041             strcat(parseList[forwardMostMove - 1], "+");
10042         break;
10043       case MT_CHECKMATE:
10044       case MT_STAINMATE:
10045         strcat(parseList[forwardMostMove - 1], "#");
10046         break;
10047     }
10048
10049     killX = killY = -1; // [HGM] lion: used up
10050 }
10051
10052 /* Updates currentMove if not pausing */
10053 void
10054 ShowMove (int fromX, int fromY, int toX, int toY)
10055 {
10056     int instant = (gameMode == PlayFromGameFile) ?
10057         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10058     if(appData.noGUI) return;
10059     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10060         if (!instant) {
10061             if (forwardMostMove == currentMove + 1) {
10062                 AnimateMove(boards[forwardMostMove - 1],
10063                             fromX, fromY, toX, toY);
10064             }
10065         }
10066         currentMove = forwardMostMove;
10067     }
10068
10069     if (instant) return;
10070
10071     DisplayMove(currentMove - 1);
10072     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10073             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10074                 SetHighlights(fromX, fromY, toX, toY);
10075             }
10076     }
10077     DrawPosition(FALSE, boards[currentMove]);
10078     DisplayBothClocks();
10079     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10080 }
10081
10082 void
10083 SendEgtPath (ChessProgramState *cps)
10084 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10085         char buf[MSG_SIZ], name[MSG_SIZ], *p;
10086
10087         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10088
10089         while(*p) {
10090             char c, *q = name+1, *r, *s;
10091
10092             name[0] = ','; // extract next format name from feature and copy with prefixed ','
10093             while(*p && *p != ',') *q++ = *p++;
10094             *q++ = ':'; *q = 0;
10095             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10096                 strcmp(name, ",nalimov:") == 0 ) {
10097                 // take nalimov path from the menu-changeable option first, if it is defined
10098               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10099                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
10100             } else
10101             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10102                 (s = StrStr(appData.egtFormats, name)) != NULL) {
10103                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10104                 s = r = StrStr(s, ":") + 1; // beginning of path info
10105                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10106                 c = *r; *r = 0;             // temporarily null-terminate path info
10107                     *--q = 0;               // strip of trailig ':' from name
10108                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10109                 *r = c;
10110                 SendToProgram(buf,cps);     // send egtbpath command for this format
10111             }
10112             if(*p == ',') p++; // read away comma to position for next format name
10113         }
10114 }
10115
10116 static int
10117 NonStandardBoardSize ()
10118 {
10119       /* [HGM] Awkward testing. Should really be a table */
10120       int overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
10121       if( gameInfo.variant == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10122       if( gameInfo.variant == VariantXiangqi )
10123            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
10124       if( gameInfo.variant == VariantShogi )
10125            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
10126       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
10127            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
10128       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
10129           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
10130            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
10131       if( gameInfo.variant == VariantCourier )
10132            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
10133       if( gameInfo.variant == VariantSuper )
10134            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
10135       if( gameInfo.variant == VariantGreat )
10136            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
10137       if( gameInfo.variant == VariantSChess )
10138            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
10139       if( gameInfo.variant == VariantGrand )
10140            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
10141       if( gameInfo.variant == VariantChu )
10142            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 12 || gameInfo.holdingsSize != 0;
10143       return overruled;
10144 }
10145
10146 void
10147 InitChessProgram (ChessProgramState *cps, int setup)
10148 /* setup needed to setup FRC opening position */
10149 {
10150     char buf[MSG_SIZ], b[MSG_SIZ];
10151     if (appData.noChessProgram) return;
10152     hintRequested = FALSE;
10153     bookRequested = FALSE;
10154
10155     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10156     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10157     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10158     if(cps->memSize) { /* [HGM] memory */
10159       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10160         SendToProgram(buf, cps);
10161     }
10162     SendEgtPath(cps); /* [HGM] EGT */
10163     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10164       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10165         SendToProgram(buf, cps);
10166     }
10167
10168     SendToProgram(cps->initString, cps);
10169     if (gameInfo.variant != VariantNormal &&
10170         gameInfo.variant != VariantLoadable
10171         /* [HGM] also send variant if board size non-standard */
10172         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
10173                                             ) {
10174       char *v = VariantName(gameInfo.variant);
10175       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
10176         /* [HGM] in protocol 1 we have to assume all variants valid */
10177         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
10178         DisplayFatalError(buf, 0, 1);
10179         return;
10180       }
10181
10182       if(NonStandardBoardSize()) { /* [HGM] make prefix for non-standard board size. */
10183         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
10184                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
10185            /* [HGM] varsize: try first if this defiant size variant is specifically known */
10186            if(StrStr(cps->variants, b) == NULL) {
10187                // specific sized variant not known, check if general sizing allowed
10188                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
10189                    if(StrStr(cps->variants, "boardsize") == NULL) {
10190                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10191                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
10192                        DisplayFatalError(buf, 0, 1);
10193                        return;
10194                    }
10195                    /* [HGM] here we really should compare with the maximum supported board size */
10196                }
10197            }
10198       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
10199       snprintf(buf, MSG_SIZ, "variant %s\n", b);
10200       SendToProgram(buf, cps);
10201     }
10202     currentlyInitializedVariant = gameInfo.variant;
10203
10204     /* [HGM] send opening position in FRC to first engine */
10205     if(setup) {
10206           SendToProgram("force\n", cps);
10207           SendBoard(cps, 0);
10208           /* engine is now in force mode! Set flag to wake it up after first move. */
10209           setboardSpoiledMachineBlack = 1;
10210     }
10211
10212     if (cps->sendICS) {
10213       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10214       SendToProgram(buf, cps);
10215     }
10216     cps->maybeThinking = FALSE;
10217     cps->offeredDraw = 0;
10218     if (!appData.icsActive) {
10219         SendTimeControl(cps, movesPerSession, timeControl,
10220                         timeIncrement, appData.searchDepth,
10221                         searchTime);
10222     }
10223     if (appData.showThinking
10224         // [HGM] thinking: four options require thinking output to be sent
10225         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10226                                 ) {
10227         SendToProgram("post\n", cps);
10228     }
10229     SendToProgram("hard\n", cps);
10230     if (!appData.ponderNextMove) {
10231         /* Warning: "easy" is a toggle in GNU Chess, so don't send
10232            it without being sure what state we are in first.  "hard"
10233            is not a toggle, so that one is OK.
10234          */
10235         SendToProgram("easy\n", cps);
10236     }
10237     if (cps->usePing) {
10238       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
10239       SendToProgram(buf, cps);
10240     }
10241     cps->initDone = TRUE;
10242     ClearEngineOutputPane(cps == &second);
10243 }
10244
10245
10246 void
10247 ResendOptions (ChessProgramState *cps)
10248 { // send the stored value of the options
10249   int i;
10250   char buf[MSG_SIZ];
10251   Option *opt = cps->option;
10252   for(i=0; i<cps->nrOptions; i++, opt++) {
10253       switch(opt->type) {
10254         case Spin:
10255         case Slider:
10256         case CheckBox:
10257             snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10258           break;
10259         case ComboBox:
10260           snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10261           break;
10262         default:
10263             snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10264           break;
10265         case Button:
10266         case SaveButton:
10267           continue;
10268       }
10269       SendToProgram(buf, cps);
10270   }
10271 }
10272
10273 void
10274 StartChessProgram (ChessProgramState *cps)
10275 {
10276     char buf[MSG_SIZ];
10277     int err;
10278
10279     if (appData.noChessProgram) return;
10280     cps->initDone = FALSE;
10281
10282     if (strcmp(cps->host, "localhost") == 0) {
10283         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10284     } else if (*appData.remoteShell == NULLCHAR) {
10285         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10286     } else {
10287         if (*appData.remoteUser == NULLCHAR) {
10288           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10289                     cps->program);
10290         } else {
10291           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10292                     cps->host, appData.remoteUser, cps->program);
10293         }
10294         err = StartChildProcess(buf, "", &cps->pr);
10295     }
10296
10297     if (err != 0) {
10298       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10299         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10300         if(cps != &first) return;
10301         appData.noChessProgram = TRUE;
10302         ThawUI();
10303         SetNCPMode();
10304 //      DisplayFatalError(buf, err, 1);
10305 //      cps->pr = NoProc;
10306 //      cps->isr = NULL;
10307         return;
10308     }
10309
10310     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10311     if (cps->protocolVersion > 1) {
10312       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10313       if(!cps->reload) { // do not clear options when reloading because of -xreuse
10314         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10315         cps->comboCnt = 0;  //                and values of combo boxes
10316       }
10317       SendToProgram(buf, cps);
10318       if(cps->reload) ResendOptions(cps);
10319     } else {
10320       SendToProgram("xboard\n", cps);
10321     }
10322 }
10323
10324 void
10325 TwoMachinesEventIfReady P((void))
10326 {
10327   static int curMess = 0;
10328   if (first.lastPing != first.lastPong) {
10329     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10330     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10331     return;
10332   }
10333   if (second.lastPing != second.lastPong) {
10334     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10335     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10336     return;
10337   }
10338   DisplayMessage("", ""); curMess = 0;
10339   TwoMachinesEvent();
10340 }
10341
10342 char *
10343 MakeName (char *template)
10344 {
10345     time_t clock;
10346     struct tm *tm;
10347     static char buf[MSG_SIZ];
10348     char *p = buf;
10349     int i;
10350
10351     clock = time((time_t *)NULL);
10352     tm = localtime(&clock);
10353
10354     while(*p++ = *template++) if(p[-1] == '%') {
10355         switch(*template++) {
10356           case 0:   *p = 0; return buf;
10357           case 'Y': i = tm->tm_year+1900; break;
10358           case 'y': i = tm->tm_year-100; break;
10359           case 'M': i = tm->tm_mon+1; break;
10360           case 'd': i = tm->tm_mday; break;
10361           case 'h': i = tm->tm_hour; break;
10362           case 'm': i = tm->tm_min; break;
10363           case 's': i = tm->tm_sec; break;
10364           default:  i = 0;
10365         }
10366         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10367     }
10368     return buf;
10369 }
10370
10371 int
10372 CountPlayers (char *p)
10373 {
10374     int n = 0;
10375     while(p = strchr(p, '\n')) p++, n++; // count participants
10376     return n;
10377 }
10378
10379 FILE *
10380 WriteTourneyFile (char *results, FILE *f)
10381 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10382     if(f == NULL) f = fopen(appData.tourneyFile, "w");
10383     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10384         // create a file with tournament description
10385         fprintf(f, "-participants {%s}\n", appData.participants);
10386         fprintf(f, "-seedBase %d\n", appData.seedBase);
10387         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10388         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10389         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10390         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10391         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10392         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10393         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10394         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10395         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10396         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10397         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10398         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10399         fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
10400         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10401         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10402         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10403         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10404         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10405         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10406         fprintf(f, "-smpCores %d\n", appData.smpCores);
10407         if(searchTime > 0)
10408                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10409         else {
10410                 fprintf(f, "-mps %d\n", appData.movesPerSession);
10411                 fprintf(f, "-tc %s\n", appData.timeControl);
10412                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10413         }
10414         fprintf(f, "-results \"%s\"\n", results);
10415     }
10416     return f;
10417 }
10418
10419 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10420
10421 void
10422 Substitute (char *participants, int expunge)
10423 {
10424     int i, changed, changes=0, nPlayers=0;
10425     char *p, *q, *r, buf[MSG_SIZ];
10426     if(participants == NULL) return;
10427     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10428     r = p = participants; q = appData.participants;
10429     while(*p && *p == *q) {
10430         if(*p == '\n') r = p+1, nPlayers++;
10431         p++; q++;
10432     }
10433     if(*p) { // difference
10434         while(*p && *p++ != '\n');
10435         while(*q && *q++ != '\n');
10436       changed = nPlayers;
10437         changes = 1 + (strcmp(p, q) != 0);
10438     }
10439     if(changes == 1) { // a single engine mnemonic was changed
10440         q = r; while(*q) nPlayers += (*q++ == '\n');
10441         p = buf; while(*r && (*p = *r++) != '\n') p++;
10442         *p = NULLCHAR;
10443         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10444         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10445         if(mnemonic[i]) { // The substitute is valid
10446             FILE *f;
10447             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10448                 flock(fileno(f), LOCK_EX);
10449                 ParseArgsFromFile(f);
10450                 fseek(f, 0, SEEK_SET);
10451                 FREE(appData.participants); appData.participants = participants;
10452                 if(expunge) { // erase results of replaced engine
10453                     int len = strlen(appData.results), w, b, dummy;
10454                     for(i=0; i<len; i++) {
10455                         Pairing(i, nPlayers, &w, &b, &dummy);
10456                         if((w == changed || b == changed) && appData.results[i] == '*') {
10457                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10458                             fclose(f);
10459                             return;
10460                         }
10461                     }
10462                     for(i=0; i<len; i++) {
10463                         Pairing(i, nPlayers, &w, &b, &dummy);
10464                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10465                     }
10466                 }
10467                 WriteTourneyFile(appData.results, f);
10468                 fclose(f); // release lock
10469                 return;
10470             }
10471         } else DisplayError(_("No engine with the name you gave is installed"), 0);
10472     }
10473     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10474     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
10475     free(participants);
10476     return;
10477 }
10478
10479 int
10480 CheckPlayers (char *participants)
10481 {
10482         int i;
10483         char buf[MSG_SIZ], *p;
10484         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10485         while(p = strchr(participants, '\n')) {
10486             *p = NULLCHAR;
10487             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10488             if(!mnemonic[i]) {
10489                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10490                 *p = '\n';
10491                 DisplayError(buf, 0);
10492                 return 1;
10493             }
10494             *p = '\n';
10495             participants = p + 1;
10496         }
10497         return 0;
10498 }
10499
10500 int
10501 CreateTourney (char *name)
10502 {
10503         FILE *f;
10504         if(matchMode && strcmp(name, appData.tourneyFile)) {
10505              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10506         }
10507         if(name[0] == NULLCHAR) {
10508             if(appData.participants[0])
10509                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10510             return 0;
10511         }
10512         f = fopen(name, "r");
10513         if(f) { // file exists
10514             ASSIGN(appData.tourneyFile, name);
10515             ParseArgsFromFile(f); // parse it
10516         } else {
10517             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10518             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10519                 DisplayError(_("Not enough participants"), 0);
10520                 return 0;
10521             }
10522             if(CheckPlayers(appData.participants)) return 0;
10523             ASSIGN(appData.tourneyFile, name);
10524             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10525             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10526         }
10527         fclose(f);
10528         appData.noChessProgram = FALSE;
10529         appData.clockMode = TRUE;
10530         SetGNUMode();
10531         return 1;
10532 }
10533
10534 int
10535 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10536 {
10537     char buf[MSG_SIZ], *p, *q;
10538     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10539     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10540     skip = !all && group[0]; // if group requested, we start in skip mode
10541     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10542         p = names; q = buf; header = 0;
10543         while(*p && *p != '\n') *q++ = *p++;
10544         *q = 0;
10545         if(*p == '\n') p++;
10546         if(buf[0] == '#') {
10547             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10548             depth++; // we must be entering a new group
10549             if(all) continue; // suppress printing group headers when complete list requested
10550             header = 1;
10551             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10552         }
10553         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10554         if(engineList[i]) free(engineList[i]);
10555         engineList[i] = strdup(buf);
10556         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10557         if(engineMnemonic[i]) free(engineMnemonic[i]);
10558         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10559             strcat(buf, " (");
10560             sscanf(q + 8, "%s", buf + strlen(buf));
10561             strcat(buf, ")");
10562         }
10563         engineMnemonic[i] = strdup(buf);
10564         i++;
10565     }
10566     engineList[i] = engineMnemonic[i] = NULL;
10567     return i;
10568 }
10569
10570 // following implemented as macro to avoid type limitations
10571 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10572
10573 void
10574 SwapEngines (int n)
10575 {   // swap settings for first engine and other engine (so far only some selected options)
10576     int h;
10577     char *p;
10578     if(n == 0) return;
10579     SWAP(directory, p)
10580     SWAP(chessProgram, p)
10581     SWAP(isUCI, h)
10582     SWAP(hasOwnBookUCI, h)
10583     SWAP(protocolVersion, h)
10584     SWAP(reuse, h)
10585     SWAP(scoreIsAbsolute, h)
10586     SWAP(timeOdds, h)
10587     SWAP(logo, p)
10588     SWAP(pgnName, p)
10589     SWAP(pvSAN, h)
10590     SWAP(engOptions, p)
10591     SWAP(engInitString, p)
10592     SWAP(computerString, p)
10593     SWAP(features, p)
10594     SWAP(fenOverride, p)
10595     SWAP(NPS, h)
10596     SWAP(accumulateTC, h)
10597     SWAP(host, p)
10598 }
10599
10600 int
10601 GetEngineLine (char *s, int n)
10602 {
10603     int i;
10604     char buf[MSG_SIZ];
10605     extern char *icsNames;
10606     if(!s || !*s) return 0;
10607     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
10608     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
10609     if(!mnemonic[i]) return 0;
10610     if(n == 11) return 1; // just testing if there was a match
10611     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
10612     if(n == 1) SwapEngines(n);
10613     ParseArgsFromString(buf);
10614     if(n == 1) SwapEngines(n);
10615     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
10616         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
10617         ParseArgsFromString(buf);
10618     }
10619     return 1;
10620 }
10621
10622 int
10623 SetPlayer (int player, char *p)
10624 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
10625     int i;
10626     char buf[MSG_SIZ], *engineName;
10627     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10628     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10629     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10630     if(mnemonic[i]) {
10631         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10632         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10633         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10634         ParseArgsFromString(buf);
10635     } else { // no engine with this nickname is installed!
10636         snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
10637         ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
10638         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10639         ModeHighlight();
10640         DisplayError(buf, 0);
10641         return 0;
10642     }
10643     free(engineName);
10644     return i;
10645 }
10646
10647 char *recentEngines;
10648
10649 void
10650 RecentEngineEvent (int nr)
10651 {
10652     int n;
10653 //    SwapEngines(1); // bump first to second
10654 //    ReplaceEngine(&second, 1); // and load it there
10655     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10656     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10657     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10658         ReplaceEngine(&first, 0);
10659         FloatToFront(&appData.recentEngineList, command[n]);
10660     }
10661 }
10662
10663 int
10664 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10665 {   // determine players from game number
10666     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10667
10668     if(appData.tourneyType == 0) {
10669         roundsPerCycle = (nPlayers - 1) | 1;
10670         pairingsPerRound = nPlayers / 2;
10671     } else if(appData.tourneyType > 0) {
10672         roundsPerCycle = nPlayers - appData.tourneyType;
10673         pairingsPerRound = appData.tourneyType;
10674     }
10675     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10676     gamesPerCycle = gamesPerRound * roundsPerCycle;
10677     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10678     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10679     curRound = nr / gamesPerRound; nr %= gamesPerRound;
10680     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10681     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10682     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10683
10684     if(appData.cycleSync) *syncInterval = gamesPerCycle;
10685     if(appData.roundSync) *syncInterval = gamesPerRound;
10686
10687     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10688
10689     if(appData.tourneyType == 0) {
10690         if(curPairing == (nPlayers-1)/2 ) {
10691             *whitePlayer = curRound;
10692             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10693         } else {
10694             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10695             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10696             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10697             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10698         }
10699     } else if(appData.tourneyType > 1) {
10700         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10701         *whitePlayer = curRound + appData.tourneyType;
10702     } else if(appData.tourneyType > 0) {
10703         *whitePlayer = curPairing;
10704         *blackPlayer = curRound + appData.tourneyType;
10705     }
10706
10707     // take care of white/black alternation per round.
10708     // For cycles and games this is already taken care of by default, derived from matchGame!
10709     return curRound & 1;
10710 }
10711
10712 int
10713 NextTourneyGame (int nr, int *swapColors)
10714 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10715     char *p, *q;
10716     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
10717     FILE *tf;
10718     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10719     tf = fopen(appData.tourneyFile, "r");
10720     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10721     ParseArgsFromFile(tf); fclose(tf);
10722     InitTimeControls(); // TC might be altered from tourney file
10723
10724     nPlayers = CountPlayers(appData.participants); // count participants
10725     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10726     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10727
10728     if(syncInterval) {
10729         p = q = appData.results;
10730         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10731         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10732             DisplayMessage(_("Waiting for other game(s)"),"");
10733             waitingForGame = TRUE;
10734             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10735             return 0;
10736         }
10737         waitingForGame = FALSE;
10738     }
10739
10740     if(appData.tourneyType < 0) {
10741         if(nr>=0 && !pairingReceived) {
10742             char buf[1<<16];
10743             if(pairing.pr == NoProc) {
10744                 if(!appData.pairingEngine[0]) {
10745                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10746                     return 0;
10747                 }
10748                 StartChessProgram(&pairing); // starts the pairing engine
10749             }
10750             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10751             SendToProgram(buf, &pairing);
10752             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10753             SendToProgram(buf, &pairing);
10754             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10755         }
10756         pairingReceived = 0;                              // ... so we continue here
10757         *swapColors = 0;
10758         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10759         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10760         matchGame = 1; roundNr = nr / syncInterval + 1;
10761     }
10762
10763     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10764
10765     // redefine engines, engine dir, etc.
10766     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10767     if(first.pr == NoProc) {
10768       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
10769       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10770     }
10771     if(second.pr == NoProc) {
10772       SwapEngines(1);
10773       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
10774       SwapEngines(1);         // and make that valid for second engine by swapping
10775       InitEngine(&second, 1);
10776     }
10777     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10778     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10779     return OK;
10780 }
10781
10782 void
10783 NextMatchGame ()
10784 {   // performs game initialization that does not invoke engines, and then tries to start the game
10785     int res, firstWhite, swapColors = 0;
10786     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10787     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
10788         char buf[MSG_SIZ];
10789         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
10790         if(strcmp(buf, currentDebugFile)) { // name has changed
10791             FILE *f = fopen(buf, "w");
10792             if(f) { // if opening the new file failed, just keep using the old one
10793                 ASSIGN(currentDebugFile, buf);
10794                 fclose(debugFP);
10795                 debugFP = f;
10796             }
10797             if(appData.serverFileName) {
10798                 if(serverFP) fclose(serverFP);
10799                 serverFP = fopen(appData.serverFileName, "w");
10800                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
10801                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
10802             }
10803         }
10804     }
10805     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10806     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10807     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
10808     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10809     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10810     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10811     Reset(FALSE, first.pr != NoProc);
10812     res = LoadGameOrPosition(matchGame); // setup game
10813     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10814     if(!res) return; // abort when bad game/pos file
10815     TwoMachinesEvent();
10816 }
10817
10818 void
10819 UserAdjudicationEvent (int result)
10820 {
10821     ChessMove gameResult = GameIsDrawn;
10822
10823     if( result > 0 ) {
10824         gameResult = WhiteWins;
10825     }
10826     else if( result < 0 ) {
10827         gameResult = BlackWins;
10828     }
10829
10830     if( gameMode == TwoMachinesPlay ) {
10831         GameEnds( gameResult, "User adjudication", GE_XBOARD );
10832     }
10833 }
10834
10835
10836 // [HGM] save: calculate checksum of game to make games easily identifiable
10837 int
10838 StringCheckSum (char *s)
10839 {
10840         int i = 0;
10841         if(s==NULL) return 0;
10842         while(*s) i = i*259 + *s++;
10843         return i;
10844 }
10845
10846 int
10847 GameCheckSum ()
10848 {
10849         int i, sum=0;
10850         for(i=backwardMostMove; i<forwardMostMove; i++) {
10851                 sum += pvInfoList[i].depth;
10852                 sum += StringCheckSum(parseList[i]);
10853                 sum += StringCheckSum(commentList[i]);
10854                 sum *= 261;
10855         }
10856         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10857         return sum + StringCheckSum(commentList[i]);
10858 } // end of save patch
10859
10860 void
10861 GameEnds (ChessMove result, char *resultDetails, int whosays)
10862 {
10863     GameMode nextGameMode;
10864     int isIcsGame;
10865     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10866
10867     if(endingGame) return; /* [HGM] crash: forbid recursion */
10868     endingGame = 1;
10869     if(twoBoards) { // [HGM] dual: switch back to one board
10870         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10871         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10872     }
10873     if (appData.debugMode) {
10874       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10875               result, resultDetails ? resultDetails : "(null)", whosays);
10876     }
10877
10878     fromX = fromY = -1; // [HGM] abort any move the user is entering.
10879
10880     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
10881
10882     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10883         /* If we are playing on ICS, the server decides when the
10884            game is over, but the engine can offer to draw, claim
10885            a draw, or resign.
10886          */
10887 #if ZIPPY
10888         if (appData.zippyPlay && first.initDone) {
10889             if (result == GameIsDrawn) {
10890                 /* In case draw still needs to be claimed */
10891                 SendToICS(ics_prefix);
10892                 SendToICS("draw\n");
10893             } else if (StrCaseStr(resultDetails, "resign")) {
10894                 SendToICS(ics_prefix);
10895                 SendToICS("resign\n");
10896             }
10897         }
10898 #endif
10899         endingGame = 0; /* [HGM] crash */
10900         return;
10901     }
10902
10903     /* If we're loading the game from a file, stop */
10904     if (whosays == GE_FILE) {
10905       (void) StopLoadGameTimer();
10906       gameFileFP = NULL;
10907     }
10908
10909     /* Cancel draw offers */
10910     first.offeredDraw = second.offeredDraw = 0;
10911
10912     /* If this is an ICS game, only ICS can really say it's done;
10913        if not, anyone can. */
10914     isIcsGame = (gameMode == IcsPlayingWhite ||
10915                  gameMode == IcsPlayingBlack ||
10916                  gameMode == IcsObserving    ||
10917                  gameMode == IcsExamining);
10918
10919     if (!isIcsGame || whosays == GE_ICS) {
10920         /* OK -- not an ICS game, or ICS said it was done */
10921         StopClocks();
10922         if (!isIcsGame && !appData.noChessProgram)
10923           SetUserThinkingEnables();
10924
10925         /* [HGM] if a machine claims the game end we verify this claim */
10926         if(gameMode == TwoMachinesPlay && appData.testClaims) {
10927             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10928                 char claimer;
10929                 ChessMove trueResult = (ChessMove) -1;
10930
10931                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
10932                                             first.twoMachinesColor[0] :
10933                                             second.twoMachinesColor[0] ;
10934
10935                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10936                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10937                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10938                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10939                 } else
10940                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10941                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10942                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10943                 } else
10944                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10945                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10946                 }
10947
10948                 // now verify win claims, but not in drop games, as we don't understand those yet
10949                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10950                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10951                     (result == WhiteWins && claimer == 'w' ||
10952                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
10953                       if (appData.debugMode) {
10954                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
10955                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10956                       }
10957                       if(result != trueResult) {
10958                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10959                               result = claimer == 'w' ? BlackWins : WhiteWins;
10960                               resultDetails = buf;
10961                       }
10962                 } else
10963                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10964                     && (forwardMostMove <= backwardMostMove ||
10965                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10966                         (claimer=='b')==(forwardMostMove&1))
10967                                                                                   ) {
10968                       /* [HGM] verify: draws that were not flagged are false claims */
10969                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10970                       result = claimer == 'w' ? BlackWins : WhiteWins;
10971                       resultDetails = buf;
10972                 }
10973                 /* (Claiming a loss is accepted no questions asked!) */
10974             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
10975                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
10976                 result = GameUnfinished;
10977                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
10978             }
10979             /* [HGM] bare: don't allow bare King to win */
10980             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10981                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10982                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10983                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10984                && result != GameIsDrawn)
10985             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10986                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10987                         int p = (signed char)boards[forwardMostMove][i][j] - color;
10988                         if(p >= 0 && p <= (int)WhiteKing) k++;
10989                 }
10990                 if (appData.debugMode) {
10991                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10992                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10993                 }
10994                 if(k <= 1) {
10995                         result = GameIsDrawn;
10996                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10997                         resultDetails = buf;
10998                 }
10999             }
11000         }
11001
11002
11003         if(serverMoves != NULL && !loadFlag) { char c = '=';
11004             if(result==WhiteWins) c = '+';
11005             if(result==BlackWins) c = '-';
11006             if(resultDetails != NULL)
11007                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11008         }
11009         if (resultDetails != NULL) {
11010             gameInfo.result = result;
11011             gameInfo.resultDetails = StrSave(resultDetails);
11012
11013             /* display last move only if game was not loaded from file */
11014             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11015                 DisplayMove(currentMove - 1);
11016
11017             if (forwardMostMove != 0) {
11018                 if (gameMode != PlayFromGameFile && gameMode != EditGame
11019                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11020                                                                 ) {
11021                     if (*appData.saveGameFile != NULLCHAR) {
11022                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11023                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11024                         else
11025                         SaveGameToFile(appData.saveGameFile, TRUE);
11026                     } else if (appData.autoSaveGames) {
11027                         if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11028                     }
11029                     if (*appData.savePositionFile != NULLCHAR) {
11030                         SavePositionToFile(appData.savePositionFile);
11031                     }
11032                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11033                 }
11034             }
11035
11036             /* Tell program how game ended in case it is learning */
11037             /* [HGM] Moved this to after saving the PGN, just in case */
11038             /* engine died and we got here through time loss. In that */
11039             /* case we will get a fatal error writing the pipe, which */
11040             /* would otherwise lose us the PGN.                       */
11041             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
11042             /* output during GameEnds should never be fatal anymore   */
11043             if (gameMode == MachinePlaysWhite ||
11044                 gameMode == MachinePlaysBlack ||
11045                 gameMode == TwoMachinesPlay ||
11046                 gameMode == IcsPlayingWhite ||
11047                 gameMode == IcsPlayingBlack ||
11048                 gameMode == BeginningOfGame) {
11049                 char buf[MSG_SIZ];
11050                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11051                         resultDetails);
11052                 if (first.pr != NoProc) {
11053                     SendToProgram(buf, &first);
11054                 }
11055                 if (second.pr != NoProc &&
11056                     gameMode == TwoMachinesPlay) {
11057                     SendToProgram(buf, &second);
11058                 }
11059             }
11060         }
11061
11062         if (appData.icsActive) {
11063             if (appData.quietPlay &&
11064                 (gameMode == IcsPlayingWhite ||
11065                  gameMode == IcsPlayingBlack)) {
11066                 SendToICS(ics_prefix);
11067                 SendToICS("set shout 1\n");
11068             }
11069             nextGameMode = IcsIdle;
11070             ics_user_moved = FALSE;
11071             /* clean up premove.  It's ugly when the game has ended and the
11072              * premove highlights are still on the board.
11073              */
11074             if (gotPremove) {
11075               gotPremove = FALSE;
11076               ClearPremoveHighlights();
11077               DrawPosition(FALSE, boards[currentMove]);
11078             }
11079             if (whosays == GE_ICS) {
11080                 switch (result) {
11081                 case WhiteWins:
11082                     if (gameMode == IcsPlayingWhite)
11083                         PlayIcsWinSound();
11084                     else if(gameMode == IcsPlayingBlack)
11085                         PlayIcsLossSound();
11086                     break;
11087                 case BlackWins:
11088                     if (gameMode == IcsPlayingBlack)
11089                         PlayIcsWinSound();
11090                     else if(gameMode == IcsPlayingWhite)
11091                         PlayIcsLossSound();
11092                     break;
11093                 case GameIsDrawn:
11094                     PlayIcsDrawSound();
11095                     break;
11096                 default:
11097                     PlayIcsUnfinishedSound();
11098                 }
11099             }
11100             if(appData.quitNext) { ExitEvent(0); return; }
11101         } else if (gameMode == EditGame ||
11102                    gameMode == PlayFromGameFile ||
11103                    gameMode == AnalyzeMode ||
11104                    gameMode == AnalyzeFile) {
11105             nextGameMode = gameMode;
11106         } else {
11107             nextGameMode = EndOfGame;
11108         }
11109         pausing = FALSE;
11110         ModeHighlight();
11111     } else {
11112         nextGameMode = gameMode;
11113     }
11114
11115     if (appData.noChessProgram) {
11116         gameMode = nextGameMode;
11117         ModeHighlight();
11118         endingGame = 0; /* [HGM] crash */
11119         return;
11120     }
11121
11122     if (first.reuse) {
11123         /* Put first chess program into idle state */
11124         if (first.pr != NoProc &&
11125             (gameMode == MachinePlaysWhite ||
11126              gameMode == MachinePlaysBlack ||
11127              gameMode == TwoMachinesPlay ||
11128              gameMode == IcsPlayingWhite ||
11129              gameMode == IcsPlayingBlack ||
11130              gameMode == BeginningOfGame)) {
11131             SendToProgram("force\n", &first);
11132             if (first.usePing) {
11133               char buf[MSG_SIZ];
11134               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11135               SendToProgram(buf, &first);
11136             }
11137         }
11138     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11139         /* Kill off first chess program */
11140         if (first.isr != NULL)
11141           RemoveInputSource(first.isr);
11142         first.isr = NULL;
11143
11144         if (first.pr != NoProc) {
11145             ExitAnalyzeMode();
11146             DoSleep( appData.delayBeforeQuit );
11147             SendToProgram("quit\n", &first);
11148             DoSleep( appData.delayAfterQuit );
11149             DestroyChildProcess(first.pr, first.useSigterm);
11150             first.reload = TRUE;
11151         }
11152         first.pr = NoProc;
11153     }
11154     if (second.reuse) {
11155         /* Put second chess program into idle state */
11156         if (second.pr != NoProc &&
11157             gameMode == TwoMachinesPlay) {
11158             SendToProgram("force\n", &second);
11159             if (second.usePing) {
11160               char buf[MSG_SIZ];
11161               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11162               SendToProgram(buf, &second);
11163             }
11164         }
11165     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11166         /* Kill off second chess program */
11167         if (second.isr != NULL)
11168           RemoveInputSource(second.isr);
11169         second.isr = NULL;
11170
11171         if (second.pr != NoProc) {
11172             DoSleep( appData.delayBeforeQuit );
11173             SendToProgram("quit\n", &second);
11174             DoSleep( appData.delayAfterQuit );
11175             DestroyChildProcess(second.pr, second.useSigterm);
11176             second.reload = TRUE;
11177         }
11178         second.pr = NoProc;
11179     }
11180
11181     if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11182         char resChar = '=';
11183         switch (result) {
11184         case WhiteWins:
11185           resChar = '+';
11186           if (first.twoMachinesColor[0] == 'w') {
11187             first.matchWins++;
11188           } else {
11189             second.matchWins++;
11190           }
11191           break;
11192         case BlackWins:
11193           resChar = '-';
11194           if (first.twoMachinesColor[0] == 'b') {
11195             first.matchWins++;
11196           } else {
11197             second.matchWins++;
11198           }
11199           break;
11200         case GameUnfinished:
11201           resChar = ' ';
11202         default:
11203           break;
11204         }
11205
11206         if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11207         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11208             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11209             ReserveGame(nextGame, resChar); // sets nextGame
11210             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11211             else ranking = strdup("busy"); //suppress popup when aborted but not finished
11212         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11213
11214         if (nextGame <= appData.matchGames && !abortMatch) {
11215             gameMode = nextGameMode;
11216             matchGame = nextGame; // this will be overruled in tourney mode!
11217             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11218             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11219             endingGame = 0; /* [HGM] crash */
11220             return;
11221         } else {
11222             gameMode = nextGameMode;
11223             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11224                      first.tidy, second.tidy,
11225                      first.matchWins, second.matchWins,
11226                      appData.matchGames - (first.matchWins + second.matchWins));
11227             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11228             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11229             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11230             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11231                 first.twoMachinesColor = "black\n";
11232                 second.twoMachinesColor = "white\n";
11233             } else {
11234                 first.twoMachinesColor = "white\n";
11235                 second.twoMachinesColor = "black\n";
11236             }
11237         }
11238     }
11239     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11240         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11241       ExitAnalyzeMode();
11242     gameMode = nextGameMode;
11243     ModeHighlight();
11244     endingGame = 0;  /* [HGM] crash */
11245     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11246         if(matchMode == TRUE) { // match through command line: exit with or without popup
11247             if(ranking) {
11248                 ToNrEvent(forwardMostMove);
11249                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11250                 else ExitEvent(0);
11251             } else DisplayFatalError(buf, 0, 0);
11252         } else { // match through menu; just stop, with or without popup
11253             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11254             ModeHighlight();
11255             if(ranking){
11256                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11257             } else DisplayNote(buf);
11258       }
11259       if(ranking) free(ranking);
11260     }
11261 }
11262
11263 /* Assumes program was just initialized (initString sent).
11264    Leaves program in force mode. */
11265 void
11266 FeedMovesToProgram (ChessProgramState *cps, int upto)
11267 {
11268     int i;
11269
11270     if (appData.debugMode)
11271       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11272               startedFromSetupPosition ? "position and " : "",
11273               backwardMostMove, upto, cps->which);
11274     if(currentlyInitializedVariant != gameInfo.variant) {
11275       char buf[MSG_SIZ];
11276         // [HGM] variantswitch: make engine aware of new variant
11277         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
11278                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11279         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11280         SendToProgram(buf, cps);
11281         currentlyInitializedVariant = gameInfo.variant;
11282     }
11283     SendToProgram("force\n", cps);
11284     if (startedFromSetupPosition) {
11285         SendBoard(cps, backwardMostMove);
11286     if (appData.debugMode) {
11287         fprintf(debugFP, "feedMoves\n");
11288     }
11289     }
11290     for (i = backwardMostMove; i < upto; i++) {
11291         SendMoveToProgram(i, cps);
11292     }
11293 }
11294
11295
11296 int
11297 ResurrectChessProgram ()
11298 {
11299      /* The chess program may have exited.
11300         If so, restart it and feed it all the moves made so far. */
11301     static int doInit = 0;
11302
11303     if (appData.noChessProgram) return 1;
11304
11305     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11306         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11307         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11308         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11309     } else {
11310         if (first.pr != NoProc) return 1;
11311         StartChessProgram(&first);
11312     }
11313     InitChessProgram(&first, FALSE);
11314     FeedMovesToProgram(&first, currentMove);
11315
11316     if (!first.sendTime) {
11317         /* can't tell gnuchess what its clock should read,
11318            so we bow to its notion. */
11319         ResetClocks();
11320         timeRemaining[0][currentMove] = whiteTimeRemaining;
11321         timeRemaining[1][currentMove] = blackTimeRemaining;
11322     }
11323
11324     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11325                 appData.icsEngineAnalyze) && first.analysisSupport) {
11326       SendToProgram("analyze\n", &first);
11327       first.analyzing = TRUE;
11328     }
11329     return 1;
11330 }
11331
11332 /*
11333  * Button procedures
11334  */
11335 void
11336 Reset (int redraw, int init)
11337 {
11338     int i;
11339
11340     if (appData.debugMode) {
11341         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11342                 redraw, init, gameMode);
11343     }
11344     CleanupTail(); // [HGM] vari: delete any stored variations
11345     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11346     pausing = pauseExamInvalid = FALSE;
11347     startedFromSetupPosition = blackPlaysFirst = FALSE;
11348     firstMove = TRUE;
11349     whiteFlag = blackFlag = FALSE;
11350     userOfferedDraw = FALSE;
11351     hintRequested = bookRequested = FALSE;
11352     first.maybeThinking = FALSE;
11353     second.maybeThinking = FALSE;
11354     first.bookSuspend = FALSE; // [HGM] book
11355     second.bookSuspend = FALSE;
11356     thinkOutput[0] = NULLCHAR;
11357     lastHint[0] = NULLCHAR;
11358     ClearGameInfo(&gameInfo);
11359     gameInfo.variant = StringToVariant(appData.variant);
11360     if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) gameInfo.variant = VariantUnknown;
11361     ics_user_moved = ics_clock_paused = FALSE;
11362     ics_getting_history = H_FALSE;
11363     ics_gamenum = -1;
11364     white_holding[0] = black_holding[0] = NULLCHAR;
11365     ClearProgramStats();
11366     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11367
11368     ResetFrontEnd();
11369     ClearHighlights();
11370     flipView = appData.flipView;
11371     ClearPremoveHighlights();
11372     gotPremove = FALSE;
11373     alarmSounded = FALSE;
11374
11375     GameEnds(EndOfFile, NULL, GE_PLAYER);
11376     if(appData.serverMovesName != NULL) {
11377         /* [HGM] prepare to make moves file for broadcasting */
11378         clock_t t = clock();
11379         if(serverMoves != NULL) fclose(serverMoves);
11380         serverMoves = fopen(appData.serverMovesName, "r");
11381         if(serverMoves != NULL) {
11382             fclose(serverMoves);
11383             /* delay 15 sec before overwriting, so all clients can see end */
11384             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11385         }
11386         serverMoves = fopen(appData.serverMovesName, "w");
11387     }
11388
11389     ExitAnalyzeMode();
11390     gameMode = BeginningOfGame;
11391     ModeHighlight();
11392     if(appData.icsActive) gameInfo.variant = VariantNormal;
11393     currentMove = forwardMostMove = backwardMostMove = 0;
11394     MarkTargetSquares(1);
11395     InitPosition(redraw);
11396     for (i = 0; i < MAX_MOVES; i++) {
11397         if (commentList[i] != NULL) {
11398             free(commentList[i]);
11399             commentList[i] = NULL;
11400         }
11401     }
11402     ResetClocks();
11403     timeRemaining[0][0] = whiteTimeRemaining;
11404     timeRemaining[1][0] = blackTimeRemaining;
11405
11406     if (first.pr == NoProc) {
11407         StartChessProgram(&first);
11408     }
11409     if (init) {
11410             InitChessProgram(&first, startedFromSetupPosition);
11411     }
11412     DisplayTitle("");
11413     DisplayMessage("", "");
11414     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11415     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11416     ClearMap();        // [HGM] exclude: invalidate map
11417 }
11418
11419 void
11420 AutoPlayGameLoop ()
11421 {
11422     for (;;) {
11423         if (!AutoPlayOneMove())
11424           return;
11425         if (matchMode || appData.timeDelay == 0)
11426           continue;
11427         if (appData.timeDelay < 0)
11428           return;
11429         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11430         break;
11431     }
11432 }
11433
11434 void
11435 AnalyzeNextGame()
11436 {
11437     ReloadGame(1); // next game
11438 }
11439
11440 int
11441 AutoPlayOneMove ()
11442 {
11443     int fromX, fromY, toX, toY;
11444
11445     if (appData.debugMode) {
11446       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11447     }
11448
11449     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11450       return FALSE;
11451
11452     if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
11453       pvInfoList[currentMove].depth = programStats.depth;
11454       pvInfoList[currentMove].score = programStats.score;
11455       pvInfoList[currentMove].time  = 0;
11456       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11457       else { // append analysis of final position as comment
11458         char buf[MSG_SIZ];
11459         snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
11460         AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
11461       }
11462       programStats.depth = 0;
11463     }
11464
11465     if (currentMove >= forwardMostMove) {
11466       if(gameMode == AnalyzeFile) {
11467           if(appData.loadGameIndex == -1) {
11468             GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
11469           ScheduleDelayedEvent(AnalyzeNextGame, 10);
11470           } else {
11471           ExitAnalyzeMode(); SendToProgram("force\n", &first);
11472         }
11473       }
11474 //      gameMode = EndOfGame;
11475 //      ModeHighlight();
11476
11477       /* [AS] Clear current move marker at the end of a game */
11478       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11479
11480       return FALSE;
11481     }
11482
11483     toX = moveList[currentMove][2] - AAA;
11484     toY = moveList[currentMove][3] - ONE;
11485
11486     if (moveList[currentMove][1] == '@') {
11487         if (appData.highlightLastMove) {
11488             SetHighlights(-1, -1, toX, toY);
11489         }
11490     } else {
11491         fromX = moveList[currentMove][0] - AAA;
11492         fromY = moveList[currentMove][1] - ONE;
11493
11494         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11495
11496         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11497
11498         if (appData.highlightLastMove) {
11499             SetHighlights(fromX, fromY, toX, toY);
11500         }
11501     }
11502     DisplayMove(currentMove);
11503     SendMoveToProgram(currentMove++, &first);
11504     DisplayBothClocks();
11505     DrawPosition(FALSE, boards[currentMove]);
11506     // [HGM] PV info: always display, routine tests if empty
11507     DisplayComment(currentMove - 1, commentList[currentMove]);
11508     return TRUE;
11509 }
11510
11511
11512 int
11513 LoadGameOneMove (ChessMove readAhead)
11514 {
11515     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11516     char promoChar = NULLCHAR;
11517     ChessMove moveType;
11518     char move[MSG_SIZ];
11519     char *p, *q;
11520
11521     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11522         gameMode != AnalyzeMode && gameMode != Training) {
11523         gameFileFP = NULL;
11524         return FALSE;
11525     }
11526
11527     yyboardindex = forwardMostMove;
11528     if (readAhead != EndOfFile) {
11529       moveType = readAhead;
11530     } else {
11531       if (gameFileFP == NULL)
11532           return FALSE;
11533       moveType = (ChessMove) Myylex();
11534     }
11535
11536     done = FALSE;
11537     switch (moveType) {
11538       case Comment:
11539         if (appData.debugMode)
11540           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11541         p = yy_text;
11542
11543         /* append the comment but don't display it */
11544         AppendComment(currentMove, p, FALSE);
11545         return TRUE;
11546
11547       case WhiteCapturesEnPassant:
11548       case BlackCapturesEnPassant:
11549       case WhitePromotion:
11550       case BlackPromotion:
11551       case WhiteNonPromotion:
11552       case BlackNonPromotion:
11553       case NormalMove:
11554       case WhiteKingSideCastle:
11555       case WhiteQueenSideCastle:
11556       case BlackKingSideCastle:
11557       case BlackQueenSideCastle:
11558       case WhiteKingSideCastleWild:
11559       case WhiteQueenSideCastleWild:
11560       case BlackKingSideCastleWild:
11561       case BlackQueenSideCastleWild:
11562       /* PUSH Fabien */
11563       case WhiteHSideCastleFR:
11564       case WhiteASideCastleFR:
11565       case BlackHSideCastleFR:
11566       case BlackASideCastleFR:
11567       /* POP Fabien */
11568         if (appData.debugMode)
11569           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11570         fromX = currentMoveString[0] - AAA;
11571         fromY = currentMoveString[1] - ONE;
11572         toX = currentMoveString[2] - AAA;
11573         toY = currentMoveString[3] - ONE;
11574         promoChar = currentMoveString[4];
11575         break;
11576
11577       case WhiteDrop:
11578       case BlackDrop:
11579         if (appData.debugMode)
11580           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11581         fromX = moveType == WhiteDrop ?
11582           (int) CharToPiece(ToUpper(currentMoveString[0])) :
11583         (int) CharToPiece(ToLower(currentMoveString[0]));
11584         fromY = DROP_RANK;
11585         toX = currentMoveString[2] - AAA;
11586         toY = currentMoveString[3] - ONE;
11587         break;
11588
11589       case WhiteWins:
11590       case BlackWins:
11591       case GameIsDrawn:
11592       case GameUnfinished:
11593         if (appData.debugMode)
11594           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11595         p = strchr(yy_text, '{');
11596         if (p == NULL) p = strchr(yy_text, '(');
11597         if (p == NULL) {
11598             p = yy_text;
11599             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11600         } else {
11601             q = strchr(p, *p == '{' ? '}' : ')');
11602             if (q != NULL) *q = NULLCHAR;
11603             p++;
11604         }
11605         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11606         GameEnds(moveType, p, GE_FILE);
11607         done = TRUE;
11608         if (cmailMsgLoaded) {
11609             ClearHighlights();
11610             flipView = WhiteOnMove(currentMove);
11611             if (moveType == GameUnfinished) flipView = !flipView;
11612             if (appData.debugMode)
11613               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11614         }
11615         break;
11616
11617       case EndOfFile:
11618         if (appData.debugMode)
11619           fprintf(debugFP, "Parser hit end of file\n");
11620         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11621           case MT_NONE:
11622           case MT_CHECK:
11623             break;
11624           case MT_CHECKMATE:
11625           case MT_STAINMATE:
11626             if (WhiteOnMove(currentMove)) {
11627                 GameEnds(BlackWins, "Black mates", GE_FILE);
11628             } else {
11629                 GameEnds(WhiteWins, "White mates", GE_FILE);
11630             }
11631             break;
11632           case MT_STALEMATE:
11633             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11634             break;
11635         }
11636         done = TRUE;
11637         break;
11638
11639       case MoveNumberOne:
11640         if (lastLoadGameStart == GNUChessGame) {
11641             /* GNUChessGames have numbers, but they aren't move numbers */
11642             if (appData.debugMode)
11643               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11644                       yy_text, (int) moveType);
11645             return LoadGameOneMove(EndOfFile); /* tail recursion */
11646         }
11647         /* else fall thru */
11648
11649       case XBoardGame:
11650       case GNUChessGame:
11651       case PGNTag:
11652         /* Reached start of next game in file */
11653         if (appData.debugMode)
11654           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11655         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11656           case MT_NONE:
11657           case MT_CHECK:
11658             break;
11659           case MT_CHECKMATE:
11660           case MT_STAINMATE:
11661             if (WhiteOnMove(currentMove)) {
11662                 GameEnds(BlackWins, "Black mates", GE_FILE);
11663             } else {
11664                 GameEnds(WhiteWins, "White mates", GE_FILE);
11665             }
11666             break;
11667           case MT_STALEMATE:
11668             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11669             break;
11670         }
11671         done = TRUE;
11672         break;
11673
11674       case PositionDiagram:     /* should not happen; ignore */
11675       case ElapsedTime:         /* ignore */
11676       case NAG:                 /* ignore */
11677         if (appData.debugMode)
11678           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11679                   yy_text, (int) moveType);
11680         return LoadGameOneMove(EndOfFile); /* tail recursion */
11681
11682       case IllegalMove:
11683         if (appData.testLegality) {
11684             if (appData.debugMode)
11685               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11686             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11687                     (forwardMostMove / 2) + 1,
11688                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11689             DisplayError(move, 0);
11690             done = TRUE;
11691         } else {
11692             if (appData.debugMode)
11693               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11694                       yy_text, currentMoveString);
11695             fromX = currentMoveString[0] - AAA;
11696             fromY = currentMoveString[1] - ONE;
11697             toX = currentMoveString[2] - AAA;
11698             toY = currentMoveString[3] - ONE;
11699             promoChar = currentMoveString[4];
11700         }
11701         break;
11702
11703       case AmbiguousMove:
11704         if (appData.debugMode)
11705           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11706         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11707                 (forwardMostMove / 2) + 1,
11708                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11709         DisplayError(move, 0);
11710         done = TRUE;
11711         break;
11712
11713       default:
11714       case ImpossibleMove:
11715         if (appData.debugMode)
11716           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11717         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11718                 (forwardMostMove / 2) + 1,
11719                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11720         DisplayError(move, 0);
11721         done = TRUE;
11722         break;
11723     }
11724
11725     if (done) {
11726         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11727             DrawPosition(FALSE, boards[currentMove]);
11728             DisplayBothClocks();
11729             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11730               DisplayComment(currentMove - 1, commentList[currentMove]);
11731         }
11732         (void) StopLoadGameTimer();
11733         gameFileFP = NULL;
11734         cmailOldMove = forwardMostMove;
11735         return FALSE;
11736     } else {
11737         /* currentMoveString is set as a side-effect of yylex */
11738
11739         thinkOutput[0] = NULLCHAR;
11740         MakeMove(fromX, fromY, toX, toY, promoChar);
11741         currentMove = forwardMostMove;
11742         return TRUE;
11743     }
11744 }
11745
11746 /* Load the nth game from the given file */
11747 int
11748 LoadGameFromFile (char *filename, int n, char *title, int useList)
11749 {
11750     FILE *f;
11751     char buf[MSG_SIZ];
11752
11753     if (strcmp(filename, "-") == 0) {
11754         f = stdin;
11755         title = "stdin";
11756     } else {
11757         f = fopen(filename, "rb");
11758         if (f == NULL) {
11759           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
11760             DisplayError(buf, errno);
11761             return FALSE;
11762         }
11763     }
11764     if (fseek(f, 0, 0) == -1) {
11765         /* f is not seekable; probably a pipe */
11766         useList = FALSE;
11767     }
11768     if (useList && n == 0) {
11769         int error = GameListBuild(f);
11770         if (error) {
11771             DisplayError(_("Cannot build game list"), error);
11772         } else if (!ListEmpty(&gameList) &&
11773                    ((ListGame *) gameList.tailPred)->number > 1) {
11774             GameListPopUp(f, title);
11775             return TRUE;
11776         }
11777         GameListDestroy();
11778         n = 1;
11779     }
11780     if (n == 0) n = 1;
11781     return LoadGame(f, n, title, FALSE);
11782 }
11783
11784
11785 void
11786 MakeRegisteredMove ()
11787 {
11788     int fromX, fromY, toX, toY;
11789     char promoChar;
11790     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11791         switch (cmailMoveType[lastLoadGameNumber - 1]) {
11792           case CMAIL_MOVE:
11793           case CMAIL_DRAW:
11794             if (appData.debugMode)
11795               fprintf(debugFP, "Restoring %s for game %d\n",
11796                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11797
11798             thinkOutput[0] = NULLCHAR;
11799             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11800             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11801             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11802             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11803             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11804             promoChar = cmailMove[lastLoadGameNumber - 1][4];
11805             MakeMove(fromX, fromY, toX, toY, promoChar);
11806             ShowMove(fromX, fromY, toX, toY);
11807
11808             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11809               case MT_NONE:
11810               case MT_CHECK:
11811                 break;
11812
11813               case MT_CHECKMATE:
11814               case MT_STAINMATE:
11815                 if (WhiteOnMove(currentMove)) {
11816                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
11817                 } else {
11818                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
11819                 }
11820                 break;
11821
11822               case MT_STALEMATE:
11823                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11824                 break;
11825             }
11826
11827             break;
11828
11829           case CMAIL_RESIGN:
11830             if (WhiteOnMove(currentMove)) {
11831                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11832             } else {
11833                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11834             }
11835             break;
11836
11837           case CMAIL_ACCEPT:
11838             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11839             break;
11840
11841           default:
11842             break;
11843         }
11844     }
11845
11846     return;
11847 }
11848
11849 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11850 int
11851 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11852 {
11853     int retVal;
11854
11855     if (gameNumber > nCmailGames) {
11856         DisplayError(_("No more games in this message"), 0);
11857         return FALSE;
11858     }
11859     if (f == lastLoadGameFP) {
11860         int offset = gameNumber - lastLoadGameNumber;
11861         if (offset == 0) {
11862             cmailMsg[0] = NULLCHAR;
11863             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11864                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11865                 nCmailMovesRegistered--;
11866             }
11867             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11868             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11869                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11870             }
11871         } else {
11872             if (! RegisterMove()) return FALSE;
11873         }
11874     }
11875
11876     retVal = LoadGame(f, gameNumber, title, useList);
11877
11878     /* Make move registered during previous look at this game, if any */
11879     MakeRegisteredMove();
11880
11881     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11882         commentList[currentMove]
11883           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11884         DisplayComment(currentMove - 1, commentList[currentMove]);
11885     }
11886
11887     return retVal;
11888 }
11889
11890 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11891 int
11892 ReloadGame (int offset)
11893 {
11894     int gameNumber = lastLoadGameNumber + offset;
11895     if (lastLoadGameFP == NULL) {
11896         DisplayError(_("No game has been loaded yet"), 0);
11897         return FALSE;
11898     }
11899     if (gameNumber <= 0) {
11900         DisplayError(_("Can't back up any further"), 0);
11901         return FALSE;
11902     }
11903     if (cmailMsgLoaded) {
11904         return CmailLoadGame(lastLoadGameFP, gameNumber,
11905                              lastLoadGameTitle, lastLoadGameUseList);
11906     } else {
11907         return LoadGame(lastLoadGameFP, gameNumber,
11908                         lastLoadGameTitle, lastLoadGameUseList);
11909     }
11910 }
11911
11912 int keys[EmptySquare+1];
11913
11914 int
11915 PositionMatches (Board b1, Board b2)
11916 {
11917     int r, f, sum=0;
11918     switch(appData.searchMode) {
11919         case 1: return CompareWithRights(b1, b2);
11920         case 2:
11921             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11922                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11923             }
11924             return TRUE;
11925         case 3:
11926             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11927               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11928                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11929             }
11930             return sum==0;
11931         case 4:
11932             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11933                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11934             }
11935             return sum==0;
11936     }
11937     return TRUE;
11938 }
11939
11940 #define Q_PROMO  4
11941 #define Q_EP     3
11942 #define Q_BCASTL 2
11943 #define Q_WCASTL 1
11944
11945 int pieceList[256], quickBoard[256];
11946 ChessSquare pieceType[256] = { EmptySquare };
11947 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11948 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11949 int soughtTotal, turn;
11950 Boolean epOK, flipSearch;
11951
11952 typedef struct {
11953     unsigned char piece, to;
11954 } Move;
11955
11956 #define DSIZE (250000)
11957
11958 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11959 Move *moveDatabase = initialSpace;
11960 unsigned int movePtr, dataSize = DSIZE;
11961
11962 int
11963 MakePieceList (Board board, int *counts)
11964 {
11965     int r, f, n=Q_PROMO, total=0;
11966     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11967     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11968         int sq = f + (r<<4);
11969         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
11970             quickBoard[sq] = ++n;
11971             pieceList[n] = sq;
11972             pieceType[n] = board[r][f];
11973             counts[board[r][f]]++;
11974             if(board[r][f] == WhiteKing) pieceList[1] = n; else
11975             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
11976             total++;
11977         }
11978     }
11979     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
11980     return total;
11981 }
11982
11983 void
11984 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
11985 {
11986     int sq = fromX + (fromY<<4);
11987     int piece = quickBoard[sq];
11988     quickBoard[sq] = 0;
11989     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
11990     if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11991         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
11992         moveDatabase[movePtr++].piece = Q_WCASTL;
11993         quickBoard[sq] = piece;
11994         piece = quickBoard[from]; quickBoard[from] = 0;
11995         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11996     } else
11997     if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11998         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
11999         moveDatabase[movePtr++].piece = Q_BCASTL;
12000         quickBoard[sq] = piece;
12001         piece = quickBoard[from]; quickBoard[from] = 0;
12002         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12003     } else
12004     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12005         quickBoard[(fromY<<4)+toX] = 0;
12006         moveDatabase[movePtr].piece = Q_EP;
12007         moveDatabase[movePtr++].to = (fromY<<4)+toX;
12008         moveDatabase[movePtr].to = sq;
12009     } else
12010     if(promoPiece != pieceType[piece]) {
12011         moveDatabase[movePtr++].piece = Q_PROMO;
12012         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12013     }
12014     moveDatabase[movePtr].piece = piece;
12015     quickBoard[sq] = piece;
12016     movePtr++;
12017 }
12018
12019 int
12020 PackGame (Board board)
12021 {
12022     Move *newSpace = NULL;
12023     moveDatabase[movePtr].piece = 0; // terminate previous game
12024     if(movePtr > dataSize) {
12025         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12026         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12027         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12028         if(newSpace) {
12029             int i;
12030             Move *p = moveDatabase, *q = newSpace;
12031             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
12032             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12033             moveDatabase = newSpace;
12034         } else { // calloc failed, we must be out of memory. Too bad...
12035             dataSize = 0; // prevent calloc events for all subsequent games
12036             return 0;     // and signal this one isn't cached
12037         }
12038     }
12039     movePtr++;
12040     MakePieceList(board, counts);
12041     return movePtr;
12042 }
12043
12044 int
12045 QuickCompare (Board board, int *minCounts, int *maxCounts)
12046 {   // compare according to search mode
12047     int r, f;
12048     switch(appData.searchMode)
12049     {
12050       case 1: // exact position match
12051         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12052         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12053             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12054         }
12055         break;
12056       case 2: // can have extra material on empty squares
12057         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12058             if(board[r][f] == EmptySquare) continue;
12059             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12060         }
12061         break;
12062       case 3: // material with exact Pawn structure
12063         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12064             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12065             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12066         } // fall through to material comparison
12067       case 4: // exact material
12068         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12069         break;
12070       case 6: // material range with given imbalance
12071         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12072         // fall through to range comparison
12073       case 5: // material range
12074         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12075     }
12076     return TRUE;
12077 }
12078
12079 int
12080 QuickScan (Board board, Move *move)
12081 {   // reconstruct game,and compare all positions in it
12082     int cnt=0, stretch=0, total = MakePieceList(board, counts);
12083     do {
12084         int piece = move->piece;
12085         int to = move->to, from = pieceList[piece];
12086         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
12087           if(!piece) return -1;
12088           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
12089             piece = (++move)->piece;
12090             from = pieceList[piece];
12091             counts[pieceType[piece]]--;
12092             pieceType[piece] = (ChessSquare) move->to;
12093             counts[move->to]++;
12094           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
12095             counts[pieceType[quickBoard[to]]]--;
12096             quickBoard[to] = 0; total--;
12097             move++;
12098             continue;
12099           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
12100             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
12101             from  = pieceList[piece]; // so this must be King
12102             quickBoard[from] = 0;
12103             pieceList[piece] = to;
12104             from = pieceList[(++move)->piece]; // for FRC this has to be done here
12105             quickBoard[from] = 0; // rook
12106             quickBoard[to] = piece;
12107             to = move->to; piece = move->piece;
12108             goto aftercastle;
12109           }
12110         }
12111         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12112         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
12113         quickBoard[from] = 0;
12114       aftercastle:
12115         quickBoard[to] = piece;
12116         pieceList[piece] = to;
12117         cnt++; turn ^= 3;
12118         if(QuickCompare(soughtBoard, minSought, maxSought) ||
12119            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12120            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12121                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12122           ) {
12123             static int lastCounts[EmptySquare+1];
12124             int i;
12125             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12126             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12127         } else stretch = 0;
12128         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
12129         move++;
12130     } while(1);
12131 }
12132
12133 void
12134 InitSearch ()
12135 {
12136     int r, f;
12137     flipSearch = FALSE;
12138     CopyBoard(soughtBoard, boards[currentMove]);
12139     soughtTotal = MakePieceList(soughtBoard, maxSought);
12140     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12141     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12142     CopyBoard(reverseBoard, boards[currentMove]);
12143     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12144         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12145         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12146         reverseBoard[r][f] = piece;
12147     }
12148     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12149     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12150     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12151                  || (boards[currentMove][CASTLING][2] == NoRights ||
12152                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12153                  && (boards[currentMove][CASTLING][5] == NoRights ||
12154                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12155       ) {
12156         flipSearch = TRUE;
12157         CopyBoard(flipBoard, soughtBoard);
12158         CopyBoard(rotateBoard, reverseBoard);
12159         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12160             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
12161             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12162         }
12163     }
12164     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12165     if(appData.searchMode >= 5) {
12166         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12167         MakePieceList(soughtBoard, minSought);
12168         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12169     }
12170     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12171         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12172 }
12173
12174 GameInfo dummyInfo;
12175 static int creatingBook;
12176
12177 int
12178 GameContainsPosition (FILE *f, ListGame *lg)
12179 {
12180     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12181     int fromX, fromY, toX, toY;
12182     char promoChar;
12183     static int initDone=FALSE;
12184
12185     // weed out games based on numerical tag comparison
12186     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12187     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12188     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12189     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12190     if(!initDone) {
12191         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12192         initDone = TRUE;
12193     }
12194     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
12195     else CopyBoard(boards[scratch], initialPosition); // default start position
12196     if(lg->moves) {
12197         turn = btm + 1;
12198         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12199         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12200     }
12201     if(btm) plyNr++;
12202     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12203     fseek(f, lg->offset, 0);
12204     yynewfile(f);
12205     while(1) {
12206         yyboardindex = scratch;
12207         quickFlag = plyNr+1;
12208         next = Myylex();
12209         quickFlag = 0;
12210         switch(next) {
12211             case PGNTag:
12212                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12213             default:
12214                 continue;
12215
12216             case XBoardGame:
12217             case GNUChessGame:
12218                 if(plyNr) return -1; // after we have seen moves, this is for new game
12219               continue;
12220
12221             case AmbiguousMove: // we cannot reconstruct the game beyond these two
12222             case ImpossibleMove:
12223             case WhiteWins: // game ends here with these four
12224             case BlackWins:
12225             case GameIsDrawn:
12226             case GameUnfinished:
12227                 return -1;
12228
12229             case IllegalMove:
12230                 if(appData.testLegality) return -1;
12231             case WhiteCapturesEnPassant:
12232             case BlackCapturesEnPassant:
12233             case WhitePromotion:
12234             case BlackPromotion:
12235             case WhiteNonPromotion:
12236             case BlackNonPromotion:
12237             case NormalMove:
12238             case WhiteKingSideCastle:
12239             case WhiteQueenSideCastle:
12240             case BlackKingSideCastle:
12241             case BlackQueenSideCastle:
12242             case WhiteKingSideCastleWild:
12243             case WhiteQueenSideCastleWild:
12244             case BlackKingSideCastleWild:
12245             case BlackQueenSideCastleWild:
12246             case WhiteHSideCastleFR:
12247             case WhiteASideCastleFR:
12248             case BlackHSideCastleFR:
12249             case BlackASideCastleFR:
12250                 fromX = currentMoveString[0] - AAA;
12251                 fromY = currentMoveString[1] - ONE;
12252                 toX = currentMoveString[2] - AAA;
12253                 toY = currentMoveString[3] - ONE;
12254                 promoChar = currentMoveString[4];
12255                 break;
12256             case WhiteDrop:
12257             case BlackDrop:
12258                 fromX = next == WhiteDrop ?
12259                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
12260                   (int) CharToPiece(ToLower(currentMoveString[0]));
12261                 fromY = DROP_RANK;
12262                 toX = currentMoveString[2] - AAA;
12263                 toY = currentMoveString[3] - ONE;
12264                 promoChar = 0;
12265                 break;
12266         }
12267         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12268         plyNr++;
12269         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12270         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12271         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12272         if(appData.findMirror) {
12273             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12274             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12275         }
12276     }
12277 }
12278
12279 /* Load the nth game from open file f */
12280 int
12281 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12282 {
12283     ChessMove cm;
12284     char buf[MSG_SIZ];
12285     int gn = gameNumber;
12286     ListGame *lg = NULL;
12287     int numPGNTags = 0;
12288     int err, pos = -1;
12289     GameMode oldGameMode;
12290     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12291
12292     if (appData.debugMode)
12293         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12294
12295     if (gameMode == Training )
12296         SetTrainingModeOff();
12297
12298     oldGameMode = gameMode;
12299     if (gameMode != BeginningOfGame) {
12300       Reset(FALSE, TRUE);
12301     }
12302
12303     gameFileFP = f;
12304     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12305         fclose(lastLoadGameFP);
12306     }
12307
12308     if (useList) {
12309         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12310
12311         if (lg) {
12312             fseek(f, lg->offset, 0);
12313             GameListHighlight(gameNumber);
12314             pos = lg->position;
12315             gn = 1;
12316         }
12317         else {
12318             if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
12319               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12320             else
12321             DisplayError(_("Game number out of range"), 0);
12322             return FALSE;
12323         }
12324     } else {
12325         GameListDestroy();
12326         if (fseek(f, 0, 0) == -1) {
12327             if (f == lastLoadGameFP ?
12328                 gameNumber == lastLoadGameNumber + 1 :
12329                 gameNumber == 1) {
12330                 gn = 1;
12331             } else {
12332                 DisplayError(_("Can't seek on game file"), 0);
12333                 return FALSE;
12334             }
12335         }
12336     }
12337     lastLoadGameFP = f;
12338     lastLoadGameNumber = gameNumber;
12339     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12340     lastLoadGameUseList = useList;
12341
12342     yynewfile(f);
12343
12344     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12345       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12346                 lg->gameInfo.black);
12347             DisplayTitle(buf);
12348     } else if (*title != NULLCHAR) {
12349         if (gameNumber > 1) {
12350           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12351             DisplayTitle(buf);
12352         } else {
12353             DisplayTitle(title);
12354         }
12355     }
12356
12357     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12358         gameMode = PlayFromGameFile;
12359         ModeHighlight();
12360     }
12361
12362     currentMove = forwardMostMove = backwardMostMove = 0;
12363     CopyBoard(boards[0], initialPosition);
12364     StopClocks();
12365
12366     /*
12367      * Skip the first gn-1 games in the file.
12368      * Also skip over anything that precedes an identifiable
12369      * start of game marker, to avoid being confused by
12370      * garbage at the start of the file.  Currently
12371      * recognized start of game markers are the move number "1",
12372      * the pattern "gnuchess .* game", the pattern
12373      * "^[#;%] [^ ]* game file", and a PGN tag block.
12374      * A game that starts with one of the latter two patterns
12375      * will also have a move number 1, possibly
12376      * following a position diagram.
12377      * 5-4-02: Let's try being more lenient and allowing a game to
12378      * start with an unnumbered move.  Does that break anything?
12379      */
12380     cm = lastLoadGameStart = EndOfFile;
12381     while (gn > 0) {
12382         yyboardindex = forwardMostMove;
12383         cm = (ChessMove) Myylex();
12384         switch (cm) {
12385           case EndOfFile:
12386             if (cmailMsgLoaded) {
12387                 nCmailGames = CMAIL_MAX_GAMES - gn;
12388             } else {
12389                 Reset(TRUE, TRUE);
12390                 DisplayError(_("Game not found in file"), 0);
12391             }
12392             return FALSE;
12393
12394           case GNUChessGame:
12395           case XBoardGame:
12396             gn--;
12397             lastLoadGameStart = cm;
12398             break;
12399
12400           case MoveNumberOne:
12401             switch (lastLoadGameStart) {
12402               case GNUChessGame:
12403               case XBoardGame:
12404               case PGNTag:
12405                 break;
12406               case MoveNumberOne:
12407               case EndOfFile:
12408                 gn--;           /* count this game */
12409                 lastLoadGameStart = cm;
12410                 break;
12411               default:
12412                 /* impossible */
12413                 break;
12414             }
12415             break;
12416
12417           case PGNTag:
12418             switch (lastLoadGameStart) {
12419               case GNUChessGame:
12420               case PGNTag:
12421               case MoveNumberOne:
12422               case EndOfFile:
12423                 gn--;           /* count this game */
12424                 lastLoadGameStart = cm;
12425                 break;
12426               case XBoardGame:
12427                 lastLoadGameStart = cm; /* game counted already */
12428                 break;
12429               default:
12430                 /* impossible */
12431                 break;
12432             }
12433             if (gn > 0) {
12434                 do {
12435                     yyboardindex = forwardMostMove;
12436                     cm = (ChessMove) Myylex();
12437                 } while (cm == PGNTag || cm == Comment);
12438             }
12439             break;
12440
12441           case WhiteWins:
12442           case BlackWins:
12443           case GameIsDrawn:
12444             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
12445                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
12446                     != CMAIL_OLD_RESULT) {
12447                     nCmailResults ++ ;
12448                     cmailResult[  CMAIL_MAX_GAMES
12449                                 - gn - 1] = CMAIL_OLD_RESULT;
12450                 }
12451             }
12452             break;
12453
12454           case NormalMove:
12455             /* Only a NormalMove can be at the start of a game
12456              * without a position diagram. */
12457             if (lastLoadGameStart == EndOfFile ) {
12458               gn--;
12459               lastLoadGameStart = MoveNumberOne;
12460             }
12461             break;
12462
12463           default:
12464             break;
12465         }
12466     }
12467
12468     if (appData.debugMode)
12469       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
12470
12471     if (cm == XBoardGame) {
12472         /* Skip any header junk before position diagram and/or move 1 */
12473         for (;;) {
12474             yyboardindex = forwardMostMove;
12475             cm = (ChessMove) Myylex();
12476
12477             if (cm == EndOfFile ||
12478                 cm == GNUChessGame || cm == XBoardGame) {
12479                 /* Empty game; pretend end-of-file and handle later */
12480                 cm = EndOfFile;
12481                 break;
12482             }
12483
12484             if (cm == MoveNumberOne || cm == PositionDiagram ||
12485                 cm == PGNTag || cm == Comment)
12486               break;
12487         }
12488     } else if (cm == GNUChessGame) {
12489         if (gameInfo.event != NULL) {
12490             free(gameInfo.event);
12491         }
12492         gameInfo.event = StrSave(yy_text);
12493     }
12494
12495     startedFromSetupPosition = FALSE;
12496     while (cm == PGNTag) {
12497         if (appData.debugMode)
12498           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
12499         err = ParsePGNTag(yy_text, &gameInfo);
12500         if (!err) numPGNTags++;
12501
12502         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
12503         if(gameInfo.variant != oldVariant) {
12504             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
12505             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
12506             InitPosition(TRUE);
12507             oldVariant = gameInfo.variant;
12508             if (appData.debugMode)
12509               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
12510         }
12511
12512
12513         if (gameInfo.fen != NULL) {
12514           Board initial_position;
12515           startedFromSetupPosition = TRUE;
12516           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
12517             Reset(TRUE, TRUE);
12518             DisplayError(_("Bad FEN position in file"), 0);
12519             return FALSE;
12520           }
12521           CopyBoard(boards[0], initial_position);
12522           if (blackPlaysFirst) {
12523             currentMove = forwardMostMove = backwardMostMove = 1;
12524             CopyBoard(boards[1], initial_position);
12525             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12526             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12527             timeRemaining[0][1] = whiteTimeRemaining;
12528             timeRemaining[1][1] = blackTimeRemaining;
12529             if (commentList[0] != NULL) {
12530               commentList[1] = commentList[0];
12531               commentList[0] = NULL;
12532             }
12533           } else {
12534             currentMove = forwardMostMove = backwardMostMove = 0;
12535           }
12536           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12537           {   int i;
12538               initialRulePlies = FENrulePlies;
12539               for( i=0; i< nrCastlingRights; i++ )
12540                   initialRights[i] = initial_position[CASTLING][i];
12541           }
12542           yyboardindex = forwardMostMove;
12543           free(gameInfo.fen);
12544           gameInfo.fen = NULL;
12545         }
12546
12547         yyboardindex = forwardMostMove;
12548         cm = (ChessMove) Myylex();
12549
12550         /* Handle comments interspersed among the tags */
12551         while (cm == Comment) {
12552             char *p;
12553             if (appData.debugMode)
12554               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12555             p = yy_text;
12556             AppendComment(currentMove, p, FALSE);
12557             yyboardindex = forwardMostMove;
12558             cm = (ChessMove) Myylex();
12559         }
12560     }
12561
12562     /* don't rely on existence of Event tag since if game was
12563      * pasted from clipboard the Event tag may not exist
12564      */
12565     if (numPGNTags > 0){
12566         char *tags;
12567         if (gameInfo.variant == VariantNormal) {
12568           VariantClass v = StringToVariant(gameInfo.event);
12569           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12570           if(v < VariantShogi) gameInfo.variant = v;
12571         }
12572         if (!matchMode) {
12573           if( appData.autoDisplayTags ) {
12574             tags = PGNTags(&gameInfo);
12575             TagsPopUp(tags, CmailMsg());
12576             free(tags);
12577           }
12578         }
12579     } else {
12580         /* Make something up, but don't display it now */
12581         SetGameInfo();
12582         TagsPopDown();
12583     }
12584
12585     if (cm == PositionDiagram) {
12586         int i, j;
12587         char *p;
12588         Board initial_position;
12589
12590         if (appData.debugMode)
12591           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12592
12593         if (!startedFromSetupPosition) {
12594             p = yy_text;
12595             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12596               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12597                 switch (*p) {
12598                   case '{':
12599                   case '[':
12600                   case '-':
12601                   case ' ':
12602                   case '\t':
12603                   case '\n':
12604                   case '\r':
12605                     break;
12606                   default:
12607                     initial_position[i][j++] = CharToPiece(*p);
12608                     break;
12609                 }
12610             while (*p == ' ' || *p == '\t' ||
12611                    *p == '\n' || *p == '\r') p++;
12612
12613             if (strncmp(p, "black", strlen("black"))==0)
12614               blackPlaysFirst = TRUE;
12615             else
12616               blackPlaysFirst = FALSE;
12617             startedFromSetupPosition = TRUE;
12618
12619             CopyBoard(boards[0], initial_position);
12620             if (blackPlaysFirst) {
12621                 currentMove = forwardMostMove = backwardMostMove = 1;
12622                 CopyBoard(boards[1], initial_position);
12623                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12624                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12625                 timeRemaining[0][1] = whiteTimeRemaining;
12626                 timeRemaining[1][1] = blackTimeRemaining;
12627                 if (commentList[0] != NULL) {
12628                     commentList[1] = commentList[0];
12629                     commentList[0] = NULL;
12630                 }
12631             } else {
12632                 currentMove = forwardMostMove = backwardMostMove = 0;
12633             }
12634         }
12635         yyboardindex = forwardMostMove;
12636         cm = (ChessMove) Myylex();
12637     }
12638
12639   if(!creatingBook) {
12640     if (first.pr == NoProc) {
12641         StartChessProgram(&first);
12642     }
12643     InitChessProgram(&first, FALSE);
12644     SendToProgram("force\n", &first);
12645     if (startedFromSetupPosition) {
12646         SendBoard(&first, forwardMostMove);
12647     if (appData.debugMode) {
12648         fprintf(debugFP, "Load Game\n");
12649     }
12650         DisplayBothClocks();
12651     }
12652   }
12653
12654     /* [HGM] server: flag to write setup moves in broadcast file as one */
12655     loadFlag = appData.suppressLoadMoves;
12656
12657     while (cm == Comment) {
12658         char *p;
12659         if (appData.debugMode)
12660           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12661         p = yy_text;
12662         AppendComment(currentMove, p, FALSE);
12663         yyboardindex = forwardMostMove;
12664         cm = (ChessMove) Myylex();
12665     }
12666
12667     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12668         cm == WhiteWins || cm == BlackWins ||
12669         cm == GameIsDrawn || cm == GameUnfinished) {
12670         DisplayMessage("", _("No moves in game"));
12671         if (cmailMsgLoaded) {
12672             if (appData.debugMode)
12673               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12674             ClearHighlights();
12675             flipView = FALSE;
12676         }
12677         DrawPosition(FALSE, boards[currentMove]);
12678         DisplayBothClocks();
12679         gameMode = EditGame;
12680         ModeHighlight();
12681         gameFileFP = NULL;
12682         cmailOldMove = 0;
12683         return TRUE;
12684     }
12685
12686     // [HGM] PV info: routine tests if comment empty
12687     if (!matchMode && (pausing || appData.timeDelay != 0)) {
12688         DisplayComment(currentMove - 1, commentList[currentMove]);
12689     }
12690     if (!matchMode && appData.timeDelay != 0)
12691       DrawPosition(FALSE, boards[currentMove]);
12692
12693     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12694       programStats.ok_to_send = 1;
12695     }
12696
12697     /* if the first token after the PGN tags is a move
12698      * and not move number 1, retrieve it from the parser
12699      */
12700     if (cm != MoveNumberOne)
12701         LoadGameOneMove(cm);
12702
12703     /* load the remaining moves from the file */
12704     while (LoadGameOneMove(EndOfFile)) {
12705       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12706       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12707     }
12708
12709     /* rewind to the start of the game */
12710     currentMove = backwardMostMove;
12711
12712     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12713
12714     if (oldGameMode == AnalyzeFile) {
12715       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
12716       AnalyzeFileEvent();
12717     } else
12718     if (oldGameMode == AnalyzeMode) {
12719       AnalyzeFileEvent();
12720     }
12721
12722     if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
12723         long int w, b; // [HGM] adjourn: restore saved clock times
12724         char *p = strstr(gameInfo.resultDetails, "(Clocks:");
12725         if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
12726             timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
12727             timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
12728         }
12729     }
12730
12731     if(creatingBook) return TRUE;
12732     if (!matchMode && pos > 0) {
12733         ToNrEvent(pos); // [HGM] no autoplay if selected on position
12734     } else
12735     if (matchMode || appData.timeDelay == 0) {
12736       ToEndEvent();
12737     } else if (appData.timeDelay > 0) {
12738       AutoPlayGameLoop();
12739     }
12740
12741     if (appData.debugMode)
12742         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
12743
12744     loadFlag = 0; /* [HGM] true game starts */
12745     return TRUE;
12746 }
12747
12748 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
12749 int
12750 ReloadPosition (int offset)
12751 {
12752     int positionNumber = lastLoadPositionNumber + offset;
12753     if (lastLoadPositionFP == NULL) {
12754         DisplayError(_("No position has been loaded yet"), 0);
12755         return FALSE;
12756     }
12757     if (positionNumber <= 0) {
12758         DisplayError(_("Can't back up any further"), 0);
12759         return FALSE;
12760     }
12761     return LoadPosition(lastLoadPositionFP, positionNumber,
12762                         lastLoadPositionTitle);
12763 }
12764
12765 /* Load the nth position from the given file */
12766 int
12767 LoadPositionFromFile (char *filename, int n, char *title)
12768 {
12769     FILE *f;
12770     char buf[MSG_SIZ];
12771
12772     if (strcmp(filename, "-") == 0) {
12773         return LoadPosition(stdin, n, "stdin");
12774     } else {
12775         f = fopen(filename, "rb");
12776         if (f == NULL) {
12777             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12778             DisplayError(buf, errno);
12779             return FALSE;
12780         } else {
12781             return LoadPosition(f, n, title);
12782         }
12783     }
12784 }
12785
12786 /* Load the nth position from the given open file, and close it */
12787 int
12788 LoadPosition (FILE *f, int positionNumber, char *title)
12789 {
12790     char *p, line[MSG_SIZ];
12791     Board initial_position;
12792     int i, j, fenMode, pn;
12793
12794     if (gameMode == Training )
12795         SetTrainingModeOff();
12796
12797     if (gameMode != BeginningOfGame) {
12798         Reset(FALSE, TRUE);
12799     }
12800     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12801         fclose(lastLoadPositionFP);
12802     }
12803     if (positionNumber == 0) positionNumber = 1;
12804     lastLoadPositionFP = f;
12805     lastLoadPositionNumber = positionNumber;
12806     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12807     if (first.pr == NoProc && !appData.noChessProgram) {
12808       StartChessProgram(&first);
12809       InitChessProgram(&first, FALSE);
12810     }
12811     pn = positionNumber;
12812     if (positionNumber < 0) {
12813         /* Negative position number means to seek to that byte offset */
12814         if (fseek(f, -positionNumber, 0) == -1) {
12815             DisplayError(_("Can't seek on position file"), 0);
12816             return FALSE;
12817         };
12818         pn = 1;
12819     } else {
12820         if (fseek(f, 0, 0) == -1) {
12821             if (f == lastLoadPositionFP ?
12822                 positionNumber == lastLoadPositionNumber + 1 :
12823                 positionNumber == 1) {
12824                 pn = 1;
12825             } else {
12826                 DisplayError(_("Can't seek on position file"), 0);
12827                 return FALSE;
12828             }
12829         }
12830     }
12831     /* See if this file is FEN or old-style xboard */
12832     if (fgets(line, MSG_SIZ, f) == NULL) {
12833         DisplayError(_("Position not found in file"), 0);
12834         return FALSE;
12835     }
12836     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12837     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12838
12839     if (pn >= 2) {
12840         if (fenMode || line[0] == '#') pn--;
12841         while (pn > 0) {
12842             /* skip positions before number pn */
12843             if (fgets(line, MSG_SIZ, f) == NULL) {
12844                 Reset(TRUE, TRUE);
12845                 DisplayError(_("Position not found in file"), 0);
12846                 return FALSE;
12847             }
12848             if (fenMode || line[0] == '#') pn--;
12849         }
12850     }
12851
12852     if (fenMode) {
12853         if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
12854             DisplayError(_("Bad FEN position in file"), 0);
12855             return FALSE;
12856         }
12857     } else {
12858         (void) fgets(line, MSG_SIZ, f);
12859         (void) fgets(line, MSG_SIZ, f);
12860
12861         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12862             (void) fgets(line, MSG_SIZ, f);
12863             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12864                 if (*p == ' ')
12865                   continue;
12866                 initial_position[i][j++] = CharToPiece(*p);
12867             }
12868         }
12869
12870         blackPlaysFirst = FALSE;
12871         if (!feof(f)) {
12872             (void) fgets(line, MSG_SIZ, f);
12873             if (strncmp(line, "black", strlen("black"))==0)
12874               blackPlaysFirst = TRUE;
12875         }
12876     }
12877     startedFromSetupPosition = TRUE;
12878
12879     CopyBoard(boards[0], initial_position);
12880     if (blackPlaysFirst) {
12881         currentMove = forwardMostMove = backwardMostMove = 1;
12882         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12883         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12884         CopyBoard(boards[1], initial_position);
12885         DisplayMessage("", _("Black to play"));
12886     } else {
12887         currentMove = forwardMostMove = backwardMostMove = 0;
12888         DisplayMessage("", _("White to play"));
12889     }
12890     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12891     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12892         SendToProgram("force\n", &first);
12893         SendBoard(&first, forwardMostMove);
12894     }
12895     if (appData.debugMode) {
12896 int i, j;
12897   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12898   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12899         fprintf(debugFP, "Load Position\n");
12900     }
12901
12902     if (positionNumber > 1) {
12903       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12904         DisplayTitle(line);
12905     } else {
12906         DisplayTitle(title);
12907     }
12908     gameMode = EditGame;
12909     ModeHighlight();
12910     ResetClocks();
12911     timeRemaining[0][1] = whiteTimeRemaining;
12912     timeRemaining[1][1] = blackTimeRemaining;
12913     DrawPosition(FALSE, boards[currentMove]);
12914
12915     return TRUE;
12916 }
12917
12918
12919 void
12920 CopyPlayerNameIntoFileName (char **dest, char *src)
12921 {
12922     while (*src != NULLCHAR && *src != ',') {
12923         if (*src == ' ') {
12924             *(*dest)++ = '_';
12925             src++;
12926         } else {
12927             *(*dest)++ = *src++;
12928         }
12929     }
12930 }
12931
12932 char *
12933 DefaultFileName (char *ext)
12934 {
12935     static char def[MSG_SIZ];
12936     char *p;
12937
12938     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12939         p = def;
12940         CopyPlayerNameIntoFileName(&p, gameInfo.white);
12941         *p++ = '-';
12942         CopyPlayerNameIntoFileName(&p, gameInfo.black);
12943         *p++ = '.';
12944         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12945     } else {
12946         def[0] = NULLCHAR;
12947     }
12948     return def;
12949 }
12950
12951 /* Save the current game to the given file */
12952 int
12953 SaveGameToFile (char *filename, int append)
12954 {
12955     FILE *f;
12956     char buf[MSG_SIZ];
12957     int result, i, t,tot=0;
12958
12959     if (strcmp(filename, "-") == 0) {
12960         return SaveGame(stdout, 0, NULL);
12961     } else {
12962         for(i=0; i<10; i++) { // upto 10 tries
12963              f = fopen(filename, append ? "a" : "w");
12964              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12965              if(f || errno != 13) break;
12966              DoSleep(t = 5 + random()%11); // wait 5-15 msec
12967              tot += t;
12968         }
12969         if (f == NULL) {
12970             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12971             DisplayError(buf, errno);
12972             return FALSE;
12973         } else {
12974             safeStrCpy(buf, lastMsg, MSG_SIZ);
12975             DisplayMessage(_("Waiting for access to save file"), "");
12976             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
12977             DisplayMessage(_("Saving game"), "");
12978             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
12979             result = SaveGame(f, 0, NULL);
12980             DisplayMessage(buf, "");
12981             return result;
12982         }
12983     }
12984 }
12985
12986 char *
12987 SavePart (char *str)
12988 {
12989     static char buf[MSG_SIZ];
12990     char *p;
12991
12992     p = strchr(str, ' ');
12993     if (p == NULL) return str;
12994     strncpy(buf, str, p - str);
12995     buf[p - str] = NULLCHAR;
12996     return buf;
12997 }
12998
12999 #define PGN_MAX_LINE 75
13000
13001 #define PGN_SIDE_WHITE  0
13002 #define PGN_SIDE_BLACK  1
13003
13004 static int
13005 FindFirstMoveOutOfBook (int side)
13006 {
13007     int result = -1;
13008
13009     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13010         int index = backwardMostMove;
13011         int has_book_hit = 0;
13012
13013         if( (index % 2) != side ) {
13014             index++;
13015         }
13016
13017         while( index < forwardMostMove ) {
13018             /* Check to see if engine is in book */
13019             int depth = pvInfoList[index].depth;
13020             int score = pvInfoList[index].score;
13021             int in_book = 0;
13022
13023             if( depth <= 2 ) {
13024                 in_book = 1;
13025             }
13026             else if( score == 0 && depth == 63 ) {
13027                 in_book = 1; /* Zappa */
13028             }
13029             else if( score == 2 && depth == 99 ) {
13030                 in_book = 1; /* Abrok */
13031             }
13032
13033             has_book_hit += in_book;
13034
13035             if( ! in_book ) {
13036                 result = index;
13037
13038                 break;
13039             }
13040
13041             index += 2;
13042         }
13043     }
13044
13045     return result;
13046 }
13047
13048 void
13049 GetOutOfBookInfo (char * buf)
13050 {
13051     int oob[2];
13052     int i;
13053     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13054
13055     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13056     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13057
13058     *buf = '\0';
13059
13060     if( oob[0] >= 0 || oob[1] >= 0 ) {
13061         for( i=0; i<2; i++ ) {
13062             int idx = oob[i];
13063
13064             if( idx >= 0 ) {
13065                 if( i > 0 && oob[0] >= 0 ) {
13066                     strcat( buf, "   " );
13067                 }
13068
13069                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
13070                 sprintf( buf+strlen(buf), "%s%.2f",
13071                     pvInfoList[idx].score >= 0 ? "+" : "",
13072                     pvInfoList[idx].score / 100.0 );
13073             }
13074         }
13075     }
13076 }
13077
13078 /* Save game in PGN style and close the file */
13079 int
13080 SaveGamePGN (FILE *f)
13081 {
13082     int i, offset, linelen, newblock;
13083 //    char *movetext;
13084     char numtext[32];
13085     int movelen, numlen, blank;
13086     char move_buffer[100]; /* [AS] Buffer for move+PV info */
13087
13088     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13089
13090     PrintPGNTags(f, &gameInfo);
13091
13092     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
13093
13094     if (backwardMostMove > 0 || startedFromSetupPosition) {
13095         char *fen = PositionToFEN(backwardMostMove, NULL, 1);
13096         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
13097         fprintf(f, "\n{--------------\n");
13098         PrintPosition(f, backwardMostMove);
13099         fprintf(f, "--------------}\n");
13100         free(fen);
13101     }
13102     else {
13103         /* [AS] Out of book annotation */
13104         if( appData.saveOutOfBookInfo ) {
13105             char buf[64];
13106
13107             GetOutOfBookInfo( buf );
13108
13109             if( buf[0] != '\0' ) {
13110                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
13111             }
13112         }
13113
13114         fprintf(f, "\n");
13115     }
13116
13117     i = backwardMostMove;
13118     linelen = 0;
13119     newblock = TRUE;
13120
13121     while (i < forwardMostMove) {
13122         /* Print comments preceding this move */
13123         if (commentList[i] != NULL) {
13124             if (linelen > 0) fprintf(f, "\n");
13125             fprintf(f, "%s", commentList[i]);
13126             linelen = 0;
13127             newblock = TRUE;
13128         }
13129
13130         /* Format move number */
13131         if ((i % 2) == 0)
13132           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13133         else
13134           if (newblock)
13135             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13136           else
13137             numtext[0] = NULLCHAR;
13138
13139         numlen = strlen(numtext);
13140         newblock = FALSE;
13141
13142         /* Print move number */
13143         blank = linelen > 0 && numlen > 0;
13144         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13145             fprintf(f, "\n");
13146             linelen = 0;
13147             blank = 0;
13148         }
13149         if (blank) {
13150             fprintf(f, " ");
13151             linelen++;
13152         }
13153         fprintf(f, "%s", numtext);
13154         linelen += numlen;
13155
13156         /* Get move */
13157         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13158         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13159
13160         /* Print move */
13161         blank = linelen > 0 && movelen > 0;
13162         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13163             fprintf(f, "\n");
13164             linelen = 0;
13165             blank = 0;
13166         }
13167         if (blank) {
13168             fprintf(f, " ");
13169             linelen++;
13170         }
13171         fprintf(f, "%s", move_buffer);
13172         linelen += movelen;
13173
13174         /* [AS] Add PV info if present */
13175         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13176             /* [HGM] add time */
13177             char buf[MSG_SIZ]; int seconds;
13178
13179             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13180
13181             if( seconds <= 0)
13182               buf[0] = 0;
13183             else
13184               if( seconds < 30 )
13185                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13186               else
13187                 {
13188                   seconds = (seconds + 4)/10; // round to full seconds
13189                   if( seconds < 60 )
13190                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
13191                   else
13192                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
13193                 }
13194
13195             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
13196                       pvInfoList[i].score >= 0 ? "+" : "",
13197                       pvInfoList[i].score / 100.0,
13198                       pvInfoList[i].depth,
13199                       buf );
13200
13201             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
13202
13203             /* Print score/depth */
13204             blank = linelen > 0 && movelen > 0;
13205             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13206                 fprintf(f, "\n");
13207                 linelen = 0;
13208                 blank = 0;
13209             }
13210             if (blank) {
13211                 fprintf(f, " ");
13212                 linelen++;
13213             }
13214             fprintf(f, "%s", move_buffer);
13215             linelen += movelen;
13216         }
13217
13218         i++;
13219     }
13220
13221     /* Start a new line */
13222     if (linelen > 0) fprintf(f, "\n");
13223
13224     /* Print comments after last move */
13225     if (commentList[i] != NULL) {
13226         fprintf(f, "%s\n", commentList[i]);
13227     }
13228
13229     /* Print result */
13230     if (gameInfo.resultDetails != NULL &&
13231         gameInfo.resultDetails[0] != NULLCHAR) {
13232         char buf[MSG_SIZ], *p = gameInfo.resultDetails;
13233         if(gameInfo.result == GameUnfinished && appData.clockMode &&
13234            (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
13235             snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
13236         fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
13237     } else {
13238         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13239     }
13240
13241     fclose(f);
13242     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13243     return TRUE;
13244 }
13245
13246 /* Save game in old style and close the file */
13247 int
13248 SaveGameOldStyle (FILE *f)
13249 {
13250     int i, offset;
13251     time_t tm;
13252
13253     tm = time((time_t *) NULL);
13254
13255     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
13256     PrintOpponents(f);
13257
13258     if (backwardMostMove > 0 || startedFromSetupPosition) {
13259         fprintf(f, "\n[--------------\n");
13260         PrintPosition(f, backwardMostMove);
13261         fprintf(f, "--------------]\n");
13262     } else {
13263         fprintf(f, "\n");
13264     }
13265
13266     i = backwardMostMove;
13267     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13268
13269     while (i < forwardMostMove) {
13270         if (commentList[i] != NULL) {
13271             fprintf(f, "[%s]\n", commentList[i]);
13272         }
13273
13274         if ((i % 2) == 1) {
13275             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
13276             i++;
13277         } else {
13278             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
13279             i++;
13280             if (commentList[i] != NULL) {
13281                 fprintf(f, "\n");
13282                 continue;
13283             }
13284             if (i >= forwardMostMove) {
13285                 fprintf(f, "\n");
13286                 break;
13287             }
13288             fprintf(f, "%s\n", parseList[i]);
13289             i++;
13290         }
13291     }
13292
13293     if (commentList[i] != NULL) {
13294         fprintf(f, "[%s]\n", commentList[i]);
13295     }
13296
13297     /* This isn't really the old style, but it's close enough */
13298     if (gameInfo.resultDetails != NULL &&
13299         gameInfo.resultDetails[0] != NULLCHAR) {
13300         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13301                 gameInfo.resultDetails);
13302     } else {
13303         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13304     }
13305
13306     fclose(f);
13307     return TRUE;
13308 }
13309
13310 /* Save the current game to open file f and close the file */
13311 int
13312 SaveGame (FILE *f, int dummy, char *dummy2)
13313 {
13314     if (gameMode == EditPosition) EditPositionDone(TRUE);
13315     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13316     if (appData.oldSaveStyle)
13317       return SaveGameOldStyle(f);
13318     else
13319       return SaveGamePGN(f);
13320 }
13321
13322 /* Save the current position to the given file */
13323 int
13324 SavePositionToFile (char *filename)
13325 {
13326     FILE *f;
13327     char buf[MSG_SIZ];
13328
13329     if (strcmp(filename, "-") == 0) {
13330         return SavePosition(stdout, 0, NULL);
13331     } else {
13332         f = fopen(filename, "a");
13333         if (f == NULL) {
13334             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13335             DisplayError(buf, errno);
13336             return FALSE;
13337         } else {
13338             safeStrCpy(buf, lastMsg, MSG_SIZ);
13339             DisplayMessage(_("Waiting for access to save file"), "");
13340             flock(fileno(f), LOCK_EX); // [HGM] lock
13341             DisplayMessage(_("Saving position"), "");
13342             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
13343             SavePosition(f, 0, NULL);
13344             DisplayMessage(buf, "");
13345             return TRUE;
13346         }
13347     }
13348 }
13349
13350 /* Save the current position to the given open file and close the file */
13351 int
13352 SavePosition (FILE *f, int dummy, char *dummy2)
13353 {
13354     time_t tm;
13355     char *fen;
13356
13357     if (gameMode == EditPosition) EditPositionDone(TRUE);
13358     if (appData.oldSaveStyle) {
13359         tm = time((time_t *) NULL);
13360
13361         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
13362         PrintOpponents(f);
13363         fprintf(f, "[--------------\n");
13364         PrintPosition(f, currentMove);
13365         fprintf(f, "--------------]\n");
13366     } else {
13367         fen = PositionToFEN(currentMove, NULL, 1);
13368         fprintf(f, "%s\n", fen);
13369         free(fen);
13370     }
13371     fclose(f);
13372     return TRUE;
13373 }
13374
13375 void
13376 ReloadCmailMsgEvent (int unregister)
13377 {
13378 #if !WIN32
13379     static char *inFilename = NULL;
13380     static char *outFilename;
13381     int i;
13382     struct stat inbuf, outbuf;
13383     int status;
13384
13385     /* Any registered moves are unregistered if unregister is set, */
13386     /* i.e. invoked by the signal handler */
13387     if (unregister) {
13388         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13389             cmailMoveRegistered[i] = FALSE;
13390             if (cmailCommentList[i] != NULL) {
13391                 free(cmailCommentList[i]);
13392                 cmailCommentList[i] = NULL;
13393             }
13394         }
13395         nCmailMovesRegistered = 0;
13396     }
13397
13398     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13399         cmailResult[i] = CMAIL_NOT_RESULT;
13400     }
13401     nCmailResults = 0;
13402
13403     if (inFilename == NULL) {
13404         /* Because the filenames are static they only get malloced once  */
13405         /* and they never get freed                                      */
13406         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
13407         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
13408
13409         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
13410         sprintf(outFilename, "%s.out", appData.cmailGameName);
13411     }
13412
13413     status = stat(outFilename, &outbuf);
13414     if (status < 0) {
13415         cmailMailedMove = FALSE;
13416     } else {
13417         status = stat(inFilename, &inbuf);
13418         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
13419     }
13420
13421     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
13422        counts the games, notes how each one terminated, etc.
13423
13424        It would be nice to remove this kludge and instead gather all
13425        the information while building the game list.  (And to keep it
13426        in the game list nodes instead of having a bunch of fixed-size
13427        parallel arrays.)  Note this will require getting each game's
13428        termination from the PGN tags, as the game list builder does
13429        not process the game moves.  --mann
13430        */
13431     cmailMsgLoaded = TRUE;
13432     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
13433
13434     /* Load first game in the file or popup game menu */
13435     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
13436
13437 #endif /* !WIN32 */
13438     return;
13439 }
13440
13441 int
13442 RegisterMove ()
13443 {
13444     FILE *f;
13445     char string[MSG_SIZ];
13446
13447     if (   cmailMailedMove
13448         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
13449         return TRUE;            /* Allow free viewing  */
13450     }
13451
13452     /* Unregister move to ensure that we don't leave RegisterMove        */
13453     /* with the move registered when the conditions for registering no   */
13454     /* longer hold                                                       */
13455     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
13456         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
13457         nCmailMovesRegistered --;
13458
13459         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
13460           {
13461               free(cmailCommentList[lastLoadGameNumber - 1]);
13462               cmailCommentList[lastLoadGameNumber - 1] = NULL;
13463           }
13464     }
13465
13466     if (cmailOldMove == -1) {
13467         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
13468         return FALSE;
13469     }
13470
13471     if (currentMove > cmailOldMove + 1) {
13472         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
13473         return FALSE;
13474     }
13475
13476     if (currentMove < cmailOldMove) {
13477         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
13478         return FALSE;
13479     }
13480
13481     if (forwardMostMove > currentMove) {
13482         /* Silently truncate extra moves */
13483         TruncateGame();
13484     }
13485
13486     if (   (currentMove == cmailOldMove + 1)
13487         || (   (currentMove == cmailOldMove)
13488             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
13489                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
13490         if (gameInfo.result != GameUnfinished) {
13491             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
13492         }
13493
13494         if (commentList[currentMove] != NULL) {
13495             cmailCommentList[lastLoadGameNumber - 1]
13496               = StrSave(commentList[currentMove]);
13497         }
13498         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
13499
13500         if (appData.debugMode)
13501           fprintf(debugFP, "Saving %s for game %d\n",
13502                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
13503
13504         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
13505
13506         f = fopen(string, "w");
13507         if (appData.oldSaveStyle) {
13508             SaveGameOldStyle(f); /* also closes the file */
13509
13510             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
13511             f = fopen(string, "w");
13512             SavePosition(f, 0, NULL); /* also closes the file */
13513         } else {
13514             fprintf(f, "{--------------\n");
13515             PrintPosition(f, currentMove);
13516             fprintf(f, "--------------}\n\n");
13517
13518             SaveGame(f, 0, NULL); /* also closes the file*/
13519         }
13520
13521         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
13522         nCmailMovesRegistered ++;
13523     } else if (nCmailGames == 1) {
13524         DisplayError(_("You have not made a move yet"), 0);
13525         return FALSE;
13526     }
13527
13528     return TRUE;
13529 }
13530
13531 void
13532 MailMoveEvent ()
13533 {
13534 #if !WIN32
13535     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
13536     FILE *commandOutput;
13537     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
13538     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
13539     int nBuffers;
13540     int i;
13541     int archived;
13542     char *arcDir;
13543
13544     if (! cmailMsgLoaded) {
13545         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
13546         return;
13547     }
13548
13549     if (nCmailGames == nCmailResults) {
13550         DisplayError(_("No unfinished games"), 0);
13551         return;
13552     }
13553
13554 #if CMAIL_PROHIBIT_REMAIL
13555     if (cmailMailedMove) {
13556       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);
13557         DisplayError(msg, 0);
13558         return;
13559     }
13560 #endif
13561
13562     if (! (cmailMailedMove || RegisterMove())) return;
13563
13564     if (   cmailMailedMove
13565         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13566       snprintf(string, MSG_SIZ, partCommandString,
13567                appData.debugMode ? " -v" : "", appData.cmailGameName);
13568         commandOutput = popen(string, "r");
13569
13570         if (commandOutput == NULL) {
13571             DisplayError(_("Failed to invoke cmail"), 0);
13572         } else {
13573             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13574                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13575             }
13576             if (nBuffers > 1) {
13577                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13578                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13579                 nBytes = MSG_SIZ - 1;
13580             } else {
13581                 (void) memcpy(msg, buffer, nBytes);
13582             }
13583             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13584
13585             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13586                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
13587
13588                 archived = TRUE;
13589                 for (i = 0; i < nCmailGames; i ++) {
13590                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
13591                         archived = FALSE;
13592                     }
13593                 }
13594                 if (   archived
13595                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13596                         != NULL)) {
13597                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13598                            arcDir,
13599                            appData.cmailGameName,
13600                            gameInfo.date);
13601                     LoadGameFromFile(buffer, 1, buffer, FALSE);
13602                     cmailMsgLoaded = FALSE;
13603                 }
13604             }
13605
13606             DisplayInformation(msg);
13607             pclose(commandOutput);
13608         }
13609     } else {
13610         if ((*cmailMsg) != '\0') {
13611             DisplayInformation(cmailMsg);
13612         }
13613     }
13614
13615     return;
13616 #endif /* !WIN32 */
13617 }
13618
13619 char *
13620 CmailMsg ()
13621 {
13622 #if WIN32
13623     return NULL;
13624 #else
13625     int  prependComma = 0;
13626     char number[5];
13627     char string[MSG_SIZ];       /* Space for game-list */
13628     int  i;
13629
13630     if (!cmailMsgLoaded) return "";
13631
13632     if (cmailMailedMove) {
13633       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13634     } else {
13635         /* Create a list of games left */
13636       snprintf(string, MSG_SIZ, "[");
13637         for (i = 0; i < nCmailGames; i ++) {
13638             if (! (   cmailMoveRegistered[i]
13639                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13640                 if (prependComma) {
13641                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13642                 } else {
13643                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13644                     prependComma = 1;
13645                 }
13646
13647                 strcat(string, number);
13648             }
13649         }
13650         strcat(string, "]");
13651
13652         if (nCmailMovesRegistered + nCmailResults == 0) {
13653             switch (nCmailGames) {
13654               case 1:
13655                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13656                 break;
13657
13658               case 2:
13659                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13660                 break;
13661
13662               default:
13663                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13664                          nCmailGames);
13665                 break;
13666             }
13667         } else {
13668             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13669               case 1:
13670                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13671                          string);
13672                 break;
13673
13674               case 0:
13675                 if (nCmailResults == nCmailGames) {
13676                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13677                 } else {
13678                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13679                 }
13680                 break;
13681
13682               default:
13683                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13684                          string);
13685             }
13686         }
13687     }
13688     return cmailMsg;
13689 #endif /* WIN32 */
13690 }
13691
13692 void
13693 ResetGameEvent ()
13694 {
13695     if (gameMode == Training)
13696       SetTrainingModeOff();
13697
13698     Reset(TRUE, TRUE);
13699     cmailMsgLoaded = FALSE;
13700     if (appData.icsActive) {
13701       SendToICS(ics_prefix);
13702       SendToICS("refresh\n");
13703     }
13704 }
13705
13706 void
13707 ExitEvent (int status)
13708 {
13709     exiting++;
13710     if (exiting > 2) {
13711       /* Give up on clean exit */
13712       exit(status);
13713     }
13714     if (exiting > 1) {
13715       /* Keep trying for clean exit */
13716       return;
13717     }
13718
13719     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
13720
13721     if (telnetISR != NULL) {
13722       RemoveInputSource(telnetISR);
13723     }
13724     if (icsPR != NoProc) {
13725       DestroyChildProcess(icsPR, TRUE);
13726     }
13727
13728     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
13729     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
13730
13731     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
13732     /* make sure this other one finishes before killing it!                  */
13733     if(endingGame) { int count = 0;
13734         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
13735         while(endingGame && count++ < 10) DoSleep(1);
13736         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
13737     }
13738
13739     /* Kill off chess programs */
13740     if (first.pr != NoProc) {
13741         ExitAnalyzeMode();
13742
13743         DoSleep( appData.delayBeforeQuit );
13744         SendToProgram("quit\n", &first);
13745         DoSleep( appData.delayAfterQuit );
13746         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
13747     }
13748     if (second.pr != NoProc) {
13749         DoSleep( appData.delayBeforeQuit );
13750         SendToProgram("quit\n", &second);
13751         DoSleep( appData.delayAfterQuit );
13752         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
13753     }
13754     if (first.isr != NULL) {
13755         RemoveInputSource(first.isr);
13756     }
13757     if (second.isr != NULL) {
13758         RemoveInputSource(second.isr);
13759     }
13760
13761     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13762     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13763
13764     ShutDownFrontEnd();
13765     exit(status);
13766 }
13767
13768 void
13769 PauseEngine (ChessProgramState *cps)
13770 {
13771     SendToProgram("pause\n", cps);
13772     cps->pause = 2;
13773 }
13774
13775 void
13776 UnPauseEngine (ChessProgramState *cps)
13777 {
13778     SendToProgram("resume\n", cps);
13779     cps->pause = 1;
13780 }
13781
13782 void
13783 PauseEvent ()
13784 {
13785     if (appData.debugMode)
13786         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13787     if (pausing) {
13788         pausing = FALSE;
13789         ModeHighlight();
13790         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
13791             StartClocks();
13792             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
13793                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
13794                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
13795             }
13796             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
13797             HandleMachineMove(stashedInputMove, stalledEngine);
13798             stalledEngine = NULL;
13799             return;
13800         }
13801         if (gameMode == MachinePlaysWhite ||
13802             gameMode == TwoMachinesPlay   ||
13803             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
13804             if(first.pause)  UnPauseEngine(&first);
13805             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
13806             if(second.pause) UnPauseEngine(&second);
13807             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
13808             StartClocks();
13809         } else {
13810             DisplayBothClocks();
13811         }
13812         if (gameMode == PlayFromGameFile) {
13813             if (appData.timeDelay >= 0)
13814                 AutoPlayGameLoop();
13815         } else if (gameMode == IcsExamining && pauseExamInvalid) {
13816             Reset(FALSE, TRUE);
13817             SendToICS(ics_prefix);
13818             SendToICS("refresh\n");
13819         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
13820             ForwardInner(forwardMostMove);
13821         }
13822         pauseExamInvalid = FALSE;
13823     } else {
13824         switch (gameMode) {
13825           default:
13826             return;
13827           case IcsExamining:
13828             pauseExamForwardMostMove = forwardMostMove;
13829             pauseExamInvalid = FALSE;
13830             /* fall through */
13831           case IcsObserving:
13832           case IcsPlayingWhite:
13833           case IcsPlayingBlack:
13834             pausing = TRUE;
13835             ModeHighlight();
13836             return;
13837           case PlayFromGameFile:
13838             (void) StopLoadGameTimer();
13839             pausing = TRUE;
13840             ModeHighlight();
13841             break;
13842           case BeginningOfGame:
13843             if (appData.icsActive) return;
13844             /* else fall through */
13845           case MachinePlaysWhite:
13846           case MachinePlaysBlack:
13847           case TwoMachinesPlay:
13848             if (forwardMostMove == 0)
13849               return;           /* don't pause if no one has moved */
13850             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
13851                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
13852                 if(onMove->pause) {           // thinking engine can be paused
13853                     PauseEngine(onMove);      // do it
13854                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
13855                         PauseEngine(onMove->other);
13856                     else
13857                         SendToProgram("easy\n", onMove->other);
13858                     StopClocks();
13859                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
13860             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
13861                 if(first.pause) {
13862                     PauseEngine(&first);
13863                     StopClocks();
13864                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
13865             } else { // human on move, pause pondering by either method
13866                 if(first.pause)
13867                     PauseEngine(&first);
13868                 else if(appData.ponderNextMove)
13869                     SendToProgram("easy\n", &first);
13870                 StopClocks();
13871             }
13872             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
13873           case AnalyzeMode:
13874             pausing = TRUE;
13875             ModeHighlight();
13876             break;
13877         }
13878     }
13879 }
13880
13881 void
13882 EditCommentEvent ()
13883 {
13884     char title[MSG_SIZ];
13885
13886     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13887       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13888     } else {
13889       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13890                WhiteOnMove(currentMove - 1) ? " " : ".. ",
13891                parseList[currentMove - 1]);
13892     }
13893
13894     EditCommentPopUp(currentMove, title, commentList[currentMove]);
13895 }
13896
13897
13898 void
13899 EditTagsEvent ()
13900 {
13901     char *tags = PGNTags(&gameInfo);
13902     bookUp = FALSE;
13903     EditTagsPopUp(tags, NULL);
13904     free(tags);
13905 }
13906
13907 void
13908 ToggleSecond ()
13909 {
13910   if(second.analyzing) {
13911     SendToProgram("exit\n", &second);
13912     second.analyzing = FALSE;
13913   } else {
13914     if (second.pr == NoProc) StartChessProgram(&second);
13915     InitChessProgram(&second, FALSE);
13916     FeedMovesToProgram(&second, currentMove);
13917
13918     SendToProgram("analyze\n", &second);
13919     second.analyzing = TRUE;
13920   }
13921 }
13922
13923 /* Toggle ShowThinking */
13924 void
13925 ToggleShowThinking()
13926 {
13927   appData.showThinking = !appData.showThinking;
13928   ShowThinkingEvent();
13929 }
13930
13931 int
13932 AnalyzeModeEvent ()
13933 {
13934     char buf[MSG_SIZ];
13935
13936     if (!first.analysisSupport) {
13937       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
13938       DisplayError(buf, 0);
13939       return 0;
13940     }
13941     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
13942     if (appData.icsActive) {
13943         if (gameMode != IcsObserving) {
13944           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
13945             DisplayError(buf, 0);
13946             /* secure check */
13947             if (appData.icsEngineAnalyze) {
13948                 if (appData.debugMode)
13949                     fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
13950                 ExitAnalyzeMode();
13951                 ModeHighlight();
13952             }
13953             return 0;
13954         }
13955         /* if enable, user wants to disable icsEngineAnalyze */
13956         if (appData.icsEngineAnalyze) {
13957                 ExitAnalyzeMode();
13958                 ModeHighlight();
13959                 return 0;
13960         }
13961         appData.icsEngineAnalyze = TRUE;
13962         if (appData.debugMode)
13963             fprintf(debugFP, "ICS engine analyze starting... \n");
13964     }
13965
13966     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
13967     if (appData.noChessProgram || gameMode == AnalyzeMode)
13968       return 0;
13969
13970     if (gameMode != AnalyzeFile) {
13971         if (!appData.icsEngineAnalyze) {
13972                EditGameEvent();
13973                if (gameMode != EditGame) return 0;
13974         }
13975         if (!appData.showThinking) ToggleShowThinking();
13976         ResurrectChessProgram();
13977         SendToProgram("analyze\n", &first);
13978         first.analyzing = TRUE;
13979         /*first.maybeThinking = TRUE;*/
13980         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13981         EngineOutputPopUp();
13982     }
13983     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
13984     pausing = FALSE;
13985     ModeHighlight();
13986     SetGameInfo();
13987
13988     StartAnalysisClock();
13989     GetTimeMark(&lastNodeCountTime);
13990     lastNodeCount = 0;
13991     return 1;
13992 }
13993
13994 void
13995 AnalyzeFileEvent ()
13996 {
13997     if (appData.noChessProgram || gameMode == AnalyzeFile)
13998       return;
13999
14000     if (!first.analysisSupport) {
14001       char buf[MSG_SIZ];
14002       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14003       DisplayError(buf, 0);
14004       return;
14005     }
14006
14007     if (gameMode != AnalyzeMode) {
14008         keepInfo = 1; // mere annotating should not alter PGN tags
14009         EditGameEvent();
14010         keepInfo = 0;
14011         if (gameMode != EditGame) return;
14012         if (!appData.showThinking) ToggleShowThinking();
14013         ResurrectChessProgram();
14014         SendToProgram("analyze\n", &first);
14015         first.analyzing = TRUE;
14016         /*first.maybeThinking = TRUE;*/
14017         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14018         EngineOutputPopUp();
14019     }
14020     gameMode = AnalyzeFile;
14021     pausing = FALSE;
14022     ModeHighlight();
14023
14024     StartAnalysisClock();
14025     GetTimeMark(&lastNodeCountTime);
14026     lastNodeCount = 0;
14027     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14028     AnalysisPeriodicEvent(1);
14029 }
14030
14031 void
14032 MachineWhiteEvent ()
14033 {
14034     char buf[MSG_SIZ];
14035     char *bookHit = NULL;
14036
14037     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14038       return;
14039
14040
14041     if (gameMode == PlayFromGameFile ||
14042         gameMode == TwoMachinesPlay  ||
14043         gameMode == Training         ||
14044         gameMode == AnalyzeMode      ||
14045         gameMode == EndOfGame)
14046         EditGameEvent();
14047
14048     if (gameMode == EditPosition)
14049         EditPositionDone(TRUE);
14050
14051     if (!WhiteOnMove(currentMove)) {
14052         DisplayError(_("It is not White's turn"), 0);
14053         return;
14054     }
14055
14056     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14057       ExitAnalyzeMode();
14058
14059     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14060         gameMode == AnalyzeFile)
14061         TruncateGame();
14062
14063     ResurrectChessProgram();    /* in case it isn't running */
14064     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
14065         gameMode = MachinePlaysWhite;
14066         ResetClocks();
14067     } else
14068     gameMode = MachinePlaysWhite;
14069     pausing = FALSE;
14070     ModeHighlight();
14071     SetGameInfo();
14072     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14073     DisplayTitle(buf);
14074     if (first.sendName) {
14075       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
14076       SendToProgram(buf, &first);
14077     }
14078     if (first.sendTime) {
14079       if (first.useColors) {
14080         SendToProgram("black\n", &first); /*gnu kludge*/
14081       }
14082       SendTimeRemaining(&first, TRUE);
14083     }
14084     if (first.useColors) {
14085       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
14086     }
14087     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14088     SetMachineThinkingEnables();
14089     first.maybeThinking = TRUE;
14090     StartClocks();
14091     firstMove = FALSE;
14092
14093     if (appData.autoFlipView && !flipView) {
14094       flipView = !flipView;
14095       DrawPosition(FALSE, NULL);
14096       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14097     }
14098
14099     if(bookHit) { // [HGM] book: simulate book reply
14100         static char bookMove[MSG_SIZ]; // a bit generous?
14101
14102         programStats.nodes = programStats.depth = programStats.time =
14103         programStats.score = programStats.got_only_move = 0;
14104         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14105
14106         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14107         strcat(bookMove, bookHit);
14108         HandleMachineMove(bookMove, &first);
14109     }
14110 }
14111
14112 void
14113 MachineBlackEvent ()
14114 {
14115   char buf[MSG_SIZ];
14116   char *bookHit = NULL;
14117
14118     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
14119         return;
14120
14121
14122     if (gameMode == PlayFromGameFile ||
14123         gameMode == TwoMachinesPlay  ||
14124         gameMode == Training         ||
14125         gameMode == AnalyzeMode      ||
14126         gameMode == EndOfGame)
14127         EditGameEvent();
14128
14129     if (gameMode == EditPosition)
14130         EditPositionDone(TRUE);
14131
14132     if (WhiteOnMove(currentMove)) {
14133         DisplayError(_("It is not Black's turn"), 0);
14134         return;
14135     }
14136
14137     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14138       ExitAnalyzeMode();
14139
14140     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14141         gameMode == AnalyzeFile)
14142         TruncateGame();
14143
14144     ResurrectChessProgram();    /* in case it isn't running */
14145     gameMode = MachinePlaysBlack;
14146     pausing = FALSE;
14147     ModeHighlight();
14148     SetGameInfo();
14149     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14150     DisplayTitle(buf);
14151     if (first.sendName) {
14152       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14153       SendToProgram(buf, &first);
14154     }
14155     if (first.sendTime) {
14156       if (first.useColors) {
14157         SendToProgram("white\n", &first); /*gnu kludge*/
14158       }
14159       SendTimeRemaining(&first, FALSE);
14160     }
14161     if (first.useColors) {
14162       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14163     }
14164     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14165     SetMachineThinkingEnables();
14166     first.maybeThinking = TRUE;
14167     StartClocks();
14168
14169     if (appData.autoFlipView && flipView) {
14170       flipView = !flipView;
14171       DrawPosition(FALSE, NULL);
14172       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14173     }
14174     if(bookHit) { // [HGM] book: simulate book reply
14175         static char bookMove[MSG_SIZ]; // a bit generous?
14176
14177         programStats.nodes = programStats.depth = programStats.time =
14178         programStats.score = programStats.got_only_move = 0;
14179         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14180
14181         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14182         strcat(bookMove, bookHit);
14183         HandleMachineMove(bookMove, &first);
14184     }
14185 }
14186
14187
14188 void
14189 DisplayTwoMachinesTitle ()
14190 {
14191     char buf[MSG_SIZ];
14192     if (appData.matchGames > 0) {
14193         if(appData.tourneyFile[0]) {
14194           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14195                    gameInfo.white, _("vs."), gameInfo.black,
14196                    nextGame+1, appData.matchGames+1,
14197                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14198         } else
14199         if (first.twoMachinesColor[0] == 'w') {
14200           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14201                    gameInfo.white, _("vs."),  gameInfo.black,
14202                    first.matchWins, second.matchWins,
14203                    matchGame - 1 - (first.matchWins + second.matchWins));
14204         } else {
14205           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14206                    gameInfo.white, _("vs."), gameInfo.black,
14207                    second.matchWins, first.matchWins,
14208                    matchGame - 1 - (first.matchWins + second.matchWins));
14209         }
14210     } else {
14211       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14212     }
14213     DisplayTitle(buf);
14214 }
14215
14216 void
14217 SettingsMenuIfReady ()
14218 {
14219   if (second.lastPing != second.lastPong) {
14220     DisplayMessage("", _("Waiting for second chess program"));
14221     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14222     return;
14223   }
14224   ThawUI();
14225   DisplayMessage("", "");
14226   SettingsPopUp(&second);
14227 }
14228
14229 int
14230 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
14231 {
14232     char buf[MSG_SIZ];
14233     if (cps->pr == NoProc) {
14234         StartChessProgram(cps);
14235         if (cps->protocolVersion == 1) {
14236           retry();
14237           ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
14238         } else {
14239           /* kludge: allow timeout for initial "feature" command */
14240           if(retry != TwoMachinesEventIfReady) FreezeUI();
14241           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
14242           DisplayMessage("", buf);
14243           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
14244         }
14245         return 1;
14246     }
14247     return 0;
14248 }
14249
14250 void
14251 TwoMachinesEvent P((void))
14252 {
14253     int i;
14254     char buf[MSG_SIZ];
14255     ChessProgramState *onmove;
14256     char *bookHit = NULL;
14257     static int stalling = 0;
14258     TimeMark now;
14259     long wait;
14260
14261     if (appData.noChessProgram) return;
14262
14263     switch (gameMode) {
14264       case TwoMachinesPlay:
14265         return;
14266       case MachinePlaysWhite:
14267       case MachinePlaysBlack:
14268         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14269             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
14270             return;
14271         }
14272         /* fall through */
14273       case BeginningOfGame:
14274       case PlayFromGameFile:
14275       case EndOfGame:
14276         EditGameEvent();
14277         if (gameMode != EditGame) return;
14278         break;
14279       case EditPosition:
14280         EditPositionDone(TRUE);
14281         break;
14282       case AnalyzeMode:
14283       case AnalyzeFile:
14284         ExitAnalyzeMode();
14285         break;
14286       case EditGame:
14287       default:
14288         break;
14289     }
14290
14291 //    forwardMostMove = currentMove;
14292     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
14293     startingEngine = TRUE;
14294
14295     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
14296
14297     if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14298     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14299       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14300       return;
14301     }
14302     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14303
14304     if(second.protocolVersion >= 2 && !strstr(second.variants, VariantName(gameInfo.variant))) {
14305         startingEngine = FALSE;
14306         DisplayError("second engine does not play this", 0);
14307         return;
14308     }
14309
14310     if(!stalling) {
14311       InitChessProgram(&second, FALSE); // unbalances ping of second engine
14312       SendToProgram("force\n", &second);
14313       stalling = 1;
14314       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14315       return;
14316     }
14317     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14318     if(appData.matchPause>10000 || appData.matchPause<10)
14319                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14320     wait = SubtractTimeMarks(&now, &pauseStart);
14321     if(wait < appData.matchPause) {
14322         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14323         return;
14324     }
14325     // we are now committed to starting the game
14326     stalling = 0;
14327     DisplayMessage("", "");
14328     if (startedFromSetupPosition) {
14329         SendBoard(&second, backwardMostMove);
14330     if (appData.debugMode) {
14331         fprintf(debugFP, "Two Machines\n");
14332     }
14333     }
14334     for (i = backwardMostMove; i < forwardMostMove; i++) {
14335         SendMoveToProgram(i, &second);
14336     }
14337
14338     gameMode = TwoMachinesPlay;
14339     pausing = startingEngine = FALSE;
14340     ModeHighlight(); // [HGM] logo: this triggers display update of logos
14341     SetGameInfo();
14342     DisplayTwoMachinesTitle();
14343     firstMove = TRUE;
14344     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14345         onmove = &first;
14346     } else {
14347         onmove = &second;
14348     }
14349     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14350     SendToProgram(first.computerString, &first);
14351     if (first.sendName) {
14352       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14353       SendToProgram(buf, &first);
14354     }
14355     SendToProgram(second.computerString, &second);
14356     if (second.sendName) {
14357       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
14358       SendToProgram(buf, &second);
14359     }
14360
14361     ResetClocks();
14362     if (!first.sendTime || !second.sendTime) {
14363         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14364         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14365     }
14366     if (onmove->sendTime) {
14367       if (onmove->useColors) {
14368         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
14369       }
14370       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
14371     }
14372     if (onmove->useColors) {
14373       SendToProgram(onmove->twoMachinesColor, onmove);
14374     }
14375     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
14376 //    SendToProgram("go\n", onmove);
14377     onmove->maybeThinking = TRUE;
14378     SetMachineThinkingEnables();
14379
14380     StartClocks();
14381
14382     if(bookHit) { // [HGM] book: simulate book reply
14383         static char bookMove[MSG_SIZ]; // a bit generous?
14384
14385         programStats.nodes = programStats.depth = programStats.time =
14386         programStats.score = programStats.got_only_move = 0;
14387         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14388
14389         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14390         strcat(bookMove, bookHit);
14391         savedMessage = bookMove; // args for deferred call
14392         savedState = onmove;
14393         ScheduleDelayedEvent(DeferredBookMove, 1);
14394     }
14395 }
14396
14397 void
14398 TrainingEvent ()
14399 {
14400     if (gameMode == Training) {
14401       SetTrainingModeOff();
14402       gameMode = PlayFromGameFile;
14403       DisplayMessage("", _("Training mode off"));
14404     } else {
14405       gameMode = Training;
14406       animateTraining = appData.animate;
14407
14408       /* make sure we are not already at the end of the game */
14409       if (currentMove < forwardMostMove) {
14410         SetTrainingModeOn();
14411         DisplayMessage("", _("Training mode on"));
14412       } else {
14413         gameMode = PlayFromGameFile;
14414         DisplayError(_("Already at end of game"), 0);
14415       }
14416     }
14417     ModeHighlight();
14418 }
14419
14420 void
14421 IcsClientEvent ()
14422 {
14423     if (!appData.icsActive) return;
14424     switch (gameMode) {
14425       case IcsPlayingWhite:
14426       case IcsPlayingBlack:
14427       case IcsObserving:
14428       case IcsIdle:
14429       case BeginningOfGame:
14430       case IcsExamining:
14431         return;
14432
14433       case EditGame:
14434         break;
14435
14436       case EditPosition:
14437         EditPositionDone(TRUE);
14438         break;
14439
14440       case AnalyzeMode:
14441       case AnalyzeFile:
14442         ExitAnalyzeMode();
14443         break;
14444
14445       default:
14446         EditGameEvent();
14447         break;
14448     }
14449
14450     gameMode = IcsIdle;
14451     ModeHighlight();
14452     return;
14453 }
14454
14455 void
14456 EditGameEvent ()
14457 {
14458     int i;
14459
14460     switch (gameMode) {
14461       case Training:
14462         SetTrainingModeOff();
14463         break;
14464       case MachinePlaysWhite:
14465       case MachinePlaysBlack:
14466       case BeginningOfGame:
14467         SendToProgram("force\n", &first);
14468         SetUserThinkingEnables();
14469         break;
14470       case PlayFromGameFile:
14471         (void) StopLoadGameTimer();
14472         if (gameFileFP != NULL) {
14473             gameFileFP = NULL;
14474         }
14475         break;
14476       case EditPosition:
14477         EditPositionDone(TRUE);
14478         break;
14479       case AnalyzeMode:
14480       case AnalyzeFile:
14481         ExitAnalyzeMode();
14482         SendToProgram("force\n", &first);
14483         break;
14484       case TwoMachinesPlay:
14485         GameEnds(EndOfFile, NULL, GE_PLAYER);
14486         ResurrectChessProgram();
14487         SetUserThinkingEnables();
14488         break;
14489       case EndOfGame:
14490         ResurrectChessProgram();
14491         break;
14492       case IcsPlayingBlack:
14493       case IcsPlayingWhite:
14494         DisplayError(_("Warning: You are still playing a game"), 0);
14495         break;
14496       case IcsObserving:
14497         DisplayError(_("Warning: You are still observing a game"), 0);
14498         break;
14499       case IcsExamining:
14500         DisplayError(_("Warning: You are still examining a game"), 0);
14501         break;
14502       case IcsIdle:
14503         break;
14504       case EditGame:
14505       default:
14506         return;
14507     }
14508
14509     pausing = FALSE;
14510     StopClocks();
14511     first.offeredDraw = second.offeredDraw = 0;
14512
14513     if (gameMode == PlayFromGameFile) {
14514         whiteTimeRemaining = timeRemaining[0][currentMove];
14515         blackTimeRemaining = timeRemaining[1][currentMove];
14516         DisplayTitle("");
14517     }
14518
14519     if (gameMode == MachinePlaysWhite ||
14520         gameMode == MachinePlaysBlack ||
14521         gameMode == TwoMachinesPlay ||
14522         gameMode == EndOfGame) {
14523         i = forwardMostMove;
14524         while (i > currentMove) {
14525             SendToProgram("undo\n", &first);
14526             i--;
14527         }
14528         if(!adjustedClock) {
14529         whiteTimeRemaining = timeRemaining[0][currentMove];
14530         blackTimeRemaining = timeRemaining[1][currentMove];
14531         DisplayBothClocks();
14532         }
14533         if (whiteFlag || blackFlag) {
14534             whiteFlag = blackFlag = 0;
14535         }
14536         DisplayTitle("");
14537     }
14538
14539     gameMode = EditGame;
14540     ModeHighlight();
14541     SetGameInfo();
14542 }
14543
14544
14545 void
14546 EditPositionEvent ()
14547 {
14548     if (gameMode == EditPosition) {
14549         EditGameEvent();
14550         return;
14551     }
14552
14553     EditGameEvent();
14554     if (gameMode != EditGame) return;
14555
14556     gameMode = EditPosition;
14557     ModeHighlight();
14558     SetGameInfo();
14559     if (currentMove > 0)
14560       CopyBoard(boards[0], boards[currentMove]);
14561
14562     blackPlaysFirst = !WhiteOnMove(currentMove);
14563     ResetClocks();
14564     currentMove = forwardMostMove = backwardMostMove = 0;
14565     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14566     DisplayMove(-1);
14567     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
14568 }
14569
14570 void
14571 ExitAnalyzeMode ()
14572 {
14573     /* [DM] icsEngineAnalyze - possible call from other functions */
14574     if (appData.icsEngineAnalyze) {
14575         appData.icsEngineAnalyze = FALSE;
14576
14577         DisplayMessage("",_("Close ICS engine analyze..."));
14578     }
14579     if (first.analysisSupport && first.analyzing) {
14580       SendToBoth("exit\n");
14581       first.analyzing = second.analyzing = FALSE;
14582     }
14583     thinkOutput[0] = NULLCHAR;
14584 }
14585
14586 void
14587 EditPositionDone (Boolean fakeRights)
14588 {
14589     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
14590
14591     startedFromSetupPosition = TRUE;
14592     InitChessProgram(&first, FALSE);
14593     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
14594       boards[0][EP_STATUS] = EP_NONE;
14595       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
14596       if(boards[0][0][BOARD_WIDTH>>1] == king) {
14597         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
14598         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
14599       } else boards[0][CASTLING][2] = NoRights;
14600       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
14601         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
14602         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
14603       } else boards[0][CASTLING][5] = NoRights;
14604       if(gameInfo.variant == VariantSChess) {
14605         int i;
14606         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
14607           boards[0][VIRGIN][i] = 0;
14608           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
14609           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
14610         }
14611       }
14612     }
14613     SendToProgram("force\n", &first);
14614     if (blackPlaysFirst) {
14615         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
14616         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
14617         currentMove = forwardMostMove = backwardMostMove = 1;
14618         CopyBoard(boards[1], boards[0]);
14619     } else {
14620         currentMove = forwardMostMove = backwardMostMove = 0;
14621     }
14622     SendBoard(&first, forwardMostMove);
14623     if (appData.debugMode) {
14624         fprintf(debugFP, "EditPosDone\n");
14625     }
14626     DisplayTitle("");
14627     DisplayMessage("", "");
14628     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14629     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14630     gameMode = EditGame;
14631     ModeHighlight();
14632     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14633     ClearHighlights(); /* [AS] */
14634 }
14635
14636 /* Pause for `ms' milliseconds */
14637 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14638 void
14639 TimeDelay (long ms)
14640 {
14641     TimeMark m1, m2;
14642
14643     GetTimeMark(&m1);
14644     do {
14645         GetTimeMark(&m2);
14646     } while (SubtractTimeMarks(&m2, &m1) < ms);
14647 }
14648
14649 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14650 void
14651 SendMultiLineToICS (char *buf)
14652 {
14653     char temp[MSG_SIZ+1], *p;
14654     int len;
14655
14656     len = strlen(buf);
14657     if (len > MSG_SIZ)
14658       len = MSG_SIZ;
14659
14660     strncpy(temp, buf, len);
14661     temp[len] = 0;
14662
14663     p = temp;
14664     while (*p) {
14665         if (*p == '\n' || *p == '\r')
14666           *p = ' ';
14667         ++p;
14668     }
14669
14670     strcat(temp, "\n");
14671     SendToICS(temp);
14672     SendToPlayer(temp, strlen(temp));
14673 }
14674
14675 void
14676 SetWhiteToPlayEvent ()
14677 {
14678     if (gameMode == EditPosition) {
14679         blackPlaysFirst = FALSE;
14680         DisplayBothClocks();    /* works because currentMove is 0 */
14681     } else if (gameMode == IcsExamining) {
14682         SendToICS(ics_prefix);
14683         SendToICS("tomove white\n");
14684     }
14685 }
14686
14687 void
14688 SetBlackToPlayEvent ()
14689 {
14690     if (gameMode == EditPosition) {
14691         blackPlaysFirst = TRUE;
14692         currentMove = 1;        /* kludge */
14693         DisplayBothClocks();
14694         currentMove = 0;
14695     } else if (gameMode == IcsExamining) {
14696         SendToICS(ics_prefix);
14697         SendToICS("tomove black\n");
14698     }
14699 }
14700
14701 void
14702 EditPositionMenuEvent (ChessSquare selection, int x, int y)
14703 {
14704     char buf[MSG_SIZ];
14705     ChessSquare piece = boards[0][y][x];
14706     static Board erasedBoard, currentBoard, menuBoard, nullBoard;
14707     static int lastVariant;
14708
14709     if (gameMode != EditPosition && gameMode != IcsExamining) return;
14710
14711     switch (selection) {
14712       case ClearBoard:
14713         CopyBoard(currentBoard, boards[0]);
14714         CopyBoard(menuBoard, initialPosition);
14715         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
14716             SendToICS(ics_prefix);
14717             SendToICS("bsetup clear\n");
14718         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
14719             SendToICS(ics_prefix);
14720             SendToICS("clearboard\n");
14721         } else {
14722             int nonEmpty = 0;
14723             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
14724                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
14725                 for (y = 0; y < BOARD_HEIGHT; y++) {
14726                     if (gameMode == IcsExamining) {
14727                         if (boards[currentMove][y][x] != EmptySquare) {
14728                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
14729                                     AAA + x, ONE + y);
14730                             SendToICS(buf);
14731                         }
14732                     } else {
14733                         if(boards[0][y][x] != p) nonEmpty++;
14734                         boards[0][y][x] = p;
14735                     }
14736                 }
14737                 menuBoard[1][x] = menuBoard[BOARD_HEIGHT-2][x] = p;
14738             }
14739             if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
14740                 for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates 
14741                     ChessSquare p = menuBoard[0][x];
14742                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[0][y] == p) menuBoard[0][y] = EmptySquare;
14743                     p = menuBoard[BOARD_HEIGHT-1][x];
14744                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[BOARD_HEIGHT-1][y] == p) menuBoard[BOARD_HEIGHT-1][y] = EmptySquare;
14745                 }
14746                 DisplayMessage("Clicking clock again restores position", "");
14747                 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
14748                 if(!nonEmpty) { // asked to clear an empty board
14749                     CopyBoard(boards[0], menuBoard);
14750                 } else
14751                 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
14752                     CopyBoard(boards[0], initialPosition);
14753                 } else
14754                 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
14755                                                                  && !CompareBoards(nullBoard, erasedBoard)) {
14756                     CopyBoard(boards[0], erasedBoard);
14757                 } else
14758                     CopyBoard(erasedBoard, currentBoard);
14759
14760             }
14761         }
14762         if (gameMode == EditPosition) {
14763             DrawPosition(FALSE, boards[0]);
14764         }
14765         break;
14766
14767       case WhitePlay:
14768         SetWhiteToPlayEvent();
14769         break;
14770
14771       case BlackPlay:
14772         SetBlackToPlayEvent();
14773         break;
14774
14775       case EmptySquare:
14776         if (gameMode == IcsExamining) {
14777             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14778             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
14779             SendToICS(buf);
14780         } else {
14781             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14782                 if(x == BOARD_LEFT-2) {
14783                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
14784                     boards[0][y][1] = 0;
14785                 } else
14786                 if(x == BOARD_RGHT+1) {
14787                     if(y >= gameInfo.holdingsSize) break;
14788                     boards[0][y][BOARD_WIDTH-2] = 0;
14789                 } else break;
14790             }
14791             boards[0][y][x] = EmptySquare;
14792             DrawPosition(FALSE, boards[0]);
14793         }
14794         break;
14795
14796       case PromotePiece:
14797         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
14798            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
14799             selection = (ChessSquare) (PROMOTED piece);
14800         } else if(piece == EmptySquare) selection = WhiteSilver;
14801         else selection = (ChessSquare)((int)piece - 1);
14802         goto defaultlabel;
14803
14804       case DemotePiece:
14805         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
14806            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
14807             selection = (ChessSquare) (DEMOTED piece);
14808         } else if(piece == EmptySquare) selection = BlackSilver;
14809         else selection = (ChessSquare)((int)piece + 1);
14810         goto defaultlabel;
14811
14812       case WhiteQueen:
14813       case BlackQueen:
14814         if(gameInfo.variant == VariantShatranj ||
14815            gameInfo.variant == VariantXiangqi  ||
14816            gameInfo.variant == VariantCourier  ||
14817            gameInfo.variant == VariantASEAN    ||
14818            gameInfo.variant == VariantMakruk     )
14819             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
14820         goto defaultlabel;
14821
14822       case WhiteKing:
14823       case BlackKing:
14824         if(gameInfo.variant == VariantXiangqi)
14825             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
14826         if(gameInfo.variant == VariantKnightmate)
14827             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
14828       default:
14829         defaultlabel:
14830         if (gameMode == IcsExamining) {
14831             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14832             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
14833                      PieceToChar(selection), AAA + x, ONE + y);
14834             SendToICS(buf);
14835         } else {
14836             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14837                 int n;
14838                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
14839                     n = PieceToNumber(selection - BlackPawn);
14840                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
14841                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
14842                     boards[0][BOARD_HEIGHT-1-n][1]++;
14843                 } else
14844                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
14845                     n = PieceToNumber(selection);
14846                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
14847                     boards[0][n][BOARD_WIDTH-1] = selection;
14848                     boards[0][n][BOARD_WIDTH-2]++;
14849                 }
14850             } else
14851             boards[0][y][x] = selection;
14852             DrawPosition(TRUE, boards[0]);
14853             ClearHighlights();
14854             fromX = fromY = -1;
14855         }
14856         break;
14857     }
14858 }
14859
14860
14861 void
14862 DropMenuEvent (ChessSquare selection, int x, int y)
14863 {
14864     ChessMove moveType;
14865
14866     switch (gameMode) {
14867       case IcsPlayingWhite:
14868       case MachinePlaysBlack:
14869         if (!WhiteOnMove(currentMove)) {
14870             DisplayMoveError(_("It is Black's turn"));
14871             return;
14872         }
14873         moveType = WhiteDrop;
14874         break;
14875       case IcsPlayingBlack:
14876       case MachinePlaysWhite:
14877         if (WhiteOnMove(currentMove)) {
14878             DisplayMoveError(_("It is White's turn"));
14879             return;
14880         }
14881         moveType = BlackDrop;
14882         break;
14883       case EditGame:
14884         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
14885         break;
14886       default:
14887         return;
14888     }
14889
14890     if (moveType == BlackDrop && selection < BlackPawn) {
14891       selection = (ChessSquare) ((int) selection
14892                                  + (int) BlackPawn - (int) WhitePawn);
14893     }
14894     if (boards[currentMove][y][x] != EmptySquare) {
14895         DisplayMoveError(_("That square is occupied"));
14896         return;
14897     }
14898
14899     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
14900 }
14901
14902 void
14903 AcceptEvent ()
14904 {
14905     /* Accept a pending offer of any kind from opponent */
14906
14907     if (appData.icsActive) {
14908         SendToICS(ics_prefix);
14909         SendToICS("accept\n");
14910     } else if (cmailMsgLoaded) {
14911         if (currentMove == cmailOldMove &&
14912             commentList[cmailOldMove] != NULL &&
14913             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14914                    "Black offers a draw" : "White offers a draw")) {
14915             TruncateGame();
14916             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14917             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14918         } else {
14919             DisplayError(_("There is no pending offer on this move"), 0);
14920             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14921         }
14922     } else {
14923         /* Not used for offers from chess program */
14924     }
14925 }
14926
14927 void
14928 DeclineEvent ()
14929 {
14930     /* Decline a pending offer of any kind from opponent */
14931
14932     if (appData.icsActive) {
14933         SendToICS(ics_prefix);
14934         SendToICS("decline\n");
14935     } else if (cmailMsgLoaded) {
14936         if (currentMove == cmailOldMove &&
14937             commentList[cmailOldMove] != NULL &&
14938             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14939                    "Black offers a draw" : "White offers a draw")) {
14940 #ifdef NOTDEF
14941             AppendComment(cmailOldMove, "Draw declined", TRUE);
14942             DisplayComment(cmailOldMove - 1, "Draw declined");
14943 #endif /*NOTDEF*/
14944         } else {
14945             DisplayError(_("There is no pending offer on this move"), 0);
14946         }
14947     } else {
14948         /* Not used for offers from chess program */
14949     }
14950 }
14951
14952 void
14953 RematchEvent ()
14954 {
14955     /* Issue ICS rematch command */
14956     if (appData.icsActive) {
14957         SendToICS(ics_prefix);
14958         SendToICS("rematch\n");
14959     }
14960 }
14961
14962 void
14963 CallFlagEvent ()
14964 {
14965     /* Call your opponent's flag (claim a win on time) */
14966     if (appData.icsActive) {
14967         SendToICS(ics_prefix);
14968         SendToICS("flag\n");
14969     } else {
14970         switch (gameMode) {
14971           default:
14972             return;
14973           case MachinePlaysWhite:
14974             if (whiteFlag) {
14975                 if (blackFlag)
14976                   GameEnds(GameIsDrawn, "Both players ran out of time",
14977                            GE_PLAYER);
14978                 else
14979                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
14980             } else {
14981                 DisplayError(_("Your opponent is not out of time"), 0);
14982             }
14983             break;
14984           case MachinePlaysBlack:
14985             if (blackFlag) {
14986                 if (whiteFlag)
14987                   GameEnds(GameIsDrawn, "Both players ran out of time",
14988                            GE_PLAYER);
14989                 else
14990                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
14991             } else {
14992                 DisplayError(_("Your opponent is not out of time"), 0);
14993             }
14994             break;
14995         }
14996     }
14997 }
14998
14999 void
15000 ClockClick (int which)
15001 {       // [HGM] code moved to back-end from winboard.c
15002         if(which) { // black clock
15003           if (gameMode == EditPosition || gameMode == IcsExamining) {
15004             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15005             SetBlackToPlayEvent();
15006           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
15007           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
15008           } else if (shiftKey) {
15009             AdjustClock(which, -1);
15010           } else if (gameMode == IcsPlayingWhite ||
15011                      gameMode == MachinePlaysBlack) {
15012             CallFlagEvent();
15013           }
15014         } else { // white clock
15015           if (gameMode == EditPosition || gameMode == IcsExamining) {
15016             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15017             SetWhiteToPlayEvent();
15018           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
15019           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
15020           } else if (shiftKey) {
15021             AdjustClock(which, -1);
15022           } else if (gameMode == IcsPlayingBlack ||
15023                    gameMode == MachinePlaysWhite) {
15024             CallFlagEvent();
15025           }
15026         }
15027 }
15028
15029 void
15030 DrawEvent ()
15031 {
15032     /* Offer draw or accept pending draw offer from opponent */
15033
15034     if (appData.icsActive) {
15035         /* Note: tournament rules require draw offers to be
15036            made after you make your move but before you punch
15037            your clock.  Currently ICS doesn't let you do that;
15038            instead, you immediately punch your clock after making
15039            a move, but you can offer a draw at any time. */
15040
15041         SendToICS(ics_prefix);
15042         SendToICS("draw\n");
15043         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
15044     } else if (cmailMsgLoaded) {
15045         if (currentMove == cmailOldMove &&
15046             commentList[cmailOldMove] != NULL &&
15047             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15048                    "Black offers a draw" : "White offers a draw")) {
15049             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15050             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15051         } else if (currentMove == cmailOldMove + 1) {
15052             char *offer = WhiteOnMove(cmailOldMove) ?
15053               "White offers a draw" : "Black offers a draw";
15054             AppendComment(currentMove, offer, TRUE);
15055             DisplayComment(currentMove - 1, offer);
15056             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
15057         } else {
15058             DisplayError(_("You must make your move before offering a draw"), 0);
15059             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15060         }
15061     } else if (first.offeredDraw) {
15062         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
15063     } else {
15064         if (first.sendDrawOffers) {
15065             SendToProgram("draw\n", &first);
15066             userOfferedDraw = TRUE;
15067         }
15068     }
15069 }
15070
15071 void
15072 AdjournEvent ()
15073 {
15074     /* Offer Adjourn or accept pending Adjourn offer from opponent */
15075
15076     if (appData.icsActive) {
15077         SendToICS(ics_prefix);
15078         SendToICS("adjourn\n");
15079     } else {
15080         /* Currently GNU Chess doesn't offer or accept Adjourns */
15081     }
15082 }
15083
15084
15085 void
15086 AbortEvent ()
15087 {
15088     /* Offer Abort or accept pending Abort offer from opponent */
15089
15090     if (appData.icsActive) {
15091         SendToICS(ics_prefix);
15092         SendToICS("abort\n");
15093     } else {
15094         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
15095     }
15096 }
15097
15098 void
15099 ResignEvent ()
15100 {
15101     /* Resign.  You can do this even if it's not your turn. */
15102
15103     if (appData.icsActive) {
15104         SendToICS(ics_prefix);
15105         SendToICS("resign\n");
15106     } else {
15107         switch (gameMode) {
15108           case MachinePlaysWhite:
15109             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15110             break;
15111           case MachinePlaysBlack:
15112             GameEnds(BlackWins, "White resigns", GE_PLAYER);
15113             break;
15114           case EditGame:
15115             if (cmailMsgLoaded) {
15116                 TruncateGame();
15117                 if (WhiteOnMove(cmailOldMove)) {
15118                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
15119                 } else {
15120                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15121                 }
15122                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
15123             }
15124             break;
15125           default:
15126             break;
15127         }
15128     }
15129 }
15130
15131
15132 void
15133 StopObservingEvent ()
15134 {
15135     /* Stop observing current games */
15136     SendToICS(ics_prefix);
15137     SendToICS("unobserve\n");
15138 }
15139
15140 void
15141 StopExaminingEvent ()
15142 {
15143     /* Stop observing current game */
15144     SendToICS(ics_prefix);
15145     SendToICS("unexamine\n");
15146 }
15147
15148 void
15149 ForwardInner (int target)
15150 {
15151     int limit; int oldSeekGraphUp = seekGraphUp;
15152
15153     if (appData.debugMode)
15154         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
15155                 target, currentMove, forwardMostMove);
15156
15157     if (gameMode == EditPosition)
15158       return;
15159
15160     seekGraphUp = FALSE;
15161     MarkTargetSquares(1);
15162
15163     if (gameMode == PlayFromGameFile && !pausing)
15164       PauseEvent();
15165
15166     if (gameMode == IcsExamining && pausing)
15167       limit = pauseExamForwardMostMove;
15168     else
15169       limit = forwardMostMove;
15170
15171     if (target > limit) target = limit;
15172
15173     if (target > 0 && moveList[target - 1][0]) {
15174         int fromX, fromY, toX, toY;
15175         toX = moveList[target - 1][2] - AAA;
15176         toY = moveList[target - 1][3] - ONE;
15177         if (moveList[target - 1][1] == '@') {
15178             if (appData.highlightLastMove) {
15179                 SetHighlights(-1, -1, toX, toY);
15180             }
15181         } else {
15182             fromX = moveList[target - 1][0] - AAA;
15183             fromY = moveList[target - 1][1] - ONE;
15184             if (target == currentMove + 1) {
15185                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
15186             }
15187             if (appData.highlightLastMove) {
15188                 SetHighlights(fromX, fromY, toX, toY);
15189             }
15190         }
15191     }
15192     if (gameMode == EditGame || gameMode == AnalyzeMode ||
15193         gameMode == Training || gameMode == PlayFromGameFile ||
15194         gameMode == AnalyzeFile) {
15195         while (currentMove < target) {
15196             if(second.analyzing) SendMoveToProgram(currentMove, &second);
15197             SendMoveToProgram(currentMove++, &first);
15198         }
15199     } else {
15200         currentMove = target;
15201     }
15202
15203     if (gameMode == EditGame || gameMode == EndOfGame) {
15204         whiteTimeRemaining = timeRemaining[0][currentMove];
15205         blackTimeRemaining = timeRemaining[1][currentMove];
15206     }
15207     DisplayBothClocks();
15208     DisplayMove(currentMove - 1);
15209     DrawPosition(oldSeekGraphUp, boards[currentMove]);
15210     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15211     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
15212         DisplayComment(currentMove - 1, commentList[currentMove]);
15213     }
15214     ClearMap(); // [HGM] exclude: invalidate map
15215 }
15216
15217
15218 void
15219 ForwardEvent ()
15220 {
15221     if (gameMode == IcsExamining && !pausing) {
15222         SendToICS(ics_prefix);
15223         SendToICS("forward\n");
15224     } else {
15225         ForwardInner(currentMove + 1);
15226     }
15227 }
15228
15229 void
15230 ToEndEvent ()
15231 {
15232     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15233         /* to optimze, we temporarily turn off analysis mode while we feed
15234          * the remaining moves to the engine. Otherwise we get analysis output
15235          * after each move.
15236          */
15237         if (first.analysisSupport) {
15238           SendToProgram("exit\nforce\n", &first);
15239           first.analyzing = FALSE;
15240         }
15241     }
15242
15243     if (gameMode == IcsExamining && !pausing) {
15244         SendToICS(ics_prefix);
15245         SendToICS("forward 999999\n");
15246     } else {
15247         ForwardInner(forwardMostMove);
15248     }
15249
15250     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15251         /* we have fed all the moves, so reactivate analysis mode */
15252         SendToProgram("analyze\n", &first);
15253         first.analyzing = TRUE;
15254         /*first.maybeThinking = TRUE;*/
15255         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15256     }
15257 }
15258
15259 void
15260 BackwardInner (int target)
15261 {
15262     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
15263
15264     if (appData.debugMode)
15265         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
15266                 target, currentMove, forwardMostMove);
15267
15268     if (gameMode == EditPosition) return;
15269     seekGraphUp = FALSE;
15270     MarkTargetSquares(1);
15271     if (currentMove <= backwardMostMove) {
15272         ClearHighlights();
15273         DrawPosition(full_redraw, boards[currentMove]);
15274         return;
15275     }
15276     if (gameMode == PlayFromGameFile && !pausing)
15277       PauseEvent();
15278
15279     if (moveList[target][0]) {
15280         int fromX, fromY, toX, toY;
15281         toX = moveList[target][2] - AAA;
15282         toY = moveList[target][3] - ONE;
15283         if (moveList[target][1] == '@') {
15284             if (appData.highlightLastMove) {
15285                 SetHighlights(-1, -1, toX, toY);
15286             }
15287         } else {
15288             fromX = moveList[target][0] - AAA;
15289             fromY = moveList[target][1] - ONE;
15290             if (target == currentMove - 1) {
15291                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
15292             }
15293             if (appData.highlightLastMove) {
15294                 SetHighlights(fromX, fromY, toX, toY);
15295             }
15296         }
15297     }
15298     if (gameMode == EditGame || gameMode==AnalyzeMode ||
15299         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
15300         while (currentMove > target) {
15301             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
15302                 // null move cannot be undone. Reload program with move history before it.
15303                 int i;
15304                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
15305                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
15306                 }
15307                 SendBoard(&first, i);
15308               if(second.analyzing) SendBoard(&second, i);
15309                 for(currentMove=i; currentMove<target; currentMove++) {
15310                     SendMoveToProgram(currentMove, &first);
15311                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
15312                 }
15313                 break;
15314             }
15315             SendToBoth("undo\n");
15316             currentMove--;
15317         }
15318     } else {
15319         currentMove = target;
15320     }
15321
15322     if (gameMode == EditGame || gameMode == EndOfGame) {
15323         whiteTimeRemaining = timeRemaining[0][currentMove];
15324         blackTimeRemaining = timeRemaining[1][currentMove];
15325     }
15326     DisplayBothClocks();
15327     DisplayMove(currentMove - 1);
15328     DrawPosition(full_redraw, boards[currentMove]);
15329     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15330     // [HGM] PV info: routine tests if comment empty
15331     DisplayComment(currentMove - 1, commentList[currentMove]);
15332     ClearMap(); // [HGM] exclude: invalidate map
15333 }
15334
15335 void
15336 BackwardEvent ()
15337 {
15338     if (gameMode == IcsExamining && !pausing) {
15339         SendToICS(ics_prefix);
15340         SendToICS("backward\n");
15341     } else {
15342         BackwardInner(currentMove - 1);
15343     }
15344 }
15345
15346 void
15347 ToStartEvent ()
15348 {
15349     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15350         /* to optimize, we temporarily turn off analysis mode while we undo
15351          * all the moves. Otherwise we get analysis output after each undo.
15352          */
15353         if (first.analysisSupport) {
15354           SendToProgram("exit\nforce\n", &first);
15355           first.analyzing = FALSE;
15356         }
15357     }
15358
15359     if (gameMode == IcsExamining && !pausing) {
15360         SendToICS(ics_prefix);
15361         SendToICS("backward 999999\n");
15362     } else {
15363         BackwardInner(backwardMostMove);
15364     }
15365
15366     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15367         /* we have fed all the moves, so reactivate analysis mode */
15368         SendToProgram("analyze\n", &first);
15369         first.analyzing = TRUE;
15370         /*first.maybeThinking = TRUE;*/
15371         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15372     }
15373 }
15374
15375 void
15376 ToNrEvent (int to)
15377 {
15378   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
15379   if (to >= forwardMostMove) to = forwardMostMove;
15380   if (to <= backwardMostMove) to = backwardMostMove;
15381   if (to < currentMove) {
15382     BackwardInner(to);
15383   } else {
15384     ForwardInner(to);
15385   }
15386 }
15387
15388 void
15389 RevertEvent (Boolean annotate)
15390 {
15391     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
15392         return;
15393     }
15394     if (gameMode != IcsExamining) {
15395         DisplayError(_("You are not examining a game"), 0);
15396         return;
15397     }
15398     if (pausing) {
15399         DisplayError(_("You can't revert while pausing"), 0);
15400         return;
15401     }
15402     SendToICS(ics_prefix);
15403     SendToICS("revert\n");
15404 }
15405
15406 void
15407 RetractMoveEvent ()
15408 {
15409     switch (gameMode) {
15410       case MachinePlaysWhite:
15411       case MachinePlaysBlack:
15412         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15413             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
15414             return;
15415         }
15416         if (forwardMostMove < 2) return;
15417         currentMove = forwardMostMove = forwardMostMove - 2;
15418         whiteTimeRemaining = timeRemaining[0][currentMove];
15419         blackTimeRemaining = timeRemaining[1][currentMove];
15420         DisplayBothClocks();
15421         DisplayMove(currentMove - 1);
15422         ClearHighlights();/*!! could figure this out*/
15423         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
15424         SendToProgram("remove\n", &first);
15425         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
15426         break;
15427
15428       case BeginningOfGame:
15429       default:
15430         break;
15431
15432       case IcsPlayingWhite:
15433       case IcsPlayingBlack:
15434         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
15435             SendToICS(ics_prefix);
15436             SendToICS("takeback 2\n");
15437         } else {
15438             SendToICS(ics_prefix);
15439             SendToICS("takeback 1\n");
15440         }
15441         break;
15442     }
15443 }
15444
15445 void
15446 MoveNowEvent ()
15447 {
15448     ChessProgramState *cps;
15449
15450     switch (gameMode) {
15451       case MachinePlaysWhite:
15452         if (!WhiteOnMove(forwardMostMove)) {
15453             DisplayError(_("It is your turn"), 0);
15454             return;
15455         }
15456         cps = &first;
15457         break;
15458       case MachinePlaysBlack:
15459         if (WhiteOnMove(forwardMostMove)) {
15460             DisplayError(_("It is your turn"), 0);
15461             return;
15462         }
15463         cps = &first;
15464         break;
15465       case TwoMachinesPlay:
15466         if (WhiteOnMove(forwardMostMove) ==
15467             (first.twoMachinesColor[0] == 'w')) {
15468             cps = &first;
15469         } else {
15470             cps = &second;
15471         }
15472         break;
15473       case BeginningOfGame:
15474       default:
15475         return;
15476     }
15477     SendToProgram("?\n", cps);
15478 }
15479
15480 void
15481 TruncateGameEvent ()
15482 {
15483     EditGameEvent();
15484     if (gameMode != EditGame) return;
15485     TruncateGame();
15486 }
15487
15488 void
15489 TruncateGame ()
15490 {
15491     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
15492     if (forwardMostMove > currentMove) {
15493         if (gameInfo.resultDetails != NULL) {
15494             free(gameInfo.resultDetails);
15495             gameInfo.resultDetails = NULL;
15496             gameInfo.result = GameUnfinished;
15497         }
15498         forwardMostMove = currentMove;
15499         HistorySet(parseList, backwardMostMove, forwardMostMove,
15500                    currentMove-1);
15501     }
15502 }
15503
15504 void
15505 HintEvent ()
15506 {
15507     if (appData.noChessProgram) return;
15508     switch (gameMode) {
15509       case MachinePlaysWhite:
15510         if (WhiteOnMove(forwardMostMove)) {
15511             DisplayError(_("Wait until your turn."), 0);
15512             return;
15513         }
15514         break;
15515       case BeginningOfGame:
15516       case MachinePlaysBlack:
15517         if (!WhiteOnMove(forwardMostMove)) {
15518             DisplayError(_("Wait until your turn."), 0);
15519             return;
15520         }
15521         break;
15522       default:
15523         DisplayError(_("No hint available"), 0);
15524         return;
15525     }
15526     SendToProgram("hint\n", &first);
15527     hintRequested = TRUE;
15528 }
15529
15530 void
15531 CreateBookEvent ()
15532 {
15533     ListGame * lg = (ListGame *) gameList.head;
15534     FILE *f, *g;
15535     int nItem;
15536     static int secondTime = FALSE;
15537
15538     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
15539         DisplayError(_("Game list not loaded or empty"), 0);
15540         return;
15541     }
15542
15543     if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
15544         fclose(g);
15545         secondTime++;
15546         DisplayNote(_("Book file exists! Try again for overwrite."));
15547         return;
15548     }
15549
15550     creatingBook = TRUE;
15551     secondTime = FALSE;
15552
15553     /* Get list size */
15554     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
15555         LoadGame(f, nItem, "", TRUE);
15556         AddGameToBook(TRUE);
15557         lg = (ListGame *) lg->node.succ;
15558     }
15559
15560     creatingBook = FALSE;
15561     FlushBook();
15562 }
15563
15564 void
15565 BookEvent ()
15566 {
15567     if (appData.noChessProgram) return;
15568     switch (gameMode) {
15569       case MachinePlaysWhite:
15570         if (WhiteOnMove(forwardMostMove)) {
15571             DisplayError(_("Wait until your turn."), 0);
15572             return;
15573         }
15574         break;
15575       case BeginningOfGame:
15576       case MachinePlaysBlack:
15577         if (!WhiteOnMove(forwardMostMove)) {
15578             DisplayError(_("Wait until your turn."), 0);
15579             return;
15580         }
15581         break;
15582       case EditPosition:
15583         EditPositionDone(TRUE);
15584         break;
15585       case TwoMachinesPlay:
15586         return;
15587       default:
15588         break;
15589     }
15590     SendToProgram("bk\n", &first);
15591     bookOutput[0] = NULLCHAR;
15592     bookRequested = TRUE;
15593 }
15594
15595 void
15596 AboutGameEvent ()
15597 {
15598     char *tags = PGNTags(&gameInfo);
15599     TagsPopUp(tags, CmailMsg());
15600     free(tags);
15601 }
15602
15603 /* end button procedures */
15604
15605 void
15606 PrintPosition (FILE *fp, int move)
15607 {
15608     int i, j;
15609
15610     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15611         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15612             char c = PieceToChar(boards[move][i][j]);
15613             fputc(c == 'x' ? '.' : c, fp);
15614             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
15615         }
15616     }
15617     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
15618       fprintf(fp, "white to play\n");
15619     else
15620       fprintf(fp, "black to play\n");
15621 }
15622
15623 void
15624 PrintOpponents (FILE *fp)
15625 {
15626     if (gameInfo.white != NULL) {
15627         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
15628     } else {
15629         fprintf(fp, "\n");
15630     }
15631 }
15632
15633 /* Find last component of program's own name, using some heuristics */
15634 void
15635 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
15636 {
15637     char *p, *q, c;
15638     int local = (strcmp(host, "localhost") == 0);
15639     while (!local && (p = strchr(prog, ';')) != NULL) {
15640         p++;
15641         while (*p == ' ') p++;
15642         prog = p;
15643     }
15644     if (*prog == '"' || *prog == '\'') {
15645         q = strchr(prog + 1, *prog);
15646     } else {
15647         q = strchr(prog, ' ');
15648     }
15649     if (q == NULL) q = prog + strlen(prog);
15650     p = q;
15651     while (p >= prog && *p != '/' && *p != '\\') p--;
15652     p++;
15653     if(p == prog && *p == '"') p++;
15654     c = *q; *q = 0;
15655     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
15656     memcpy(buf, p, q - p);
15657     buf[q - p] = NULLCHAR;
15658     if (!local) {
15659         strcat(buf, "@");
15660         strcat(buf, host);
15661     }
15662 }
15663
15664 char *
15665 TimeControlTagValue ()
15666 {
15667     char buf[MSG_SIZ];
15668     if (!appData.clockMode) {
15669       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
15670     } else if (movesPerSession > 0) {
15671       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
15672     } else if (timeIncrement == 0) {
15673       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
15674     } else {
15675       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
15676     }
15677     return StrSave(buf);
15678 }
15679
15680 void
15681 SetGameInfo ()
15682 {
15683     /* This routine is used only for certain modes */
15684     VariantClass v = gameInfo.variant;
15685     ChessMove r = GameUnfinished;
15686     char *p = NULL;
15687
15688     if(keepInfo) return;
15689
15690     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
15691         r = gameInfo.result;
15692         p = gameInfo.resultDetails;
15693         gameInfo.resultDetails = NULL;
15694     }
15695     ClearGameInfo(&gameInfo);
15696     gameInfo.variant = v;
15697
15698     switch (gameMode) {
15699       case MachinePlaysWhite:
15700         gameInfo.event = StrSave( appData.pgnEventHeader );
15701         gameInfo.site = StrSave(HostName());
15702         gameInfo.date = PGNDate();
15703         gameInfo.round = StrSave("-");
15704         gameInfo.white = StrSave(first.tidy);
15705         gameInfo.black = StrSave(UserName());
15706         gameInfo.timeControl = TimeControlTagValue();
15707         break;
15708
15709       case MachinePlaysBlack:
15710         gameInfo.event = StrSave( appData.pgnEventHeader );
15711         gameInfo.site = StrSave(HostName());
15712         gameInfo.date = PGNDate();
15713         gameInfo.round = StrSave("-");
15714         gameInfo.white = StrSave(UserName());
15715         gameInfo.black = StrSave(first.tidy);
15716         gameInfo.timeControl = TimeControlTagValue();
15717         break;
15718
15719       case TwoMachinesPlay:
15720         gameInfo.event = StrSave( appData.pgnEventHeader );
15721         gameInfo.site = StrSave(HostName());
15722         gameInfo.date = PGNDate();
15723         if (roundNr > 0) {
15724             char buf[MSG_SIZ];
15725             snprintf(buf, MSG_SIZ, "%d", roundNr);
15726             gameInfo.round = StrSave(buf);
15727         } else {
15728             gameInfo.round = StrSave("-");
15729         }
15730         if (first.twoMachinesColor[0] == 'w') {
15731             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15732             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15733         } else {
15734             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15735             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15736         }
15737         gameInfo.timeControl = TimeControlTagValue();
15738         break;
15739
15740       case EditGame:
15741         gameInfo.event = StrSave("Edited game");
15742         gameInfo.site = StrSave(HostName());
15743         gameInfo.date = PGNDate();
15744         gameInfo.round = StrSave("-");
15745         gameInfo.white = StrSave("-");
15746         gameInfo.black = StrSave("-");
15747         gameInfo.result = r;
15748         gameInfo.resultDetails = p;
15749         break;
15750
15751       case EditPosition:
15752         gameInfo.event = StrSave("Edited position");
15753         gameInfo.site = StrSave(HostName());
15754         gameInfo.date = PGNDate();
15755         gameInfo.round = StrSave("-");
15756         gameInfo.white = StrSave("-");
15757         gameInfo.black = StrSave("-");
15758         break;
15759
15760       case IcsPlayingWhite:
15761       case IcsPlayingBlack:
15762       case IcsObserving:
15763       case IcsExamining:
15764         break;
15765
15766       case PlayFromGameFile:
15767         gameInfo.event = StrSave("Game from non-PGN file");
15768         gameInfo.site = StrSave(HostName());
15769         gameInfo.date = PGNDate();
15770         gameInfo.round = StrSave("-");
15771         gameInfo.white = StrSave("?");
15772         gameInfo.black = StrSave("?");
15773         break;
15774
15775       default:
15776         break;
15777     }
15778 }
15779
15780 void
15781 ReplaceComment (int index, char *text)
15782 {
15783     int len;
15784     char *p;
15785     float score;
15786
15787     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
15788        pvInfoList[index-1].depth == len &&
15789        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
15790        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
15791     while (*text == '\n') text++;
15792     len = strlen(text);
15793     while (len > 0 && text[len - 1] == '\n') len--;
15794
15795     if (commentList[index] != NULL)
15796       free(commentList[index]);
15797
15798     if (len == 0) {
15799         commentList[index] = NULL;
15800         return;
15801     }
15802   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
15803       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
15804       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
15805     commentList[index] = (char *) malloc(len + 2);
15806     strncpy(commentList[index], text, len);
15807     commentList[index][len] = '\n';
15808     commentList[index][len + 1] = NULLCHAR;
15809   } else {
15810     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
15811     char *p;
15812     commentList[index] = (char *) malloc(len + 7);
15813     safeStrCpy(commentList[index], "{\n", 3);
15814     safeStrCpy(commentList[index]+2, text, len+1);
15815     commentList[index][len+2] = NULLCHAR;
15816     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
15817     strcat(commentList[index], "\n}\n");
15818   }
15819 }
15820
15821 void
15822 CrushCRs (char *text)
15823 {
15824   char *p = text;
15825   char *q = text;
15826   char ch;
15827
15828   do {
15829     ch = *p++;
15830     if (ch == '\r') continue;
15831     *q++ = ch;
15832   } while (ch != '\0');
15833 }
15834
15835 void
15836 AppendComment (int index, char *text, Boolean addBraces)
15837 /* addBraces  tells if we should add {} */
15838 {
15839     int oldlen, len;
15840     char *old;
15841
15842 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
15843     if(addBraces == 3) addBraces = 0; else // force appending literally
15844     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
15845
15846     CrushCRs(text);
15847     while (*text == '\n') text++;
15848     len = strlen(text);
15849     while (len > 0 && text[len - 1] == '\n') len--;
15850     text[len] = NULLCHAR;
15851
15852     if (len == 0) return;
15853
15854     if (commentList[index] != NULL) {
15855       Boolean addClosingBrace = addBraces;
15856         old = commentList[index];
15857         oldlen = strlen(old);
15858         while(commentList[index][oldlen-1] ==  '\n')
15859           commentList[index][--oldlen] = NULLCHAR;
15860         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
15861         safeStrCpy(commentList[index], old, oldlen + len + 6);
15862         free(old);
15863         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
15864         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
15865           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
15866           while (*text == '\n') { text++; len--; }
15867           commentList[index][--oldlen] = NULLCHAR;
15868       }
15869         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
15870         else          strcat(commentList[index], "\n");
15871         strcat(commentList[index], text);
15872         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
15873         else          strcat(commentList[index], "\n");
15874     } else {
15875         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
15876         if(addBraces)
15877           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
15878         else commentList[index][0] = NULLCHAR;
15879         strcat(commentList[index], text);
15880         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
15881         if(addBraces == TRUE) strcat(commentList[index], "}\n");
15882     }
15883 }
15884
15885 static char *
15886 FindStr (char * text, char * sub_text)
15887 {
15888     char * result = strstr( text, sub_text );
15889
15890     if( result != NULL ) {
15891         result += strlen( sub_text );
15892     }
15893
15894     return result;
15895 }
15896
15897 /* [AS] Try to extract PV info from PGN comment */
15898 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
15899 char *
15900 GetInfoFromComment (int index, char * text)
15901 {
15902     char * sep = text, *p;
15903
15904     if( text != NULL && index > 0 ) {
15905         int score = 0;
15906         int depth = 0;
15907         int time = -1, sec = 0, deci;
15908         char * s_eval = FindStr( text, "[%eval " );
15909         char * s_emt = FindStr( text, "[%emt " );
15910 #if 0
15911         if( s_eval != NULL || s_emt != NULL ) {
15912 #else
15913         if(0) { // [HGM] this code is not finished, and could actually be detrimental
15914 #endif
15915             /* New style */
15916             char delim;
15917
15918             if( s_eval != NULL ) {
15919                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
15920                     return text;
15921                 }
15922
15923                 if( delim != ']' ) {
15924                     return text;
15925                 }
15926             }
15927
15928             if( s_emt != NULL ) {
15929             }
15930                 return text;
15931         }
15932         else {
15933             /* We expect something like: [+|-]nnn.nn/dd */
15934             int score_lo = 0;
15935
15936             if(*text != '{') return text; // [HGM] braces: must be normal comment
15937
15938             sep = strchr( text, '/' );
15939             if( sep == NULL || sep < (text+4) ) {
15940                 return text;
15941             }
15942
15943             p = text;
15944             if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
15945             if(p[1] == '(') { // comment starts with PV
15946                p = strchr(p, ')'); // locate end of PV
15947                if(p == NULL || sep < p+5) return text;
15948                // at this point we have something like "{(.*) +0.23/6 ..."
15949                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
15950                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
15951                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
15952             }
15953             time = -1; sec = -1; deci = -1;
15954             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
15955                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
15956                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
15957                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
15958                 return text;
15959             }
15960
15961             if( score_lo < 0 || score_lo >= 100 ) {
15962                 return text;
15963             }
15964
15965             if(sec >= 0) time = 600*time + 10*sec; else
15966             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
15967
15968             score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
15969
15970             /* [HGM] PV time: now locate end of PV info */
15971             while( *++sep >= '0' && *sep <= '9'); // strip depth
15972             if(time >= 0)
15973             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
15974             if(sec >= 0)
15975             while( *++sep >= '0' && *sep <= '9'); // strip seconds
15976             if(deci >= 0)
15977             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
15978             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
15979         }
15980
15981         if( depth <= 0 ) {
15982             return text;
15983         }
15984
15985         if( time < 0 ) {
15986             time = -1;
15987         }
15988
15989         pvInfoList[index-1].depth = depth;
15990         pvInfoList[index-1].score = score;
15991         pvInfoList[index-1].time  = 10*time; // centi-sec
15992         if(*sep == '}') *sep = 0; else *--sep = '{';
15993         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
15994     }
15995     return sep;
15996 }
15997
15998 void
15999 SendToProgram (char *message, ChessProgramState *cps)
16000 {
16001     int count, outCount, error;
16002     char buf[MSG_SIZ];
16003
16004     if (cps->pr == NoProc) return;
16005     Attention(cps);
16006
16007     if (appData.debugMode) {
16008         TimeMark now;
16009         GetTimeMark(&now);
16010         fprintf(debugFP, "%ld >%-6s: %s",
16011                 SubtractTimeMarks(&now, &programStartTime),
16012                 cps->which, message);
16013         if(serverFP)
16014             fprintf(serverFP, "%ld >%-6s: %s",
16015                 SubtractTimeMarks(&now, &programStartTime),
16016                 cps->which, message), fflush(serverFP);
16017     }
16018
16019     count = strlen(message);
16020     outCount = OutputToProcess(cps->pr, message, count, &error);
16021     if (outCount < count && !exiting
16022                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
16023       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
16024       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
16025         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16026             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16027                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16028                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16029                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16030             } else {
16031                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16032                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16033                 gameInfo.result = res;
16034             }
16035             gameInfo.resultDetails = StrSave(buf);
16036         }
16037         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16038         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16039     }
16040 }
16041
16042 void
16043 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
16044 {
16045     char *end_str;
16046     char buf[MSG_SIZ];
16047     ChessProgramState *cps = (ChessProgramState *)closure;
16048
16049     if (isr != cps->isr) return; /* Killed intentionally */
16050     if (count <= 0) {
16051         if (count == 0) {
16052             RemoveInputSource(cps->isr);
16053             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
16054                     _(cps->which), cps->program);
16055             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
16056             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16057                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16058                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16059                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16060                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16061                 } else {
16062                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16063                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16064                     gameInfo.result = res;
16065                 }
16066                 gameInfo.resultDetails = StrSave(buf);
16067             }
16068             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16069             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
16070         } else {
16071             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
16072                     _(cps->which), cps->program);
16073             RemoveInputSource(cps->isr);
16074
16075             /* [AS] Program is misbehaving badly... kill it */
16076             if( count == -2 ) {
16077                 DestroyChildProcess( cps->pr, 9 );
16078                 cps->pr = NoProc;
16079             }
16080
16081             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16082         }
16083         return;
16084     }
16085
16086     if ((end_str = strchr(message, '\r')) != NULL)
16087       *end_str = NULLCHAR;
16088     if ((end_str = strchr(message, '\n')) != NULL)
16089       *end_str = NULLCHAR;
16090
16091     if (appData.debugMode) {
16092         TimeMark now; int print = 1;
16093         char *quote = ""; char c; int i;
16094
16095         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
16096                 char start = message[0];
16097                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
16098                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
16099                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
16100                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
16101                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
16102                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
16103                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
16104                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
16105                    sscanf(message, "hint: %c", &c)!=1 &&
16106                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
16107                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
16108                     print = (appData.engineComments >= 2);
16109                 }
16110                 message[0] = start; // restore original message
16111         }
16112         if(print) {
16113                 GetTimeMark(&now);
16114                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
16115                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16116                         quote,
16117                         message);
16118                 if(serverFP)
16119                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
16120                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16121                         quote,
16122                         message), fflush(serverFP);
16123         }
16124     }
16125
16126     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
16127     if (appData.icsEngineAnalyze) {
16128         if (strstr(message, "whisper") != NULL ||
16129              strstr(message, "kibitz") != NULL ||
16130             strstr(message, "tellics") != NULL) return;
16131     }
16132
16133     HandleMachineMove(message, cps);
16134 }
16135
16136
16137 void
16138 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
16139 {
16140     char buf[MSG_SIZ];
16141     int seconds;
16142
16143     if( timeControl_2 > 0 ) {
16144         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
16145             tc = timeControl_2;
16146         }
16147     }
16148     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
16149     inc /= cps->timeOdds;
16150     st  /= cps->timeOdds;
16151
16152     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
16153
16154     if (st > 0) {
16155       /* Set exact time per move, normally using st command */
16156       if (cps->stKludge) {
16157         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
16158         seconds = st % 60;
16159         if (seconds == 0) {
16160           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
16161         } else {
16162           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
16163         }
16164       } else {
16165         snprintf(buf, MSG_SIZ, "st %d\n", st);
16166       }
16167     } else {
16168       /* Set conventional or incremental time control, using level command */
16169       if (seconds == 0) {
16170         /* Note old gnuchess bug -- minutes:seconds used to not work.
16171            Fixed in later versions, but still avoid :seconds
16172            when seconds is 0. */
16173         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
16174       } else {
16175         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
16176                  seconds, inc/1000.);
16177       }
16178     }
16179     SendToProgram(buf, cps);
16180
16181     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
16182     /* Orthogonally, limit search to given depth */
16183     if (sd > 0) {
16184       if (cps->sdKludge) {
16185         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
16186       } else {
16187         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
16188       }
16189       SendToProgram(buf, cps);
16190     }
16191
16192     if(cps->nps >= 0) { /* [HGM] nps */
16193         if(cps->supportsNPS == FALSE)
16194           cps->nps = -1; // don't use if engine explicitly says not supported!
16195         else {
16196           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
16197           SendToProgram(buf, cps);
16198         }
16199     }
16200 }
16201
16202 ChessProgramState *
16203 WhitePlayer ()
16204 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
16205 {
16206     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
16207        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
16208         return &second;
16209     return &first;
16210 }
16211
16212 void
16213 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
16214 {
16215     char message[MSG_SIZ];
16216     long time, otime;
16217
16218     /* Note: this routine must be called when the clocks are stopped
16219        or when they have *just* been set or switched; otherwise
16220        it will be off by the time since the current tick started.
16221     */
16222     if (machineWhite) {
16223         time = whiteTimeRemaining / 10;
16224         otime = blackTimeRemaining / 10;
16225     } else {
16226         time = blackTimeRemaining / 10;
16227         otime = whiteTimeRemaining / 10;
16228     }
16229     /* [HGM] translate opponent's time by time-odds factor */
16230     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
16231
16232     if (time <= 0) time = 1;
16233     if (otime <= 0) otime = 1;
16234
16235     snprintf(message, MSG_SIZ, "time %ld\n", time);
16236     SendToProgram(message, cps);
16237
16238     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
16239     SendToProgram(message, cps);
16240 }
16241
16242 char *
16243 EngineDefinedVariant (ChessProgramState *cps, int n)
16244 {   // return name of n-th unknown variant that engine supports
16245     static char buf[MSG_SIZ];
16246     char *p, *s = cps->variants;
16247     if(!s) return NULL;
16248     do { // parse string from variants feature
16249       VariantClass v;
16250         p = strchr(s, ',');
16251         if(p) *p = NULLCHAR;
16252       v = StringToVariant(s);
16253       if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
16254         if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
16255             if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
16256         }
16257         if(p) *p++ = ',';
16258         if(n < 0) return buf;
16259     } while(s = p);
16260     return NULL;
16261 }
16262
16263 int
16264 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16265 {
16266   char buf[MSG_SIZ];
16267   int len = strlen(name);
16268   int val;
16269
16270   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16271     (*p) += len + 1;
16272     sscanf(*p, "%d", &val);
16273     *loc = (val != 0);
16274     while (**p && **p != ' ')
16275       (*p)++;
16276     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16277     SendToProgram(buf, cps);
16278     return TRUE;
16279   }
16280   return FALSE;
16281 }
16282
16283 int
16284 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16285 {
16286   char buf[MSG_SIZ];
16287   int len = strlen(name);
16288   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16289     (*p) += len + 1;
16290     sscanf(*p, "%d", loc);
16291     while (**p && **p != ' ') (*p)++;
16292     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16293     SendToProgram(buf, cps);
16294     return TRUE;
16295   }
16296   return FALSE;
16297 }
16298
16299 int
16300 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
16301 {
16302   char buf[MSG_SIZ];
16303   int len = strlen(name);
16304   if (strncmp((*p), name, len) == 0
16305       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
16306     (*p) += len + 2;
16307     ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
16308     sscanf(*p, "%[^\"]", *loc);
16309     while (**p && **p != '\"') (*p)++;
16310     if (**p == '\"') (*p)++;
16311     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16312     SendToProgram(buf, cps);
16313     return TRUE;
16314   }
16315   return FALSE;
16316 }
16317
16318 int
16319 ParseOption (Option *opt, ChessProgramState *cps)
16320 // [HGM] options: process the string that defines an engine option, and determine
16321 // name, type, default value, and allowed value range
16322 {
16323         char *p, *q, buf[MSG_SIZ];
16324         int n, min = (-1)<<31, max = 1<<31, def;
16325
16326         if(p = strstr(opt->name, " -spin ")) {
16327             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16328             if(max < min) max = min; // enforce consistency
16329             if(def < min) def = min;
16330             if(def > max) def = max;
16331             opt->value = def;
16332             opt->min = min;
16333             opt->max = max;
16334             opt->type = Spin;
16335         } else if((p = strstr(opt->name, " -slider "))) {
16336             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
16337             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16338             if(max < min) max = min; // enforce consistency
16339             if(def < min) def = min;
16340             if(def > max) def = max;
16341             opt->value = def;
16342             opt->min = min;
16343             opt->max = max;
16344             opt->type = Spin; // Slider;
16345         } else if((p = strstr(opt->name, " -string "))) {
16346             opt->textValue = p+9;
16347             opt->type = TextBox;
16348         } else if((p = strstr(opt->name, " -file "))) {
16349             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16350             opt->textValue = p+7;
16351             opt->type = FileName; // FileName;
16352         } else if((p = strstr(opt->name, " -path "))) {
16353             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16354             opt->textValue = p+7;
16355             opt->type = PathName; // PathName;
16356         } else if(p = strstr(opt->name, " -check ")) {
16357             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
16358             opt->value = (def != 0);
16359             opt->type = CheckBox;
16360         } else if(p = strstr(opt->name, " -combo ")) {
16361             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
16362             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
16363             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
16364             opt->value = n = 0;
16365             while(q = StrStr(q, " /// ")) {
16366                 n++; *q = 0;    // count choices, and null-terminate each of them
16367                 q += 5;
16368                 if(*q == '*') { // remember default, which is marked with * prefix
16369                     q++;
16370                     opt->value = n;
16371                 }
16372                 cps->comboList[cps->comboCnt++] = q;
16373             }
16374             cps->comboList[cps->comboCnt++] = NULL;
16375             opt->max = n + 1;
16376             opt->type = ComboBox;
16377         } else if(p = strstr(opt->name, " -button")) {
16378             opt->type = Button;
16379         } else if(p = strstr(opt->name, " -save")) {
16380             opt->type = SaveButton;
16381         } else return FALSE;
16382         *p = 0; // terminate option name
16383         // now look if the command-line options define a setting for this engine option.
16384         if(cps->optionSettings && cps->optionSettings[0])
16385             p = strstr(cps->optionSettings, opt->name); else p = NULL;
16386         if(p && (p == cps->optionSettings || p[-1] == ',')) {
16387           snprintf(buf, MSG_SIZ, "option %s", p);
16388                 if(p = strstr(buf, ",")) *p = 0;
16389                 if(q = strchr(buf, '=')) switch(opt->type) {
16390                     case ComboBox:
16391                         for(n=0; n<opt->max; n++)
16392                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
16393                         break;
16394                     case TextBox:
16395                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
16396                         break;
16397                     case Spin:
16398                     case CheckBox:
16399                         opt->value = atoi(q+1);
16400                     default:
16401                         break;
16402                 }
16403                 strcat(buf, "\n");
16404                 SendToProgram(buf, cps);
16405         }
16406         return TRUE;
16407 }
16408
16409 void
16410 FeatureDone (ChessProgramState *cps, int val)
16411 {
16412   DelayedEventCallback cb = GetDelayedEvent();
16413   if ((cb == InitBackEnd3 && cps == &first) ||
16414       (cb == SettingsMenuIfReady && cps == &second) ||
16415       (cb == LoadEngine) ||
16416       (cb == TwoMachinesEventIfReady)) {
16417     CancelDelayedEvent();
16418     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
16419   }
16420   cps->initDone = val;
16421   if(val) cps->reload = FALSE;
16422 }
16423
16424 /* Parse feature command from engine */
16425 void
16426 ParseFeatures (char *args, ChessProgramState *cps)
16427 {
16428   char *p = args;
16429   char *q = NULL;
16430   int val;
16431   char buf[MSG_SIZ];
16432
16433   for (;;) {
16434     while (*p == ' ') p++;
16435     if (*p == NULLCHAR) return;
16436
16437     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
16438     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
16439     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
16440     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
16441     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
16442     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
16443     if (BoolFeature(&p, "reuse", &val, cps)) {
16444       /* Engine can disable reuse, but can't enable it if user said no */
16445       if (!val) cps->reuse = FALSE;
16446       continue;
16447     }
16448     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
16449     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
16450       if (gameMode == TwoMachinesPlay) {
16451         DisplayTwoMachinesTitle();
16452       } else {
16453         DisplayTitle("");
16454       }
16455       continue;
16456     }
16457     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
16458     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
16459     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
16460     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
16461     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
16462     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
16463     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
16464     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
16465     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
16466     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
16467     if (IntFeature(&p, "done", &val, cps)) {
16468       FeatureDone(cps, val);
16469       continue;
16470     }
16471     /* Added by Tord: */
16472     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
16473     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
16474     /* End of additions by Tord */
16475
16476     /* [HGM] added features: */
16477     if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
16478     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
16479     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
16480     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
16481     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
16482     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
16483     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
16484     if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
16485         if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
16486         FREE(cps->option[cps->nrOptions].name);
16487         cps->option[cps->nrOptions].name = q; q = NULL;
16488         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
16489           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
16490             SendToProgram(buf, cps);
16491             continue;
16492         }
16493         if(cps->nrOptions >= MAX_OPTIONS) {
16494             cps->nrOptions--;
16495             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
16496             DisplayError(buf, 0);
16497         }
16498         continue;
16499     }
16500     /* End of additions by HGM */
16501
16502     /* unknown feature: complain and skip */
16503     q = p;
16504     while (*q && *q != '=') q++;
16505     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
16506     SendToProgram(buf, cps);
16507     p = q;
16508     if (*p == '=') {
16509       p++;
16510       if (*p == '\"') {
16511         p++;
16512         while (*p && *p != '\"') p++;
16513         if (*p == '\"') p++;
16514       } else {
16515         while (*p && *p != ' ') p++;
16516       }
16517     }
16518   }
16519
16520 }
16521
16522 void
16523 PeriodicUpdatesEvent (int newState)
16524 {
16525     if (newState == appData.periodicUpdates)
16526       return;
16527
16528     appData.periodicUpdates=newState;
16529
16530     /* Display type changes, so update it now */
16531 //    DisplayAnalysis();
16532
16533     /* Get the ball rolling again... */
16534     if (newState) {
16535         AnalysisPeriodicEvent(1);
16536         StartAnalysisClock();
16537     }
16538 }
16539
16540 void
16541 PonderNextMoveEvent (int newState)
16542 {
16543     if (newState == appData.ponderNextMove) return;
16544     if (gameMode == EditPosition) EditPositionDone(TRUE);
16545     if (newState) {
16546         SendToProgram("hard\n", &first);
16547         if (gameMode == TwoMachinesPlay) {
16548             SendToProgram("hard\n", &second);
16549         }
16550     } else {
16551         SendToProgram("easy\n", &first);
16552         thinkOutput[0] = NULLCHAR;
16553         if (gameMode == TwoMachinesPlay) {
16554             SendToProgram("easy\n", &second);
16555         }
16556     }
16557     appData.ponderNextMove = newState;
16558 }
16559
16560 void
16561 NewSettingEvent (int option, int *feature, char *command, int value)
16562 {
16563     char buf[MSG_SIZ];
16564
16565     if (gameMode == EditPosition) EditPositionDone(TRUE);
16566     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
16567     if(feature == NULL || *feature) SendToProgram(buf, &first);
16568     if (gameMode == TwoMachinesPlay) {
16569         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
16570     }
16571 }
16572
16573 void
16574 ShowThinkingEvent ()
16575 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
16576 {
16577     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
16578     int newState = appData.showThinking
16579         // [HGM] thinking: other features now need thinking output as well
16580         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
16581
16582     if (oldState == newState) return;
16583     oldState = newState;
16584     if (gameMode == EditPosition) EditPositionDone(TRUE);
16585     if (oldState) {
16586         SendToProgram("post\n", &first);
16587         if (gameMode == TwoMachinesPlay) {
16588             SendToProgram("post\n", &second);
16589         }
16590     } else {
16591         SendToProgram("nopost\n", &first);
16592         thinkOutput[0] = NULLCHAR;
16593         if (gameMode == TwoMachinesPlay) {
16594             SendToProgram("nopost\n", &second);
16595         }
16596     }
16597 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
16598 }
16599
16600 void
16601 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
16602 {
16603   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
16604   if (pr == NoProc) return;
16605   AskQuestion(title, question, replyPrefix, pr);
16606 }
16607
16608 void
16609 TypeInEvent (char firstChar)
16610 {
16611     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
16612         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
16613         gameMode == AnalyzeMode || gameMode == EditGame ||
16614         gameMode == EditPosition || gameMode == IcsExamining ||
16615         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
16616         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
16617                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
16618                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
16619         gameMode == Training) PopUpMoveDialog(firstChar);
16620 }
16621
16622 void
16623 TypeInDoneEvent (char *move)
16624 {
16625         Board board;
16626         int n, fromX, fromY, toX, toY;
16627         char promoChar;
16628         ChessMove moveType;
16629
16630         // [HGM] FENedit
16631         if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
16632                 EditPositionPasteFEN(move);
16633                 return;
16634         }
16635         // [HGM] movenum: allow move number to be typed in any mode
16636         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
16637           ToNrEvent(2*n-1);
16638           return;
16639         }
16640         // undocumented kludge: allow command-line option to be typed in!
16641         // (potentially fatal, and does not implement the effect of the option.)
16642         // should only be used for options that are values on which future decisions will be made,
16643         // and definitely not on options that would be used during initialization.
16644         if(strstr(move, "!!! -") == move) {
16645             ParseArgsFromString(move+4);
16646             return;
16647         }
16648
16649       if (gameMode != EditGame && currentMove != forwardMostMove &&
16650         gameMode != Training) {
16651         DisplayMoveError(_("Displayed move is not current"));
16652       } else {
16653         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16654           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
16655         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
16656         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16657           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
16658           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
16659         } else {
16660           DisplayMoveError(_("Could not parse move"));
16661         }
16662       }
16663 }
16664
16665 void
16666 DisplayMove (int moveNumber)
16667 {
16668     char message[MSG_SIZ];
16669     char res[MSG_SIZ];
16670     char cpThinkOutput[MSG_SIZ];
16671
16672     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
16673
16674     if (moveNumber == forwardMostMove - 1 ||
16675         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16676
16677         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
16678
16679         if (strchr(cpThinkOutput, '\n')) {
16680             *strchr(cpThinkOutput, '\n') = NULLCHAR;
16681         }
16682     } else {
16683         *cpThinkOutput = NULLCHAR;
16684     }
16685
16686     /* [AS] Hide thinking from human user */
16687     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
16688         *cpThinkOutput = NULLCHAR;
16689         if( thinkOutput[0] != NULLCHAR ) {
16690             int i;
16691
16692             for( i=0; i<=hiddenThinkOutputState; i++ ) {
16693                 cpThinkOutput[i] = '.';
16694             }
16695             cpThinkOutput[i] = NULLCHAR;
16696             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
16697         }
16698     }
16699
16700     if (moveNumber == forwardMostMove - 1 &&
16701         gameInfo.resultDetails != NULL) {
16702         if (gameInfo.resultDetails[0] == NULLCHAR) {
16703           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
16704         } else {
16705           snprintf(res, MSG_SIZ, " {%s} %s",
16706                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
16707         }
16708     } else {
16709         res[0] = NULLCHAR;
16710     }
16711
16712     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16713         DisplayMessage(res, cpThinkOutput);
16714     } else {
16715       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
16716                 WhiteOnMove(moveNumber) ? " " : ".. ",
16717                 parseList[moveNumber], res);
16718         DisplayMessage(message, cpThinkOutput);
16719     }
16720 }
16721
16722 void
16723 DisplayComment (int moveNumber, char *text)
16724 {
16725     char title[MSG_SIZ];
16726
16727     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16728       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
16729     } else {
16730       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
16731               WhiteOnMove(moveNumber) ? " " : ".. ",
16732               parseList[moveNumber]);
16733     }
16734     if (text != NULL && (appData.autoDisplayComment || commentUp))
16735         CommentPopUp(title, text);
16736 }
16737
16738 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
16739  * might be busy thinking or pondering.  It can be omitted if your
16740  * gnuchess is configured to stop thinking immediately on any user
16741  * input.  However, that gnuchess feature depends on the FIONREAD
16742  * ioctl, which does not work properly on some flavors of Unix.
16743  */
16744 void
16745 Attention (ChessProgramState *cps)
16746 {
16747 #if ATTENTION
16748     if (!cps->useSigint) return;
16749     if (appData.noChessProgram || (cps->pr == NoProc)) return;
16750     switch (gameMode) {
16751       case MachinePlaysWhite:
16752       case MachinePlaysBlack:
16753       case TwoMachinesPlay:
16754       case IcsPlayingWhite:
16755       case IcsPlayingBlack:
16756       case AnalyzeMode:
16757       case AnalyzeFile:
16758         /* Skip if we know it isn't thinking */
16759         if (!cps->maybeThinking) return;
16760         if (appData.debugMode)
16761           fprintf(debugFP, "Interrupting %s\n", cps->which);
16762         InterruptChildProcess(cps->pr);
16763         cps->maybeThinking = FALSE;
16764         break;
16765       default:
16766         break;
16767     }
16768 #endif /*ATTENTION*/
16769 }
16770
16771 int
16772 CheckFlags ()
16773 {
16774     if (whiteTimeRemaining <= 0) {
16775         if (!whiteFlag) {
16776             whiteFlag = TRUE;
16777             if (appData.icsActive) {
16778                 if (appData.autoCallFlag &&
16779                     gameMode == IcsPlayingBlack && !blackFlag) {
16780                   SendToICS(ics_prefix);
16781                   SendToICS("flag\n");
16782                 }
16783             } else {
16784                 if (blackFlag) {
16785                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16786                 } else {
16787                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
16788                     if (appData.autoCallFlag) {
16789                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
16790                         return TRUE;
16791                     }
16792                 }
16793             }
16794         }
16795     }
16796     if (blackTimeRemaining <= 0) {
16797         if (!blackFlag) {
16798             blackFlag = TRUE;
16799             if (appData.icsActive) {
16800                 if (appData.autoCallFlag &&
16801                     gameMode == IcsPlayingWhite && !whiteFlag) {
16802                   SendToICS(ics_prefix);
16803                   SendToICS("flag\n");
16804                 }
16805             } else {
16806                 if (whiteFlag) {
16807                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16808                 } else {
16809                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
16810                     if (appData.autoCallFlag) {
16811                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
16812                         return TRUE;
16813                     }
16814                 }
16815             }
16816         }
16817     }
16818     return FALSE;
16819 }
16820
16821 void
16822 CheckTimeControl ()
16823 {
16824     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
16825         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
16826
16827     /*
16828      * add time to clocks when time control is achieved ([HGM] now also used for increment)
16829      */
16830     if ( !WhiteOnMove(forwardMostMove) ) {
16831         /* White made time control */
16832         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
16833         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
16834         /* [HGM] time odds: correct new time quota for time odds! */
16835                                             / WhitePlayer()->timeOdds;
16836         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
16837     } else {
16838         lastBlack -= blackTimeRemaining;
16839         /* Black made time control */
16840         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
16841                                             / WhitePlayer()->other->timeOdds;
16842         lastWhite = whiteTimeRemaining;
16843     }
16844 }
16845
16846 void
16847 DisplayBothClocks ()
16848 {
16849     int wom = gameMode == EditPosition ?
16850       !blackPlaysFirst : WhiteOnMove(currentMove);
16851     DisplayWhiteClock(whiteTimeRemaining, wom);
16852     DisplayBlackClock(blackTimeRemaining, !wom);
16853 }
16854
16855
16856 /* Timekeeping seems to be a portability nightmare.  I think everyone
16857    has ftime(), but I'm really not sure, so I'm including some ifdefs
16858    to use other calls if you don't.  Clocks will be less accurate if
16859    you have neither ftime nor gettimeofday.
16860 */
16861
16862 /* VS 2008 requires the #include outside of the function */
16863 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
16864 #include <sys/timeb.h>
16865 #endif
16866
16867 /* Get the current time as a TimeMark */
16868 void
16869 GetTimeMark (TimeMark *tm)
16870 {
16871 #if HAVE_GETTIMEOFDAY
16872
16873     struct timeval timeVal;
16874     struct timezone timeZone;
16875
16876     gettimeofday(&timeVal, &timeZone);
16877     tm->sec = (long) timeVal.tv_sec;
16878     tm->ms = (int) (timeVal.tv_usec / 1000L);
16879
16880 #else /*!HAVE_GETTIMEOFDAY*/
16881 #if HAVE_FTIME
16882
16883 // include <sys/timeb.h> / moved to just above start of function
16884     struct timeb timeB;
16885
16886     ftime(&timeB);
16887     tm->sec = (long) timeB.time;
16888     tm->ms = (int) timeB.millitm;
16889
16890 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
16891     tm->sec = (long) time(NULL);
16892     tm->ms = 0;
16893 #endif
16894 #endif
16895 }
16896
16897 /* Return the difference in milliseconds between two
16898    time marks.  We assume the difference will fit in a long!
16899 */
16900 long
16901 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
16902 {
16903     return 1000L*(tm2->sec - tm1->sec) +
16904            (long) (tm2->ms - tm1->ms);
16905 }
16906
16907
16908 /*
16909  * Code to manage the game clocks.
16910  *
16911  * In tournament play, black starts the clock and then white makes a move.
16912  * We give the human user a slight advantage if he is playing white---the
16913  * clocks don't run until he makes his first move, so it takes zero time.
16914  * Also, we don't account for network lag, so we could get out of sync
16915  * with GNU Chess's clock -- but then, referees are always right.
16916  */
16917
16918 static TimeMark tickStartTM;
16919 static long intendedTickLength;
16920
16921 long
16922 NextTickLength (long timeRemaining)
16923 {
16924     long nominalTickLength, nextTickLength;
16925
16926     if (timeRemaining > 0L && timeRemaining <= 10000L)
16927       nominalTickLength = 100L;
16928     else
16929       nominalTickLength = 1000L;
16930     nextTickLength = timeRemaining % nominalTickLength;
16931     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
16932
16933     return nextTickLength;
16934 }
16935
16936 /* Adjust clock one minute up or down */
16937 void
16938 AdjustClock (Boolean which, int dir)
16939 {
16940     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
16941     if(which) blackTimeRemaining += 60000*dir;
16942     else      whiteTimeRemaining += 60000*dir;
16943     DisplayBothClocks();
16944     adjustedClock = TRUE;
16945 }
16946
16947 /* Stop clocks and reset to a fresh time control */
16948 void
16949 ResetClocks ()
16950 {
16951     (void) StopClockTimer();
16952     if (appData.icsActive) {
16953         whiteTimeRemaining = blackTimeRemaining = 0;
16954     } else if (searchTime) {
16955         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16956         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16957     } else { /* [HGM] correct new time quote for time odds */
16958         whiteTC = blackTC = fullTimeControlString;
16959         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
16960         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
16961     }
16962     if (whiteFlag || blackFlag) {
16963         DisplayTitle("");
16964         whiteFlag = blackFlag = FALSE;
16965     }
16966     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
16967     DisplayBothClocks();
16968     adjustedClock = FALSE;
16969 }
16970
16971 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
16972
16973 /* Decrement running clock by amount of time that has passed */
16974 void
16975 DecrementClocks ()
16976 {
16977     long timeRemaining;
16978     long lastTickLength, fudge;
16979     TimeMark now;
16980
16981     if (!appData.clockMode) return;
16982     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
16983
16984     GetTimeMark(&now);
16985
16986     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16987
16988     /* Fudge if we woke up a little too soon */
16989     fudge = intendedTickLength - lastTickLength;
16990     if (fudge < 0 || fudge > FUDGE) fudge = 0;
16991
16992     if (WhiteOnMove(forwardMostMove)) {
16993         if(whiteNPS >= 0) lastTickLength = 0;
16994         timeRemaining = whiteTimeRemaining -= lastTickLength;
16995         if(timeRemaining < 0 && !appData.icsActive) {
16996             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
16997             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
16998                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
16999                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
17000             }
17001         }
17002         DisplayWhiteClock(whiteTimeRemaining - fudge,
17003                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17004     } else {
17005         if(blackNPS >= 0) lastTickLength = 0;
17006         timeRemaining = blackTimeRemaining -= lastTickLength;
17007         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
17008             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
17009             if(suddenDeath) {
17010                 blackStartMove = forwardMostMove;
17011                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
17012             }
17013         }
17014         DisplayBlackClock(blackTimeRemaining - fudge,
17015                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17016     }
17017     if (CheckFlags()) return;
17018
17019     if(twoBoards) { // count down secondary board's clocks as well
17020         activePartnerTime -= lastTickLength;
17021         partnerUp = 1;
17022         if(activePartner == 'W')
17023             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
17024         else
17025             DisplayBlackClock(activePartnerTime, TRUE);
17026         partnerUp = 0;
17027     }
17028
17029     tickStartTM = now;
17030     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
17031     StartClockTimer(intendedTickLength);
17032
17033     /* if the time remaining has fallen below the alarm threshold, sound the
17034      * alarm. if the alarm has sounded and (due to a takeback or time control
17035      * with increment) the time remaining has increased to a level above the
17036      * threshold, reset the alarm so it can sound again.
17037      */
17038
17039     if (appData.icsActive && appData.icsAlarm) {
17040
17041         /* make sure we are dealing with the user's clock */
17042         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
17043                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
17044            )) return;
17045
17046         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
17047             alarmSounded = FALSE;
17048         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
17049             PlayAlarmSound();
17050             alarmSounded = TRUE;
17051         }
17052     }
17053 }
17054
17055
17056 /* A player has just moved, so stop the previously running
17057    clock and (if in clock mode) start the other one.
17058    We redisplay both clocks in case we're in ICS mode, because
17059    ICS gives us an update to both clocks after every move.
17060    Note that this routine is called *after* forwardMostMove
17061    is updated, so the last fractional tick must be subtracted
17062    from the color that is *not* on move now.
17063 */
17064 void
17065 SwitchClocks (int newMoveNr)
17066 {
17067     long lastTickLength;
17068     TimeMark now;
17069     int flagged = FALSE;
17070
17071     GetTimeMark(&now);
17072
17073     if (StopClockTimer() && appData.clockMode) {
17074         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17075         if (!WhiteOnMove(forwardMostMove)) {
17076             if(blackNPS >= 0) lastTickLength = 0;
17077             blackTimeRemaining -= lastTickLength;
17078            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17079 //         if(pvInfoList[forwardMostMove].time == -1)
17080                  pvInfoList[forwardMostMove].time =               // use GUI time
17081                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
17082         } else {
17083            if(whiteNPS >= 0) lastTickLength = 0;
17084            whiteTimeRemaining -= lastTickLength;
17085            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17086 //         if(pvInfoList[forwardMostMove].time == -1)
17087                  pvInfoList[forwardMostMove].time =
17088                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
17089         }
17090         flagged = CheckFlags();
17091     }
17092     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
17093     CheckTimeControl();
17094
17095     if (flagged || !appData.clockMode) return;
17096
17097     switch (gameMode) {
17098       case MachinePlaysBlack:
17099       case MachinePlaysWhite:
17100       case BeginningOfGame:
17101         if (pausing) return;
17102         break;
17103
17104       case EditGame:
17105       case PlayFromGameFile:
17106       case IcsExamining:
17107         return;
17108
17109       default:
17110         break;
17111     }
17112
17113     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
17114         if(WhiteOnMove(forwardMostMove))
17115              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17116         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17117     }
17118
17119     tickStartTM = now;
17120     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17121       whiteTimeRemaining : blackTimeRemaining);
17122     StartClockTimer(intendedTickLength);
17123 }
17124
17125
17126 /* Stop both clocks */
17127 void
17128 StopClocks ()
17129 {
17130     long lastTickLength;
17131     TimeMark now;
17132
17133     if (!StopClockTimer()) return;
17134     if (!appData.clockMode) return;
17135
17136     GetTimeMark(&now);
17137
17138     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17139     if (WhiteOnMove(forwardMostMove)) {
17140         if(whiteNPS >= 0) lastTickLength = 0;
17141         whiteTimeRemaining -= lastTickLength;
17142         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
17143     } else {
17144         if(blackNPS >= 0) lastTickLength = 0;
17145         blackTimeRemaining -= lastTickLength;
17146         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
17147     }
17148     CheckFlags();
17149 }
17150
17151 /* Start clock of player on move.  Time may have been reset, so
17152    if clock is already running, stop and restart it. */
17153 void
17154 StartClocks ()
17155 {
17156     (void) StopClockTimer(); /* in case it was running already */
17157     DisplayBothClocks();
17158     if (CheckFlags()) return;
17159
17160     if (!appData.clockMode) return;
17161     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
17162
17163     GetTimeMark(&tickStartTM);
17164     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17165       whiteTimeRemaining : blackTimeRemaining);
17166
17167    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
17168     whiteNPS = blackNPS = -1;
17169     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
17170        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
17171         whiteNPS = first.nps;
17172     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
17173        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
17174         blackNPS = first.nps;
17175     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
17176         whiteNPS = second.nps;
17177     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
17178         blackNPS = second.nps;
17179     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
17180
17181     StartClockTimer(intendedTickLength);
17182 }
17183
17184 char *
17185 TimeString (long ms)
17186 {
17187     long second, minute, hour, day;
17188     char *sign = "";
17189     static char buf[32];
17190
17191     if (ms > 0 && ms <= 9900) {
17192       /* convert milliseconds to tenths, rounding up */
17193       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
17194
17195       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
17196       return buf;
17197     }
17198
17199     /* convert milliseconds to seconds, rounding up */
17200     /* use floating point to avoid strangeness of integer division
17201        with negative dividends on many machines */
17202     second = (long) floor(((double) (ms + 999L)) / 1000.0);
17203
17204     if (second < 0) {
17205         sign = "-";
17206         second = -second;
17207     }
17208
17209     day = second / (60 * 60 * 24);
17210     second = second % (60 * 60 * 24);
17211     hour = second / (60 * 60);
17212     second = second % (60 * 60);
17213     minute = second / 60;
17214     second = second % 60;
17215
17216     if (day > 0)
17217       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
17218               sign, day, hour, minute, second);
17219     else if (hour > 0)
17220       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
17221     else
17222       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
17223
17224     return buf;
17225 }
17226
17227
17228 /*
17229  * This is necessary because some C libraries aren't ANSI C compliant yet.
17230  */
17231 char *
17232 StrStr (char *string, char *match)
17233 {
17234     int i, length;
17235
17236     length = strlen(match);
17237
17238     for (i = strlen(string) - length; i >= 0; i--, string++)
17239       if (!strncmp(match, string, length))
17240         return string;
17241
17242     return NULL;
17243 }
17244
17245 char *
17246 StrCaseStr (char *string, char *match)
17247 {
17248     int i, j, length;
17249
17250     length = strlen(match);
17251
17252     for (i = strlen(string) - length; i >= 0; i--, string++) {
17253         for (j = 0; j < length; j++) {
17254             if (ToLower(match[j]) != ToLower(string[j]))
17255               break;
17256         }
17257         if (j == length) return string;
17258     }
17259
17260     return NULL;
17261 }
17262
17263 #ifndef _amigados
17264 int
17265 StrCaseCmp (char *s1, char *s2)
17266 {
17267     char c1, c2;
17268
17269     for (;;) {
17270         c1 = ToLower(*s1++);
17271         c2 = ToLower(*s2++);
17272         if (c1 > c2) return 1;
17273         if (c1 < c2) return -1;
17274         if (c1 == NULLCHAR) return 0;
17275     }
17276 }
17277
17278
17279 int
17280 ToLower (int c)
17281 {
17282     return isupper(c) ? tolower(c) : c;
17283 }
17284
17285
17286 int
17287 ToUpper (int c)
17288 {
17289     return islower(c) ? toupper(c) : c;
17290 }
17291 #endif /* !_amigados    */
17292
17293 char *
17294 StrSave (char *s)
17295 {
17296   char *ret;
17297
17298   if ((ret = (char *) malloc(strlen(s) + 1)))
17299     {
17300       safeStrCpy(ret, s, strlen(s)+1);
17301     }
17302   return ret;
17303 }
17304
17305 char *
17306 StrSavePtr (char *s, char **savePtr)
17307 {
17308     if (*savePtr) {
17309         free(*savePtr);
17310     }
17311     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
17312       safeStrCpy(*savePtr, s, strlen(s)+1);
17313     }
17314     return(*savePtr);
17315 }
17316
17317 char *
17318 PGNDate ()
17319 {
17320     time_t clock;
17321     struct tm *tm;
17322     char buf[MSG_SIZ];
17323
17324     clock = time((time_t *)NULL);
17325     tm = localtime(&clock);
17326     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
17327             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
17328     return StrSave(buf);
17329 }
17330
17331
17332 char *
17333 PositionToFEN (int move, char *overrideCastling, int moveCounts)
17334 {
17335     int i, j, fromX, fromY, toX, toY;
17336     int whiteToPlay;
17337     char buf[MSG_SIZ];
17338     char *p, *q;
17339     int emptycount;
17340     ChessSquare piece;
17341
17342     whiteToPlay = (gameMode == EditPosition) ?
17343       !blackPlaysFirst : (move % 2 == 0);
17344     p = buf;
17345
17346     /* Piece placement data */
17347     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17348         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
17349         emptycount = 0;
17350         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
17351             if (boards[move][i][j] == EmptySquare) {
17352                 emptycount++;
17353             } else { ChessSquare piece = boards[move][i][j];
17354                 if (emptycount > 0) {
17355                     if(emptycount<10) /* [HGM] can be >= 10 */
17356                         *p++ = '0' + emptycount;
17357                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17358                     emptycount = 0;
17359                 }
17360                 if(PieceToChar(piece) == '+') {
17361                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
17362                     *p++ = '+';
17363                     piece = (ChessSquare)(DEMOTED piece);
17364                 }
17365                 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
17366                 if(p[-1] == '~') {
17367                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
17368                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
17369                     *p++ = '~';
17370                 }
17371             }
17372         }
17373         if (emptycount > 0) {
17374             if(emptycount<10) /* [HGM] can be >= 10 */
17375                 *p++ = '0' + emptycount;
17376             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17377             emptycount = 0;
17378         }
17379         *p++ = '/';
17380     }
17381     *(p - 1) = ' ';
17382
17383     /* [HGM] print Crazyhouse or Shogi holdings */
17384     if( gameInfo.holdingsWidth ) {
17385         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
17386         q = p;
17387         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
17388             piece = boards[move][i][BOARD_WIDTH-1];
17389             if( piece != EmptySquare )
17390               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
17391                   *p++ = PieceToChar(piece);
17392         }
17393         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
17394             piece = boards[move][BOARD_HEIGHT-i-1][0];
17395             if( piece != EmptySquare )
17396               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
17397                   *p++ = PieceToChar(piece);
17398         }
17399
17400         if( q == p ) *p++ = '-';
17401         *p++ = ']';
17402         *p++ = ' ';
17403     }
17404
17405     /* Active color */
17406     *p++ = whiteToPlay ? 'w' : 'b';
17407     *p++ = ' ';
17408
17409   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
17410     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
17411   } else {
17412   if(nrCastlingRights) {
17413      q = p;
17414      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
17415        /* [HGM] write directly from rights */
17416            if(boards[move][CASTLING][2] != NoRights &&
17417               boards[move][CASTLING][0] != NoRights   )
17418                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
17419            if(boards[move][CASTLING][2] != NoRights &&
17420               boards[move][CASTLING][1] != NoRights   )
17421                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
17422            if(boards[move][CASTLING][5] != NoRights &&
17423               boards[move][CASTLING][3] != NoRights   )
17424                 *p++ = boards[move][CASTLING][3] + AAA;
17425            if(boards[move][CASTLING][5] != NoRights &&
17426               boards[move][CASTLING][4] != NoRights   )
17427                 *p++ = boards[move][CASTLING][4] + AAA;
17428      } else {
17429
17430         /* [HGM] write true castling rights */
17431         if( nrCastlingRights == 6 ) {
17432             int q, k=0;
17433             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
17434                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
17435             q = (boards[move][CASTLING][1] == BOARD_LEFT &&
17436                  boards[move][CASTLING][2] != NoRights  );
17437             if(gameInfo.variant == VariantSChess) { // for S-Chess, indicate all vrgin backrank pieces
17438                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_RGHT]; // count white held pieces
17439                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17440                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
17441                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
17442             }
17443             if(q) *p++ = 'Q';
17444             k = 0;
17445             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
17446                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
17447             q = (boards[move][CASTLING][4] == BOARD_LEFT &&
17448                  boards[move][CASTLING][5] != NoRights  );
17449             if(gameInfo.variant == VariantSChess) {
17450                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_LEFT-1]; // count black held pieces
17451                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17452                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
17453                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17454             }
17455             if(q) *p++ = 'q';
17456         }
17457      }
17458      if (q == p) *p++ = '-'; /* No castling rights */
17459      *p++ = ' ';
17460   }
17461
17462   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17463      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17464      gameInfo.variant != VariantMakruk   && gameInfo.variant != VariantASEAN ) {
17465     /* En passant target square */
17466     if (move > backwardMostMove) {
17467         fromX = moveList[move - 1][0] - AAA;
17468         fromY = moveList[move - 1][1] - ONE;
17469         toX = moveList[move - 1][2] - AAA;
17470         toY = moveList[move - 1][3] - ONE;
17471         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
17472             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
17473             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
17474             fromX == toX) {
17475             /* 2-square pawn move just happened */
17476             *p++ = toX + AAA;
17477             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17478         } else {
17479             *p++ = '-';
17480         }
17481     } else if(move == backwardMostMove) {
17482         // [HGM] perhaps we should always do it like this, and forget the above?
17483         if((signed char)boards[move][EP_STATUS] >= 0) {
17484             *p++ = boards[move][EP_STATUS] + AAA;
17485             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17486         } else {
17487             *p++ = '-';
17488         }
17489     } else {
17490         *p++ = '-';
17491     }
17492     *p++ = ' ';
17493   }
17494   }
17495
17496     if(moveCounts)
17497     {   int i = 0, j=move;
17498
17499         /* [HGM] find reversible plies */
17500         if (appData.debugMode) { int k;
17501             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
17502             for(k=backwardMostMove; k<=forwardMostMove; k++)
17503                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
17504
17505         }
17506
17507         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
17508         if( j == backwardMostMove ) i += initialRulePlies;
17509         sprintf(p, "%d ", i);
17510         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
17511
17512         /* Fullmove number */
17513         sprintf(p, "%d", (move / 2) + 1);
17514     } else *--p = NULLCHAR;
17515
17516     return StrSave(buf);
17517 }
17518
17519 Boolean
17520 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
17521 {
17522     int i, j, k, w=0;
17523     char *p, c;
17524     int emptycount, virgin[BOARD_FILES];
17525     ChessSquare piece;
17526
17527     p = fen;
17528
17529     /* Piece placement data */
17530     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17531         j = 0;
17532         for (;;) {
17533             if (*p == '/' || *p == ' ' || *p == '[' ) {
17534                 if(j > w) w = j;
17535                 emptycount = gameInfo.boardWidth - j;
17536                 while (emptycount--)
17537                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17538                 if (*p == '/') p++;
17539                 else if(autoSize) { // we stumbled unexpectedly into end of board
17540                     for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
17541                         for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
17542                     }
17543                     appData.NrRanks = gameInfo.boardHeight - i; i=0;
17544                 }
17545                 break;
17546 #if(BOARD_FILES >= 10)
17547             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
17548                 p++; emptycount=10;
17549                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17550                 while (emptycount--)
17551                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17552 #endif
17553             } else if (*p == '*') {
17554                 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
17555             } else if (isdigit(*p)) {
17556                 emptycount = *p++ - '0';
17557                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
17558                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17559                 while (emptycount--)
17560                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17561             } else if (*p == '+' || isalpha(*p)) {
17562                 if (j >= gameInfo.boardWidth) return FALSE;
17563                 if(*p=='+') {
17564                     piece = CharToPiece(*++p);
17565                     if(piece == EmptySquare) return FALSE; /* unknown piece */
17566                     piece = (ChessSquare) (CHUPROMOTED piece ); p++;
17567                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
17568                 } else piece = CharToPiece(*p++);
17569
17570                 if(piece==EmptySquare) return FALSE; /* unknown piece */
17571                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
17572                     piece = (ChessSquare) (PROMOTED piece);
17573                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
17574                     p++;
17575                 }
17576                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
17577             } else {
17578                 return FALSE;
17579             }
17580         }
17581     }
17582     while (*p == '/' || *p == ' ') p++;
17583
17584     if(autoSize) appData.NrFiles = w, InitPosition(TRUE);
17585
17586     /* [HGM] by default clear Crazyhouse holdings, if present */
17587     if(gameInfo.holdingsWidth) {
17588        for(i=0; i<BOARD_HEIGHT; i++) {
17589            board[i][0]             = EmptySquare; /* black holdings */
17590            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
17591            board[i][1]             = (ChessSquare) 0; /* black counts */
17592            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
17593        }
17594     }
17595
17596     /* [HGM] look for Crazyhouse holdings here */
17597     while(*p==' ') p++;
17598     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
17599         if(*p == '[') p++;
17600         if(*p == '-' ) p++; /* empty holdings */ else {
17601             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
17602             /* if we would allow FEN reading to set board size, we would   */
17603             /* have to add holdings and shift the board read so far here   */
17604             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
17605                 p++;
17606                 if((int) piece >= (int) BlackPawn ) {
17607                     i = (int)piece - (int)BlackPawn;
17608                     i = PieceToNumber((ChessSquare)i);
17609                     if( i >= gameInfo.holdingsSize ) return FALSE;
17610                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
17611                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
17612                 } else {
17613                     i = (int)piece - (int)WhitePawn;
17614                     i = PieceToNumber((ChessSquare)i);
17615                     if( i >= gameInfo.holdingsSize ) return FALSE;
17616                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
17617                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
17618                 }
17619             }
17620         }
17621         if(*p == ']') p++;
17622     }
17623
17624     while(*p == ' ') p++;
17625
17626     /* Active color */
17627     c = *p++;
17628     if(appData.colorNickNames) {
17629       if( c == appData.colorNickNames[0] ) c = 'w'; else
17630       if( c == appData.colorNickNames[1] ) c = 'b';
17631     }
17632     switch (c) {
17633       case 'w':
17634         *blackPlaysFirst = FALSE;
17635         break;
17636       case 'b':
17637         *blackPlaysFirst = TRUE;
17638         break;
17639       default:
17640         return FALSE;
17641     }
17642
17643     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
17644     /* return the extra info in global variiables             */
17645
17646     /* set defaults in case FEN is incomplete */
17647     board[EP_STATUS] = EP_UNKNOWN;
17648     for(i=0; i<nrCastlingRights; i++ ) {
17649         board[CASTLING][i] =
17650             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
17651     }   /* assume possible unless obviously impossible */
17652     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
17653     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
17654     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
17655                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
17656     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
17657     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
17658     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
17659                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
17660     FENrulePlies = 0;
17661
17662     while(*p==' ') p++;
17663     if(nrCastlingRights) {
17664       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
17665       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
17666           /* castling indicator present, so default becomes no castlings */
17667           for(i=0; i<nrCastlingRights; i++ ) {
17668                  board[CASTLING][i] = NoRights;
17669           }
17670       }
17671       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
17672              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess) &&
17673              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
17674              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
17675         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
17676
17677         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
17678             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
17679             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
17680         }
17681         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
17682             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
17683         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
17684                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
17685         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
17686                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
17687         switch(c) {
17688           case'K':
17689               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
17690               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
17691               board[CASTLING][2] = whiteKingFile;
17692               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
17693               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17694               break;
17695           case'Q':
17696               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
17697               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
17698               board[CASTLING][2] = whiteKingFile;
17699               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
17700               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17701               break;
17702           case'k':
17703               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
17704               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
17705               board[CASTLING][5] = blackKingFile;
17706               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
17707               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17708               break;
17709           case'q':
17710               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
17711               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
17712               board[CASTLING][5] = blackKingFile;
17713               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
17714               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17715           case '-':
17716               break;
17717           default: /* FRC castlings */
17718               if(c >= 'a') { /* black rights */
17719                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
17720                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17721                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
17722                   if(i == BOARD_RGHT) break;
17723                   board[CASTLING][5] = i;
17724                   c -= AAA;
17725                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
17726                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
17727                   if(c > i)
17728                       board[CASTLING][3] = c;
17729                   else
17730                       board[CASTLING][4] = c;
17731               } else { /* white rights */
17732                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
17733                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17734                     if(board[0][i] == WhiteKing) break;
17735                   if(i == BOARD_RGHT) break;
17736                   board[CASTLING][2] = i;
17737                   c -= AAA - 'a' + 'A';
17738                   if(board[0][c] >= WhiteKing) break;
17739                   if(c > i)
17740                       board[CASTLING][0] = c;
17741                   else
17742                       board[CASTLING][1] = c;
17743               }
17744         }
17745       }
17746       for(i=0; i<nrCastlingRights; i++)
17747         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
17748       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = virgin[i];
17749     if (appData.debugMode) {
17750         fprintf(debugFP, "FEN castling rights:");
17751         for(i=0; i<nrCastlingRights; i++)
17752         fprintf(debugFP, " %d", board[CASTLING][i]);
17753         fprintf(debugFP, "\n");
17754     }
17755
17756       while(*p==' ') p++;
17757     }
17758
17759     /* read e.p. field in games that know e.p. capture */
17760     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17761        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17762        gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
17763       if(*p=='-') {
17764         p++; board[EP_STATUS] = EP_NONE;
17765       } else {
17766          char c = *p++ - AAA;
17767
17768          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
17769          if(*p >= '0' && *p <='9') p++;
17770          board[EP_STATUS] = c;
17771       }
17772     }
17773
17774
17775     if(sscanf(p, "%d", &i) == 1) {
17776         FENrulePlies = i; /* 50-move ply counter */
17777         /* (The move number is still ignored)    */
17778     }
17779
17780     return TRUE;
17781 }
17782
17783 void
17784 EditPositionPasteFEN (char *fen)
17785 {
17786   if (fen != NULL) {
17787     Board initial_position;
17788
17789     if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
17790       DisplayError(_("Bad FEN position in clipboard"), 0);
17791       return ;
17792     } else {
17793       int savedBlackPlaysFirst = blackPlaysFirst;
17794       EditPositionEvent();
17795       blackPlaysFirst = savedBlackPlaysFirst;
17796       CopyBoard(boards[0], initial_position);
17797       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
17798       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
17799       DisplayBothClocks();
17800       DrawPosition(FALSE, boards[currentMove]);
17801     }
17802   }
17803 }
17804
17805 static char cseq[12] = "\\   ";
17806
17807 Boolean
17808 set_cont_sequence (char *new_seq)
17809 {
17810     int len;
17811     Boolean ret;
17812
17813     // handle bad attempts to set the sequence
17814         if (!new_seq)
17815                 return 0; // acceptable error - no debug
17816
17817     len = strlen(new_seq);
17818     ret = (len > 0) && (len < sizeof(cseq));
17819     if (ret)
17820       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
17821     else if (appData.debugMode)
17822       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
17823     return ret;
17824 }
17825
17826 /*
17827     reformat a source message so words don't cross the width boundary.  internal
17828     newlines are not removed.  returns the wrapped size (no null character unless
17829     included in source message).  If dest is NULL, only calculate the size required
17830     for the dest buffer.  lp argument indicats line position upon entry, and it's
17831     passed back upon exit.
17832 */
17833 int
17834 wrap (char *dest, char *src, int count, int width, int *lp)
17835 {
17836     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
17837
17838     cseq_len = strlen(cseq);
17839     old_line = line = *lp;
17840     ansi = len = clen = 0;
17841
17842     for (i=0; i < count; i++)
17843     {
17844         if (src[i] == '\033')
17845             ansi = 1;
17846
17847         // if we hit the width, back up
17848         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
17849         {
17850             // store i & len in case the word is too long
17851             old_i = i, old_len = len;
17852
17853             // find the end of the last word
17854             while (i && src[i] != ' ' && src[i] != '\n')
17855             {
17856                 i--;
17857                 len--;
17858             }
17859
17860             // word too long?  restore i & len before splitting it
17861             if ((old_i-i+clen) >= width)
17862             {
17863                 i = old_i;
17864                 len = old_len;
17865             }
17866
17867             // extra space?
17868             if (i && src[i-1] == ' ')
17869                 len--;
17870
17871             if (src[i] != ' ' && src[i] != '\n')
17872             {
17873                 i--;
17874                 if (len)
17875                     len--;
17876             }
17877
17878             // now append the newline and continuation sequence
17879             if (dest)
17880                 dest[len] = '\n';
17881             len++;
17882             if (dest)
17883                 strncpy(dest+len, cseq, cseq_len);
17884             len += cseq_len;
17885             line = cseq_len;
17886             clen = cseq_len;
17887             continue;
17888         }
17889
17890         if (dest)
17891             dest[len] = src[i];
17892         len++;
17893         if (!ansi)
17894             line++;
17895         if (src[i] == '\n')
17896             line = 0;
17897         if (src[i] == 'm')
17898             ansi = 0;
17899     }
17900     if (dest && appData.debugMode)
17901     {
17902         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
17903             count, width, line, len, *lp);
17904         show_bytes(debugFP, src, count);
17905         fprintf(debugFP, "\ndest: ");
17906         show_bytes(debugFP, dest, len);
17907         fprintf(debugFP, "\n");
17908     }
17909     *lp = dest ? line : old_line;
17910
17911     return len;
17912 }
17913
17914 // [HGM] vari: routines for shelving variations
17915 Boolean modeRestore = FALSE;
17916
17917 void
17918 PushInner (int firstMove, int lastMove)
17919 {
17920         int i, j, nrMoves = lastMove - firstMove;
17921
17922         // push current tail of game on stack
17923         savedResult[storedGames] = gameInfo.result;
17924         savedDetails[storedGames] = gameInfo.resultDetails;
17925         gameInfo.resultDetails = NULL;
17926         savedFirst[storedGames] = firstMove;
17927         savedLast [storedGames] = lastMove;
17928         savedFramePtr[storedGames] = framePtr;
17929         framePtr -= nrMoves; // reserve space for the boards
17930         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
17931             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
17932             for(j=0; j<MOVE_LEN; j++)
17933                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
17934             for(j=0; j<2*MOVE_LEN; j++)
17935                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
17936             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
17937             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
17938             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
17939             pvInfoList[firstMove+i-1].depth = 0;
17940             commentList[framePtr+i] = commentList[firstMove+i];
17941             commentList[firstMove+i] = NULL;
17942         }
17943
17944         storedGames++;
17945         forwardMostMove = firstMove; // truncate game so we can start variation
17946 }
17947
17948 void
17949 PushTail (int firstMove, int lastMove)
17950 {
17951         if(appData.icsActive) { // only in local mode
17952                 forwardMostMove = currentMove; // mimic old ICS behavior
17953                 return;
17954         }
17955         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
17956
17957         PushInner(firstMove, lastMove);
17958         if(storedGames == 1) GreyRevert(FALSE);
17959         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
17960 }
17961
17962 void
17963 PopInner (Boolean annotate)
17964 {
17965         int i, j, nrMoves;
17966         char buf[8000], moveBuf[20];
17967
17968         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
17969         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
17970         nrMoves = savedLast[storedGames] - currentMove;
17971         if(annotate) {
17972                 int cnt = 10;
17973                 if(!WhiteOnMove(currentMove))
17974                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
17975                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
17976                 for(i=currentMove; i<forwardMostMove; i++) {
17977                         if(WhiteOnMove(i))
17978                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
17979                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
17980                         strcat(buf, moveBuf);
17981                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
17982                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
17983                 }
17984                 strcat(buf, ")");
17985         }
17986         for(i=1; i<=nrMoves; i++) { // copy last variation back
17987             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
17988             for(j=0; j<MOVE_LEN; j++)
17989                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
17990             for(j=0; j<2*MOVE_LEN; j++)
17991                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
17992             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
17993             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
17994             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
17995             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
17996             commentList[currentMove+i] = commentList[framePtr+i];
17997             commentList[framePtr+i] = NULL;
17998         }
17999         if(annotate) AppendComment(currentMove+1, buf, FALSE);
18000         framePtr = savedFramePtr[storedGames];
18001         gameInfo.result = savedResult[storedGames];
18002         if(gameInfo.resultDetails != NULL) {
18003             free(gameInfo.resultDetails);
18004       }
18005         gameInfo.resultDetails = savedDetails[storedGames];
18006         forwardMostMove = currentMove + nrMoves;
18007 }
18008
18009 Boolean
18010 PopTail (Boolean annotate)
18011 {
18012         if(appData.icsActive) return FALSE; // only in local mode
18013         if(!storedGames) return FALSE; // sanity
18014         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
18015
18016         PopInner(annotate);
18017         if(currentMove < forwardMostMove) ForwardEvent(); else
18018         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
18019
18020         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
18021         return TRUE;
18022 }
18023
18024 void
18025 CleanupTail ()
18026 {       // remove all shelved variations
18027         int i;
18028         for(i=0; i<storedGames; i++) {
18029             if(savedDetails[i])
18030                 free(savedDetails[i]);
18031             savedDetails[i] = NULL;
18032         }
18033         for(i=framePtr; i<MAX_MOVES; i++) {
18034                 if(commentList[i]) free(commentList[i]);
18035                 commentList[i] = NULL;
18036         }
18037         framePtr = MAX_MOVES-1;
18038         storedGames = 0;
18039 }
18040
18041 void
18042 LoadVariation (int index, char *text)
18043 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
18044         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
18045         int level = 0, move;
18046
18047         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
18048         // first find outermost bracketing variation
18049         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
18050             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
18051                 if(*p == '{') wait = '}'; else
18052                 if(*p == '[') wait = ']'; else
18053                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
18054                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
18055             }
18056             if(*p == wait) wait = NULLCHAR; // closing ]} found
18057             p++;
18058         }
18059         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
18060         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
18061         end[1] = NULLCHAR; // clip off comment beyond variation
18062         ToNrEvent(currentMove-1);
18063         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
18064         // kludge: use ParsePV() to append variation to game
18065         move = currentMove;
18066         ParsePV(start, TRUE, TRUE);
18067         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
18068         ClearPremoveHighlights();
18069         CommentPopDown();
18070         ToNrEvent(currentMove+1);
18071 }
18072
18073 void
18074 LoadTheme ()
18075 {
18076     char *p, *q, buf[MSG_SIZ];
18077     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
18078         snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
18079         ParseArgsFromString(buf);
18080         ActivateTheme(TRUE); // also redo colors
18081         return;
18082     }
18083     p = nickName;
18084     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
18085     {
18086         int len;
18087         q = appData.themeNames;
18088         snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
18089       if(appData.useBitmaps) {
18090         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
18091                 appData.liteBackTextureFile, appData.darkBackTextureFile,
18092                 appData.liteBackTextureMode,
18093                 appData.darkBackTextureMode );
18094       } else {
18095         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
18096                 Col2Text(2),   // lightSquareColor
18097                 Col2Text(3) ); // darkSquareColor
18098       }
18099       if(appData.useBorder) {
18100         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
18101                 appData.border);
18102       } else {
18103         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
18104       }
18105       if(appData.useFont) {
18106         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
18107                 appData.renderPiecesWithFont,
18108                 appData.fontToPieceTable,
18109                 Col2Text(9),    // appData.fontBackColorWhite
18110                 Col2Text(10) ); // appData.fontForeColorBlack
18111       } else {
18112         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
18113                 appData.pieceDirectory);
18114         if(!appData.pieceDirectory[0])
18115           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
18116                 Col2Text(0),   // whitePieceColor
18117                 Col2Text(1) ); // blackPieceColor
18118       }
18119       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
18120                 Col2Text(4),   // highlightSquareColor
18121                 Col2Text(5) ); // premoveHighlightColor
18122         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
18123         if(insert != q) insert[-1] = NULLCHAR;
18124         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
18125         if(q)   free(q);
18126     }
18127     ActivateTheme(FALSE);
18128 }