Implement Chu Shogi
[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         int r, f;
7173         if(dragging == 2) DragPieceMove(xPix, yPix); // [HGM] lion: drag without button for second leg
7174         if(!first.highlight) return;
7175         if(hiX == -1 && hiY == -1 && x == fromX && y == fromY) // record markings 
7176           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7177             baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7178         else if(hiX != x || hiY != y) {
7179           // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7180           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7181             marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7182           if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7183             char buf[MSG_SIZ];
7184             snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7185             SendToProgram(buf, &first);
7186           }
7187           SetHighlights(fromX, fromY, x, y);
7188         }
7189 }
7190
7191 void ReportClick(char *action, int x, int y)
7192 {
7193         char buf[MSG_SIZ]; // Inform engine of what user does
7194         int r, f;
7195         if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7196           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 1, marker[r][f] = 0;
7197         if(!first.highlight || gameMode == EditPosition) return;
7198         snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7199         SendToProgram(buf, &first);
7200 }
7201
7202 void
7203 LeftClick (ClickType clickType, int xPix, int yPix)
7204 {
7205     int x, y;
7206     Boolean saveAnimate;
7207     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7208     char promoChoice = NULLCHAR;
7209     ChessSquare piece;
7210     static TimeMark lastClickTime, prevClickTime;
7211
7212     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7213
7214     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7215
7216     if (clickType == Press) ErrorPopDown();
7217     lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7218
7219     x = EventToSquare(xPix, BOARD_WIDTH);
7220     y = EventToSquare(yPix, BOARD_HEIGHT);
7221     if (!flipView && y >= 0) {
7222         y = BOARD_HEIGHT - 1 - y;
7223     }
7224     if (flipView && x >= 0) {
7225         x = BOARD_WIDTH - 1 - x;
7226     }
7227
7228     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7229         defaultPromoChoice = promoSweep;
7230         promoSweep = EmptySquare;   // terminate sweep
7231         promoDefaultAltered = TRUE;
7232         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7233     }
7234
7235     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7236         if(clickType == Release) return; // ignore upclick of click-click destination
7237         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7238         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7239         if(gameInfo.holdingsWidth &&
7240                 (WhiteOnMove(currentMove)
7241                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7242                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7243             // click in right holdings, for determining promotion piece
7244             ChessSquare p = boards[currentMove][y][x];
7245             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7246             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7247             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7248                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7249                 fromX = fromY = -1;
7250                 return;
7251             }
7252         }
7253         DrawPosition(FALSE, boards[currentMove]);
7254         return;
7255     }
7256
7257     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7258     if(clickType == Press
7259             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7260               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7261               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7262         return;
7263
7264     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7265         // could be static click on premove from-square: abort premove
7266         gotPremove = 0;
7267         ClearPremoveHighlights();
7268     }
7269
7270     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7271         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7272
7273     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7274         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7275                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7276         defaultPromoChoice = DefaultPromoChoice(side);
7277     }
7278
7279     autoQueen = appData.alwaysPromoteToQueen;
7280
7281     if (fromX == -1) {
7282       int originalY = y;
7283       gatingPiece = EmptySquare;
7284       if (clickType != Press) {
7285         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7286             DragPieceEnd(xPix, yPix); dragging = 0;
7287             DrawPosition(FALSE, NULL);
7288         }
7289         return;
7290       }
7291       doubleClick = FALSE;
7292       if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7293         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7294       }
7295       fromX = x; fromY = y; toX = toY = -1;
7296       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7297          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7298          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7299             /* First square */
7300             if (OKToStartUserMove(fromX, fromY)) {
7301                 second = 0;
7302                 ReportClick("lift", x, y);
7303                 MarkTargetSquares(0);
7304                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7305                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7306                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7307                     promoSweep = defaultPromoChoice;
7308                     selectFlag = 0; lastX = xPix; lastY = yPix;
7309                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7310                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7311                 }
7312                 if (appData.highlightDragging) {
7313                     SetHighlights(fromX, fromY, -1, -1);
7314                 } else {
7315                     ClearHighlights();
7316                 }
7317             } else fromX = fromY = -1;
7318             return;
7319         }
7320     }
7321
7322     /* fromX != -1 */
7323     if (clickType == Press && gameMode != EditPosition && killX < 0) {
7324         ChessSquare fromP;
7325         ChessSquare toP;
7326         int frc;
7327
7328         // ignore off-board to clicks
7329         if(y < 0 || x < 0) return;
7330
7331         /* Check if clicking again on the same color piece */
7332         fromP = boards[currentMove][fromY][fromX];
7333         toP = boards[currentMove][y][x];
7334         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
7335         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
7336              WhitePawn <= toP && toP <= WhiteKing &&
7337              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7338              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7339             (BlackPawn <= fromP && fromP <= BlackKing &&
7340              BlackPawn <= toP && toP <= BlackKing &&
7341              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7342              !(fromP == BlackKing && toP == BlackRook && frc))) {
7343             /* Clicked again on same color piece -- changed his mind */
7344             second = (x == fromX && y == fromY);
7345             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7346                 second = FALSE; // first double-click rather than scond click
7347                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7348             }
7349             promoDefaultAltered = FALSE;
7350             MarkTargetSquares(1);
7351            if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7352             if (appData.highlightDragging) {
7353                 SetHighlights(x, y, -1, -1);
7354             } else {
7355                 ClearHighlights();
7356             }
7357             if (OKToStartUserMove(x, y)) {
7358                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7359                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7360                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7361                  gatingPiece = boards[currentMove][fromY][fromX];
7362                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7363                 fromX = x;
7364                 fromY = y; dragging = 1;
7365                 ReportClick("lift", x, y);
7366                 MarkTargetSquares(0);
7367                 DragPieceBegin(xPix, yPix, FALSE);
7368                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7369                     promoSweep = defaultPromoChoice;
7370                     selectFlag = 0; lastX = xPix; lastY = yPix;
7371                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7372                 }
7373             }
7374            }
7375            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7376            second = FALSE;
7377         }
7378         // ignore clicks on holdings
7379         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7380     }
7381
7382     if (clickType == Release && x == fromX && y == fromY && killX < 0) {
7383         DragPieceEnd(xPix, yPix); dragging = 0;
7384         if(clearFlag) {
7385             // a deferred attempt to click-click move an empty square on top of a piece
7386             boards[currentMove][y][x] = EmptySquare;
7387             ClearHighlights();
7388             DrawPosition(FALSE, boards[currentMove]);
7389             fromX = fromY = -1; clearFlag = 0;
7390             return;
7391         }
7392         if (appData.animateDragging) {
7393             /* Undo animation damage if any */
7394             DrawPosition(FALSE, NULL);
7395         }
7396         if (second || sweepSelecting) {
7397             /* Second up/down in same square; just abort move */
7398             if(sweepSelecting) DrawPosition(FALSE, boards[currentMove]);
7399             second = sweepSelecting = 0;
7400             fromX = fromY = -1;
7401             gatingPiece = EmptySquare;
7402             MarkTargetSquares(1);
7403             ClearHighlights();
7404             gotPremove = 0;
7405             ClearPremoveHighlights();
7406         } else {
7407             /* First upclick in same square; start click-click mode */
7408             SetHighlights(x, y, -1, -1);
7409         }
7410         return;
7411     }
7412
7413     clearFlag = 0;
7414
7415     if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] && (x != killX || y != killY)) {
7416         if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7417         DisplayMessage(_("only marked squares are legal"),"");
7418         DrawPosition(TRUE, NULL);
7419         return; // ignore to-click
7420     }
7421
7422     /* we now have a different from- and (possibly off-board) to-square */
7423     /* Completed move */
7424     if(!sweepSelecting) {
7425         toX = x;
7426         toY = y;
7427     } else sweepSelecting = 0; // this must be the up-click corresponding to the down-click that started the sweep
7428
7429     saveAnimate = appData.animate;
7430     if (clickType == Press) {
7431         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7432             // must be Edit Position mode with empty-square selected
7433             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7434             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7435             return;
7436         }
7437         if(dragging == 2) {  // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job
7438             dragging = 1;
7439             return;
7440         }
7441         if(x == killX && y == killY) {              // second click on this square, which was selected as first-leg target
7442             killX = killY = -1;                     // this informs us no second leg is coming, so treat as to-click without intermediate
7443         } else
7444         if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
7445         if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7446           if(appData.sweepSelect) {
7447             ChessSquare piece = boards[currentMove][fromY][fromX];
7448             promoSweep = defaultPromoChoice;
7449             if(PieceToChar(CHUPROMOTED piece) == '+') promoSweep = CHUPROMOTED piece;
7450             selectFlag = 0; lastX = xPix; lastY = yPix;
7451             Sweep(0); // Pawn that is going to promote: preview promotion piece
7452             sweepSelecting = 1;
7453             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7454             MarkTargetSquares(1);
7455           }
7456           return; // promo popup appears on up-click
7457         }
7458         /* Finish clickclick move */
7459         if (appData.animate || appData.highlightLastMove) {
7460             SetHighlights(fromX, fromY, toX, toY);
7461         } else {
7462             ClearHighlights();
7463         }
7464     } else {
7465 #if 0
7466 // [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
7467         /* Finish drag move */
7468         if (appData.highlightLastMove) {
7469             SetHighlights(fromX, fromY, toX, toY);
7470         } else {
7471             ClearHighlights();
7472         }
7473 #endif
7474         if(!dragging || marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
7475           dragging *= 2;            // flag button-less dragging if we are dragging
7476           MarkTargetSquares(1);
7477           if(x == killX && y == killY) killX = killY = -1; else {
7478             killX = x; killY = y;     //remeber this square as intermediate
7479             ReportClick("put", x, y); // and inform engine
7480             ReportClick("lift", x, y);
7481             return;
7482           }
7483         }
7484         DragPieceEnd(xPix, yPix); dragging = 0;
7485         /* Don't animate move and drag both */
7486         appData.animate = FALSE;
7487     }
7488
7489     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7490     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7491         ChessSquare piece = boards[currentMove][fromY][fromX];
7492         if(gameMode == EditPosition && piece != EmptySquare &&
7493            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7494             int n;
7495
7496             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7497                 n = PieceToNumber(piece - (int)BlackPawn);
7498                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7499                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7500                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7501             } else
7502             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7503                 n = PieceToNumber(piece);
7504                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7505                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7506                 boards[currentMove][n][BOARD_WIDTH-2]++;
7507             }
7508             boards[currentMove][fromY][fromX] = EmptySquare;
7509         }
7510         ClearHighlights();
7511         fromX = fromY = -1;
7512         MarkTargetSquares(1);
7513         DrawPosition(TRUE, boards[currentMove]);
7514         return;
7515     }
7516
7517     // off-board moves should not be highlighted
7518     if(x < 0 || y < 0) ClearHighlights();
7519     else ReportClick("put", x, y);
7520
7521     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7522
7523     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7524         SetHighlights(fromX, fromY, toX, toY);
7525         MarkTargetSquares(1);
7526         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7527             // [HGM] super: promotion to captured piece selected from holdings
7528             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7529             promotionChoice = TRUE;
7530             // kludge follows to temporarily execute move on display, without promoting yet
7531             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7532             boards[currentMove][toY][toX] = p;
7533             DrawPosition(FALSE, boards[currentMove]);
7534             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7535             boards[currentMove][toY][toX] = q;
7536             DisplayMessage("Click in holdings to choose piece", "");
7537             return;
7538         }
7539         PromotionPopUp();
7540     } else {
7541         int oldMove = currentMove;
7542         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7543         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7544         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7545         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7546            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7547             DrawPosition(TRUE, boards[currentMove]);
7548         MarkTargetSquares(1);
7549         fromX = fromY = -1;
7550     }
7551     appData.animate = saveAnimate;
7552     if (appData.animate || appData.animateDragging) {
7553         /* Undo animation damage if needed */
7554         DrawPosition(FALSE, NULL);
7555     }
7556 }
7557
7558 int
7559 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7560 {   // front-end-free part taken out of PieceMenuPopup
7561     int whichMenu; int xSqr, ySqr;
7562
7563     if(seekGraphUp) { // [HGM] seekgraph
7564         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7565         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7566         return -2;
7567     }
7568
7569     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7570          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7571         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7572         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7573         if(action == Press)   {
7574             originalFlip = flipView;
7575             flipView = !flipView; // temporarily flip board to see game from partners perspective
7576             DrawPosition(TRUE, partnerBoard);
7577             DisplayMessage(partnerStatus, "");
7578             partnerUp = TRUE;
7579         } else if(action == Release) {
7580             flipView = originalFlip;
7581             DrawPosition(TRUE, boards[currentMove]);
7582             partnerUp = FALSE;
7583         }
7584         return -2;
7585     }
7586
7587     xSqr = EventToSquare(x, BOARD_WIDTH);
7588     ySqr = EventToSquare(y, BOARD_HEIGHT);
7589     if (action == Release) {
7590         if(pieceSweep != EmptySquare) {
7591             EditPositionMenuEvent(pieceSweep, toX, toY);
7592             pieceSweep = EmptySquare;
7593         } else UnLoadPV(); // [HGM] pv
7594     }
7595     if (action != Press) return -2; // return code to be ignored
7596     switch (gameMode) {
7597       case IcsExamining:
7598         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7599       case EditPosition:
7600         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7601         if (xSqr < 0 || ySqr < 0) return -1;
7602         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7603         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7604         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7605         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7606         NextPiece(0);
7607         return 2; // grab
7608       case IcsObserving:
7609         if(!appData.icsEngineAnalyze) return -1;
7610       case IcsPlayingWhite:
7611       case IcsPlayingBlack:
7612         if(!appData.zippyPlay) goto noZip;
7613       case AnalyzeMode:
7614       case AnalyzeFile:
7615       case MachinePlaysWhite:
7616       case MachinePlaysBlack:
7617       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7618         if (!appData.dropMenu) {
7619           LoadPV(x, y);
7620           return 2; // flag front-end to grab mouse events
7621         }
7622         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7623            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7624       case EditGame:
7625       noZip:
7626         if (xSqr < 0 || ySqr < 0) return -1;
7627         if (!appData.dropMenu || appData.testLegality &&
7628             gameInfo.variant != VariantBughouse &&
7629             gameInfo.variant != VariantCrazyhouse) return -1;
7630         whichMenu = 1; // drop menu
7631         break;
7632       default:
7633         return -1;
7634     }
7635
7636     if (((*fromX = xSqr) < 0) ||
7637         ((*fromY = ySqr) < 0)) {
7638         *fromX = *fromY = -1;
7639         return -1;
7640     }
7641     if (flipView)
7642       *fromX = BOARD_WIDTH - 1 - *fromX;
7643     else
7644       *fromY = BOARD_HEIGHT - 1 - *fromY;
7645
7646     return whichMenu;
7647 }
7648
7649 void
7650 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7651 {
7652 //    char * hint = lastHint;
7653     FrontEndProgramStats stats;
7654
7655     stats.which = cps == &first ? 0 : 1;
7656     stats.depth = cpstats->depth;
7657     stats.nodes = cpstats->nodes;
7658     stats.score = cpstats->score;
7659     stats.time = cpstats->time;
7660     stats.pv = cpstats->movelist;
7661     stats.hint = lastHint;
7662     stats.an_move_index = 0;
7663     stats.an_move_count = 0;
7664
7665     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7666         stats.hint = cpstats->move_name;
7667         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7668         stats.an_move_count = cpstats->nr_moves;
7669     }
7670
7671     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
7672
7673     SetProgramStats( &stats );
7674 }
7675
7676 void
7677 ClearEngineOutputPane (int which)
7678 {
7679     static FrontEndProgramStats dummyStats;
7680     dummyStats.which = which;
7681     dummyStats.pv = "#";
7682     SetProgramStats( &dummyStats );
7683 }
7684
7685 #define MAXPLAYERS 500
7686
7687 char *
7688 TourneyStandings (int display)
7689 {
7690     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7691     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7692     char result, *p, *names[MAXPLAYERS];
7693
7694     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7695         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7696     names[0] = p = strdup(appData.participants);
7697     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7698
7699     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7700
7701     while(result = appData.results[nr]) {
7702         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7703         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7704         wScore = bScore = 0;
7705         switch(result) {
7706           case '+': wScore = 2; break;
7707           case '-': bScore = 2; break;
7708           case '=': wScore = bScore = 1; break;
7709           case ' ':
7710           case '*': return strdup("busy"); // tourney not finished
7711         }
7712         score[w] += wScore;
7713         score[b] += bScore;
7714         games[w]++;
7715         games[b]++;
7716         nr++;
7717     }
7718     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7719     for(w=0; w<nPlayers; w++) {
7720         bScore = -1;
7721         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7722         ranking[w] = b; points[w] = bScore; score[b] = -2;
7723     }
7724     p = malloc(nPlayers*34+1);
7725     for(w=0; w<nPlayers && w<display; w++)
7726         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7727     free(names[0]);
7728     return p;
7729 }
7730
7731 void
7732 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7733 {       // count all piece types
7734         int p, f, r;
7735         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7736         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7737         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7738                 p = board[r][f];
7739                 pCnt[p]++;
7740                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7741                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7742                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7743                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7744                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7745                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7746         }
7747 }
7748
7749 int
7750 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7751 {
7752         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7753         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7754
7755         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7756         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7757         if(myPawns == 2 && nMine == 3) // KPP
7758             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7759         if(myPawns == 1 && nMine == 2) // KP
7760             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7761         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7762             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7763         if(myPawns) return FALSE;
7764         if(pCnt[WhiteRook+side])
7765             return pCnt[BlackRook-side] ||
7766                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7767                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7768                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7769         if(pCnt[WhiteCannon+side]) {
7770             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7771             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7772         }
7773         if(pCnt[WhiteKnight+side])
7774             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7775         return FALSE;
7776 }
7777
7778 int
7779 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7780 {
7781         VariantClass v = gameInfo.variant;
7782
7783         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7784         if(v == VariantShatranj) return TRUE; // always winnable through baring
7785         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7786         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7787
7788         if(v == VariantXiangqi) {
7789                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7790
7791                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7792                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7793                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7794                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7795                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7796                 if(stale) // we have at least one last-rank P plus perhaps C
7797                     return majors // KPKX
7798                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7799                 else // KCA*E*
7800                     return pCnt[WhiteFerz+side] // KCAK
7801                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7802                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7803                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7804
7805         } else if(v == VariantKnightmate) {
7806                 if(nMine == 1) return FALSE;
7807                 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
7808         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7809                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7810
7811                 if(nMine == 1) return FALSE; // bare King
7812                 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
7813                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7814                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7815                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7816                 if(pCnt[WhiteKnight+side])
7817                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7818                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7819                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7820                 if(nBishops)
7821                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7822                 if(pCnt[WhiteAlfil+side])
7823                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7824                 if(pCnt[WhiteWazir+side])
7825                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7826         }
7827
7828         return TRUE;
7829 }
7830
7831 int
7832 CompareWithRights (Board b1, Board b2)
7833 {
7834     int rights = 0;
7835     if(!CompareBoards(b1, b2)) return FALSE;
7836     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7837     /* compare castling rights */
7838     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7839            rights++; /* King lost rights, while rook still had them */
7840     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7841         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7842            rights++; /* but at least one rook lost them */
7843     }
7844     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7845            rights++;
7846     if( b1[CASTLING][5] != NoRights ) {
7847         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7848            rights++;
7849     }
7850     return rights == 0;
7851 }
7852
7853 int
7854 Adjudicate (ChessProgramState *cps)
7855 {       // [HGM] some adjudications useful with buggy engines
7856         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7857         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7858         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7859         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7860         int k, drop, count = 0; static int bare = 1;
7861         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7862         Boolean canAdjudicate = !appData.icsActive;
7863
7864         // most tests only when we understand the game, i.e. legality-checking on
7865             if( appData.testLegality )
7866             {   /* [HGM] Some more adjudications for obstinate engines */
7867                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7868                 static int moveCount = 6;
7869                 ChessMove result;
7870                 char *reason = NULL;
7871
7872                 /* Count what is on board. */
7873                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7874
7875                 /* Some material-based adjudications that have to be made before stalemate test */
7876                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7877                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7878                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7879                      if(canAdjudicate && appData.checkMates) {
7880                          if(engineOpponent)
7881                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7882                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7883                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7884                          return 1;
7885                      }
7886                 }
7887
7888                 /* Bare King in Shatranj (loses) or Losers (wins) */
7889                 if( nrW == 1 || nrB == 1) {
7890                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7891                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7892                      if(canAdjudicate && appData.checkMates) {
7893                          if(engineOpponent)
7894                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7895                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7896                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7897                          return 1;
7898                      }
7899                   } else
7900                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7901                   {    /* bare King */
7902                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7903                         if(canAdjudicate && appData.checkMates) {
7904                             /* but only adjudicate if adjudication enabled */
7905                             if(engineOpponent)
7906                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7907                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7908                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7909                             return 1;
7910                         }
7911                   }
7912                 } else bare = 1;
7913
7914
7915             // don't wait for engine to announce game end if we can judge ourselves
7916             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7917               case MT_CHECK:
7918                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7919                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7920                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7921                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7922                             checkCnt++;
7923                         if(checkCnt >= 2) {
7924                             reason = "Xboard adjudication: 3rd check";
7925                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7926                             break;
7927                         }
7928                     }
7929                 }
7930               case MT_NONE:
7931               default:
7932                 break;
7933               case MT_STALEMATE:
7934               case MT_STAINMATE:
7935                 reason = "Xboard adjudication: Stalemate";
7936                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7937                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7938                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7939                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7940                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7941                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7942                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7943                                                                         EP_CHECKMATE : EP_WINS);
7944                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
7945                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7946                 }
7947                 break;
7948               case MT_CHECKMATE:
7949                 reason = "Xboard adjudication: Checkmate";
7950                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7951                 if(gameInfo.variant == VariantShogi) {
7952                     if(forwardMostMove > backwardMostMove
7953                        && moveList[forwardMostMove-1][1] == '@'
7954                        && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
7955                         reason = "XBoard adjudication: pawn-drop mate";
7956                         boards[forwardMostMove][EP_STATUS] = EP_WINS;
7957                     }
7958                 }
7959                 break;
7960             }
7961
7962                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7963                     case EP_STALEMATE:
7964                         result = GameIsDrawn; break;
7965                     case EP_CHECKMATE:
7966                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7967                     case EP_WINS:
7968                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7969                     default:
7970                         result = EndOfFile;
7971                 }
7972                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7973                     if(engineOpponent)
7974                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7975                     GameEnds( result, reason, GE_XBOARD );
7976                     return 1;
7977                 }
7978
7979                 /* Next absolutely insufficient mating material. */
7980                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7981                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7982                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7983
7984                      /* always flag draws, for judging claims */
7985                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7986
7987                      if(canAdjudicate && appData.materialDraws) {
7988                          /* but only adjudicate them if adjudication enabled */
7989                          if(engineOpponent) {
7990                            SendToProgram("force\n", engineOpponent); // suppress reply
7991                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7992                          }
7993                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7994                          return 1;
7995                      }
7996                 }
7997
7998                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7999                 if(gameInfo.variant == VariantXiangqi ?
8000                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
8001                  : nrW + nrB == 4 &&
8002                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
8003                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
8004                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
8005                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
8006                    ) ) {
8007                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
8008                      {    /* if the first 3 moves do not show a tactical win, declare draw */
8009                           if(engineOpponent) {
8010                             SendToProgram("force\n", engineOpponent); // suppress reply
8011                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8012                           }
8013                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
8014                           return 1;
8015                      }
8016                 } else moveCount = 6;
8017             }
8018
8019         // Repetition draws and 50-move rule can be applied independently of legality testing
8020
8021                 /* Check for rep-draws */
8022                 count = 0;
8023                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
8024                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
8025                 for(k = forwardMostMove-2;
8026                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
8027                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
8028                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
8029                     k-=2)
8030                 {   int rights=0;
8031                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
8032                         /* compare castling rights */
8033                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8034                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8035                                 rights++; /* King lost rights, while rook still had them */
8036                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8037                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8038                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8039                                    rights++; /* but at least one rook lost them */
8040                         }
8041                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8042                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8043                                 rights++;
8044                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8045                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8046                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8047                                    rights++;
8048                         }
8049                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8050                             && appData.drawRepeats > 1) {
8051                              /* adjudicate after user-specified nr of repeats */
8052                              int result = GameIsDrawn;
8053                              char *details = "XBoard adjudication: repetition draw";
8054                              if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8055                                 // [HGM] xiangqi: check for forbidden perpetuals
8056                                 int m, ourPerpetual = 1, hisPerpetual = 1;
8057                                 for(m=forwardMostMove; m>k; m-=2) {
8058                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8059                                         ourPerpetual = 0; // the current mover did not always check
8060                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8061                                         hisPerpetual = 0; // the opponent did not always check
8062                                 }
8063                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8064                                                                         ourPerpetual, hisPerpetual);
8065                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8066                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8067                                     details = "Xboard adjudication: perpetual checking";
8068                                 } else
8069                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8070                                     break; // (or we would have caught him before). Abort repetition-checking loop.
8071                                 } else
8072                                 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8073                                     if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8074                                         result = BlackWins;
8075                                         details = "Xboard adjudication: repetition";
8076                                     }
8077                                 } else // it must be XQ
8078                                 // Now check for perpetual chases
8079                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8080                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
8081                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8082                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8083                                         static char resdet[MSG_SIZ];
8084                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8085                                         details = resdet;
8086                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8087                                     } else
8088                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
8089                                         break; // Abort repetition-checking loop.
8090                                 }
8091                                 // if neither of us is checking or chasing all the time, or both are, it is draw
8092                              }
8093                              if(engineOpponent) {
8094                                SendToProgram("force\n", engineOpponent); // suppress reply
8095                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8096                              }
8097                              GameEnds( result, details, GE_XBOARD );
8098                              return 1;
8099                         }
8100                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8101                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8102                     }
8103                 }
8104
8105                 /* Now we test for 50-move draws. Determine ply count */
8106                 count = forwardMostMove;
8107                 /* look for last irreversble move */
8108                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8109                     count--;
8110                 /* if we hit starting position, add initial plies */
8111                 if( count == backwardMostMove )
8112                     count -= initialRulePlies;
8113                 count = forwardMostMove - count;
8114                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8115                         // adjust reversible move counter for checks in Xiangqi
8116                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
8117                         if(i < backwardMostMove) i = backwardMostMove;
8118                         while(i <= forwardMostMove) {
8119                                 lastCheck = inCheck; // check evasion does not count
8120                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8121                                 if(inCheck || lastCheck) count--; // check does not count
8122                                 i++;
8123                         }
8124                 }
8125                 if( count >= 100)
8126                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8127                          /* this is used to judge if draw claims are legal */
8128                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8129                          if(engineOpponent) {
8130                            SendToProgram("force\n", engineOpponent); // suppress reply
8131                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8132                          }
8133                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8134                          return 1;
8135                 }
8136
8137                 /* if draw offer is pending, treat it as a draw claim
8138                  * when draw condition present, to allow engines a way to
8139                  * claim draws before making their move to avoid a race
8140                  * condition occurring after their move
8141                  */
8142                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8143                          char *p = NULL;
8144                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8145                              p = "Draw claim: 50-move rule";
8146                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8147                              p = "Draw claim: 3-fold repetition";
8148                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8149                              p = "Draw claim: insufficient mating material";
8150                          if( p != NULL && canAdjudicate) {
8151                              if(engineOpponent) {
8152                                SendToProgram("force\n", engineOpponent); // suppress reply
8153                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8154                              }
8155                              GameEnds( GameIsDrawn, p, GE_XBOARD );
8156                              return 1;
8157                          }
8158                 }
8159
8160                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8161                     if(engineOpponent) {
8162                       SendToProgram("force\n", engineOpponent); // suppress reply
8163                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8164                     }
8165                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8166                     return 1;
8167                 }
8168         return 0;
8169 }
8170
8171 char *
8172 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8173 {   // [HGM] book: this routine intercepts moves to simulate book replies
8174     char *bookHit = NULL;
8175
8176     //first determine if the incoming move brings opponent into his book
8177     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8178         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8179     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8180     if(bookHit != NULL && !cps->bookSuspend) {
8181         // make sure opponent is not going to reply after receiving move to book position
8182         SendToProgram("force\n", cps);
8183         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8184     }
8185     if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8186     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8187     // now arrange restart after book miss
8188     if(bookHit) {
8189         // after a book hit we never send 'go', and the code after the call to this routine
8190         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8191         char buf[MSG_SIZ], *move = bookHit;
8192         if(cps->useSAN) {
8193             int fromX, fromY, toX, toY;
8194             char promoChar;
8195             ChessMove moveType;
8196             move = buf + 30;
8197             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8198                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8199                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8200                                     PosFlags(forwardMostMove),
8201                                     fromY, fromX, toY, toX, promoChar, move);
8202             } else {
8203                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8204                 bookHit = NULL;
8205             }
8206         }
8207         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8208         SendToProgram(buf, cps);
8209         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8210     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8211         SendToProgram("go\n", cps);
8212         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8213     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8214         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8215             SendToProgram("go\n", cps);
8216         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8217     }
8218     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8219 }
8220
8221 int
8222 LoadError (char *errmess, ChessProgramState *cps)
8223 {   // unloads engine and switches back to -ncp mode if it was first
8224     if(cps->initDone) return FALSE;
8225     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8226     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8227     cps->pr = NoProc;
8228     if(cps == &first) {
8229         appData.noChessProgram = TRUE;
8230         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8231         gameMode = BeginningOfGame; ModeHighlight();
8232         SetNCPMode();
8233     }
8234     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8235     DisplayMessage("", ""); // erase waiting message
8236     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8237     return TRUE;
8238 }
8239
8240 char *savedMessage;
8241 ChessProgramState *savedState;
8242 void
8243 DeferredBookMove (void)
8244 {
8245         if(savedState->lastPing != savedState->lastPong)
8246                     ScheduleDelayedEvent(DeferredBookMove, 10);
8247         else
8248         HandleMachineMove(savedMessage, savedState);
8249 }
8250
8251 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8252 static ChessProgramState *stalledEngine;
8253 static char stashedInputMove[MSG_SIZ];
8254
8255 void
8256 HandleMachineMove (char *message, ChessProgramState *cps)
8257 {
8258     static char firstLeg[20];
8259     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8260     char realname[MSG_SIZ];
8261     int fromX, fromY, toX, toY;
8262     ChessMove moveType;
8263     char promoChar;
8264     char *p, *pv=buf1;
8265     int machineWhite, oldError;
8266     char *bookHit;
8267
8268     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8269         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8270         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8271             DisplayError(_("Invalid pairing from pairing engine"), 0);
8272             return;
8273         }
8274         pairingReceived = 1;
8275         NextMatchGame();
8276         return; // Skim the pairing messages here.
8277     }
8278
8279     oldError = cps->userError; cps->userError = 0;
8280
8281 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8282     /*
8283      * Kludge to ignore BEL characters
8284      */
8285     while (*message == '\007') message++;
8286
8287     /*
8288      * [HGM] engine debug message: ignore lines starting with '#' character
8289      */
8290     if(cps->debug && *message == '#') return;
8291
8292     /*
8293      * Look for book output
8294      */
8295     if (cps == &first && bookRequested) {
8296         if (message[0] == '\t' || message[0] == ' ') {
8297             /* Part of the book output is here; append it */
8298             strcat(bookOutput, message);
8299             strcat(bookOutput, "  \n");
8300             return;
8301         } else if (bookOutput[0] != NULLCHAR) {
8302             /* All of book output has arrived; display it */
8303             char *p = bookOutput;
8304             while (*p != NULLCHAR) {
8305                 if (*p == '\t') *p = ' ';
8306                 p++;
8307             }
8308             DisplayInformation(bookOutput);
8309             bookRequested = FALSE;
8310             /* Fall through to parse the current output */
8311         }
8312     }
8313
8314     /*
8315      * Look for machine move.
8316      */
8317     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8318         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8319     {
8320         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8321             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8322             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8323             stalledEngine = cps;
8324             if(appData.ponderNextMove) { // bring opponent out of ponder
8325                 if(gameMode == TwoMachinesPlay) {
8326                     if(cps->other->pause)
8327                         PauseEngine(cps->other);
8328                     else
8329                         SendToProgram("easy\n", cps->other);
8330                 }
8331             }
8332             StopClocks();
8333             return;
8334         }
8335
8336         /* This method is only useful on engines that support ping */
8337         if (cps->lastPing != cps->lastPong) {
8338           if (gameMode == BeginningOfGame) {
8339             /* Extra move from before last new; ignore */
8340             if (appData.debugMode) {
8341                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8342             }
8343           } else {
8344             if (appData.debugMode) {
8345                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8346                         cps->which, gameMode);
8347             }
8348
8349             SendToProgram("undo\n", cps);
8350           }
8351           return;
8352         }
8353
8354         switch (gameMode) {
8355           case BeginningOfGame:
8356             /* Extra move from before last reset; ignore */
8357             if (appData.debugMode) {
8358                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8359             }
8360             return;
8361
8362           case EndOfGame:
8363           case IcsIdle:
8364           default:
8365             /* Extra move after we tried to stop.  The mode test is
8366                not a reliable way of detecting this problem, but it's
8367                the best we can do on engines that don't support ping.
8368             */
8369             if (appData.debugMode) {
8370                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8371                         cps->which, gameMode);
8372             }
8373             SendToProgram("undo\n", cps);
8374             return;
8375
8376           case MachinePlaysWhite:
8377           case IcsPlayingWhite:
8378             machineWhite = TRUE;
8379             break;
8380
8381           case MachinePlaysBlack:
8382           case IcsPlayingBlack:
8383             machineWhite = FALSE;
8384             break;
8385
8386           case TwoMachinesPlay:
8387             machineWhite = (cps->twoMachinesColor[0] == 'w');
8388             break;
8389         }
8390         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8391             if (appData.debugMode) {
8392                 fprintf(debugFP,
8393                         "Ignoring move out of turn by %s, gameMode %d"
8394                         ", forwardMost %d\n",
8395                         cps->which, gameMode, forwardMostMove);
8396             }
8397             return;
8398         }
8399
8400         if(cps->alphaRank) AlphaRank(machineMove, 4);
8401
8402         // [HGM] lion: (some very limited) support for Alien protocol
8403         killX = killY = -1;
8404         if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
8405             safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
8406             return;
8407         } else if(firstLeg[0]) { // there was a previous leg;
8408             // only support case where same piece makes two step (and don't even test that!)
8409             char buf[20], *p = machineMove+1, *q = buf+1, f;
8410             safeStrCpy(buf, machineMove, 20);
8411             while(isdigit(*q)) q++; // find start of to-square
8412             safeStrCpy(machineMove, firstLeg, 20);
8413             while(isdigit(*p)) p++;
8414             safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
8415             sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
8416             firstLeg[0] = NULLCHAR;
8417         }
8418
8419         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8420                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8421             /* Machine move could not be parsed; ignore it. */
8422           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8423                     machineMove, _(cps->which));
8424             DisplayMoveError(buf1);
8425             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
8426                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
8427             if (gameMode == TwoMachinesPlay) {
8428               GameEnds(machineWhite ? BlackWins : WhiteWins,
8429                        buf1, GE_XBOARD);
8430             }
8431             return;
8432         }
8433
8434         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8435         /* So we have to redo legality test with true e.p. status here,  */
8436         /* to make sure an illegal e.p. capture does not slip through,   */
8437         /* to cause a forfeit on a justified illegal-move complaint      */
8438         /* of the opponent.                                              */
8439         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8440            ChessMove moveType;
8441            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8442                              fromY, fromX, toY, toX, promoChar);
8443             if(moveType == IllegalMove) {
8444               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8445                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8446                 GameEnds(machineWhite ? BlackWins : WhiteWins,
8447                            buf1, GE_XBOARD);
8448                 return;
8449            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
8450            /* [HGM] Kludge to handle engines that send FRC-style castling
8451               when they shouldn't (like TSCP-Gothic) */
8452            switch(moveType) {
8453              case WhiteASideCastleFR:
8454              case BlackASideCastleFR:
8455                toX+=2;
8456                currentMoveString[2]++;
8457                break;
8458              case WhiteHSideCastleFR:
8459              case BlackHSideCastleFR:
8460                toX--;
8461                currentMoveString[2]--;
8462                break;
8463              default: ; // nothing to do, but suppresses warning of pedantic compilers
8464            }
8465         }
8466         hintRequested = FALSE;
8467         lastHint[0] = NULLCHAR;
8468         bookRequested = FALSE;
8469         /* Program may be pondering now */
8470         cps->maybeThinking = TRUE;
8471         if (cps->sendTime == 2) cps->sendTime = 1;
8472         if (cps->offeredDraw) cps->offeredDraw--;
8473
8474         /* [AS] Save move info*/
8475         pvInfoList[ forwardMostMove ].score = programStats.score;
8476         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8477         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8478
8479         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8480
8481         /* Test suites abort the 'game' after one move */
8482         if(*appData.finger) {
8483            static FILE *f;
8484            char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8485            if(!f) f = fopen(appData.finger, "w");
8486            if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8487            else { DisplayFatalError("Bad output file", errno, 0); return; }
8488            free(fen);
8489            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8490         }
8491
8492         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8493         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8494             int count = 0;
8495
8496             while( count < adjudicateLossPlies ) {
8497                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8498
8499                 if( count & 1 ) {
8500                     score = -score; /* Flip score for winning side */
8501                 }
8502
8503                 if( score > adjudicateLossThreshold ) {
8504                     break;
8505                 }
8506
8507                 count++;
8508             }
8509
8510             if( count >= adjudicateLossPlies ) {
8511                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8512
8513                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8514                     "Xboard adjudication",
8515                     GE_XBOARD );
8516
8517                 return;
8518             }
8519         }
8520
8521         if(Adjudicate(cps)) {
8522             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8523             return; // [HGM] adjudicate: for all automatic game ends
8524         }
8525
8526 #if ZIPPY
8527         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8528             first.initDone) {
8529           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8530                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8531                 SendToICS("draw ");
8532                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8533           }
8534           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8535           ics_user_moved = 1;
8536           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8537                 char buf[3*MSG_SIZ];
8538
8539                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8540                         programStats.score / 100.,
8541                         programStats.depth,
8542                         programStats.time / 100.,
8543                         (unsigned int)programStats.nodes,
8544                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8545                         programStats.movelist);
8546                 SendToICS(buf);
8547 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8548           }
8549         }
8550 #endif
8551
8552         /* [AS] Clear stats for next move */
8553         ClearProgramStats();
8554         thinkOutput[0] = NULLCHAR;
8555         hiddenThinkOutputState = 0;
8556
8557         bookHit = NULL;
8558         if (gameMode == TwoMachinesPlay) {
8559             /* [HGM] relaying draw offers moved to after reception of move */
8560             /* and interpreting offer as claim if it brings draw condition */
8561             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8562                 SendToProgram("draw\n", cps->other);
8563             }
8564             if (cps->other->sendTime) {
8565                 SendTimeRemaining(cps->other,
8566                                   cps->other->twoMachinesColor[0] == 'w');
8567             }
8568             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8569             if (firstMove && !bookHit) {
8570                 firstMove = FALSE;
8571                 if (cps->other->useColors) {
8572                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8573                 }
8574                 SendToProgram("go\n", cps->other);
8575             }
8576             cps->other->maybeThinking = TRUE;
8577         }
8578
8579         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8580
8581         if (!pausing && appData.ringBellAfterMoves) {
8582             RingBell();
8583         }
8584
8585         /*
8586          * Reenable menu items that were disabled while
8587          * machine was thinking
8588          */
8589         if (gameMode != TwoMachinesPlay)
8590             SetUserThinkingEnables();
8591
8592         // [HGM] book: after book hit opponent has received move and is now in force mode
8593         // force the book reply into it, and then fake that it outputted this move by jumping
8594         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8595         if(bookHit) {
8596                 static char bookMove[MSG_SIZ]; // a bit generous?
8597
8598                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8599                 strcat(bookMove, bookHit);
8600                 message = bookMove;
8601                 cps = cps->other;
8602                 programStats.nodes = programStats.depth = programStats.time =
8603                 programStats.score = programStats.got_only_move = 0;
8604                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8605
8606                 if(cps->lastPing != cps->lastPong) {
8607                     savedMessage = message; // args for deferred call
8608                     savedState = cps;
8609                     ScheduleDelayedEvent(DeferredBookMove, 10);
8610                     return;
8611                 }
8612                 goto FakeBookMove;
8613         }
8614
8615         return;
8616     }
8617
8618     /* Set special modes for chess engines.  Later something general
8619      *  could be added here; for now there is just one kludge feature,
8620      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8621      *  when "xboard" is given as an interactive command.
8622      */
8623     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8624         cps->useSigint = FALSE;
8625         cps->useSigterm = FALSE;
8626     }
8627     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8628       ParseFeatures(message+8, cps);
8629       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8630     }
8631
8632     if (!strncmp(message, "setup ", 6) && 
8633         (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown || NonStandardBoardSize())
8634                                         ) { // [HGM] allow first engine to define opening position
8635       int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
8636       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8637       *buf = NULLCHAR;
8638       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8639       if(startedFromSetupPosition) return;
8640       dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
8641       if(dummy >= 3) {
8642         while(message[s] && message[s++] != ' ');
8643         if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
8644            dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
8645             appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
8646             if(dummy == 4) gameInfo.variant = StringToVariant(varName);     // parent variant
8647           InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
8648           if(*buf) SetCharTable(pieceToChar, buf); // do again, for it was spoiled by InitPosition
8649         }
8650       }
8651       ParseFEN(boards[0], &dummy, message+s, FALSE);
8652       DrawPosition(TRUE, boards[0]);
8653       startedFromSetupPosition = TRUE;
8654       return;
8655     }
8656     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8657      * want this, I was asked to put it in, and obliged.
8658      */
8659     if (!strncmp(message, "setboard ", 9)) {
8660         Board initial_position;
8661
8662         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8663
8664         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
8665             DisplayError(_("Bad FEN received from engine"), 0);
8666             return ;
8667         } else {
8668            Reset(TRUE, FALSE);
8669            CopyBoard(boards[0], initial_position);
8670            initialRulePlies = FENrulePlies;
8671            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8672            else gameMode = MachinePlaysBlack;
8673            DrawPosition(FALSE, boards[currentMove]);
8674         }
8675         return;
8676     }
8677
8678     /*
8679      * Look for communication commands
8680      */
8681     if (!strncmp(message, "telluser ", 9)) {
8682         if(message[9] == '\\' && message[10] == '\\')
8683             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8684         PlayTellSound();
8685         DisplayNote(message + 9);
8686         return;
8687     }
8688     if (!strncmp(message, "tellusererror ", 14)) {
8689         cps->userError = 1;
8690         if(message[14] == '\\' && message[15] == '\\')
8691             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8692         PlayTellSound();
8693         DisplayError(message + 14, 0);
8694         return;
8695     }
8696     if (!strncmp(message, "tellopponent ", 13)) {
8697       if (appData.icsActive) {
8698         if (loggedOn) {
8699           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8700           SendToICS(buf1);
8701         }
8702       } else {
8703         DisplayNote(message + 13);
8704       }
8705       return;
8706     }
8707     if (!strncmp(message, "tellothers ", 11)) {
8708       if (appData.icsActive) {
8709         if (loggedOn) {
8710           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8711           SendToICS(buf1);
8712         }
8713       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
8714       return;
8715     }
8716     if (!strncmp(message, "tellall ", 8)) {
8717       if (appData.icsActive) {
8718         if (loggedOn) {
8719           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8720           SendToICS(buf1);
8721         }
8722       } else {
8723         DisplayNote(message + 8);
8724       }
8725       return;
8726     }
8727     if (strncmp(message, "warning", 7) == 0) {
8728         /* Undocumented feature, use tellusererror in new code */
8729         DisplayError(message, 0);
8730         return;
8731     }
8732     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8733         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8734         strcat(realname, " query");
8735         AskQuestion(realname, buf2, buf1, cps->pr);
8736         return;
8737     }
8738     /* Commands from the engine directly to ICS.  We don't allow these to be
8739      *  sent until we are logged on. Crafty kibitzes have been known to
8740      *  interfere with the login process.
8741      */
8742     if (loggedOn) {
8743         if (!strncmp(message, "tellics ", 8)) {
8744             SendToICS(message + 8);
8745             SendToICS("\n");
8746             return;
8747         }
8748         if (!strncmp(message, "tellicsnoalias ", 15)) {
8749             SendToICS(ics_prefix);
8750             SendToICS(message + 15);
8751             SendToICS("\n");
8752             return;
8753         }
8754         /* The following are for backward compatibility only */
8755         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8756             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8757             SendToICS(ics_prefix);
8758             SendToICS(message);
8759             SendToICS("\n");
8760             return;
8761         }
8762     }
8763     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8764         return;
8765     }
8766     if(!strncmp(message, "highlight ", 10)) {
8767         if(appData.testLegality && appData.markers) return;
8768         MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
8769         return;
8770     }
8771     if(!strncmp(message, "click ", 6)) {
8772         char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
8773         if(appData.testLegality || !appData.oneClick) return;
8774         sscanf(message+6, "%c%d%c", &f, &y, &c);
8775         x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
8776         if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
8777         x = x*squareSize + (x+1)*lineGap + squareSize/2;
8778         y = y*squareSize + (y+1)*lineGap + squareSize/2;
8779         f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
8780         if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
8781             LeftClick(Release, lastLeftX, lastLeftY);
8782         controlKey  = (c == ',');
8783         LeftClick(Press, x, y);
8784         LeftClick(Release, x, y);
8785         first.highlight = f;
8786         return;
8787     }
8788     /*
8789      * If the move is illegal, cancel it and redraw the board.
8790      * Also deal with other error cases.  Matching is rather loose
8791      * here to accommodate engines written before the spec.
8792      */
8793     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8794         strncmp(message, "Error", 5) == 0) {
8795         if (StrStr(message, "name") ||
8796             StrStr(message, "rating") || StrStr(message, "?") ||
8797             StrStr(message, "result") || StrStr(message, "board") ||
8798             StrStr(message, "bk") || StrStr(message, "computer") ||
8799             StrStr(message, "variant") || StrStr(message, "hint") ||
8800             StrStr(message, "random") || StrStr(message, "depth") ||
8801             StrStr(message, "accepted")) {
8802             return;
8803         }
8804         if (StrStr(message, "protover")) {
8805           /* Program is responding to input, so it's apparently done
8806              initializing, and this error message indicates it is
8807              protocol version 1.  So we don't need to wait any longer
8808              for it to initialize and send feature commands. */
8809           FeatureDone(cps, 1);
8810           cps->protocolVersion = 1;
8811           return;
8812         }
8813         cps->maybeThinking = FALSE;
8814
8815         if (StrStr(message, "draw")) {
8816             /* Program doesn't have "draw" command */
8817             cps->sendDrawOffers = 0;
8818             return;
8819         }
8820         if (cps->sendTime != 1 &&
8821             (StrStr(message, "time") || StrStr(message, "otim"))) {
8822           /* Program apparently doesn't have "time" or "otim" command */
8823           cps->sendTime = 0;
8824           return;
8825         }
8826         if (StrStr(message, "analyze")) {
8827             cps->analysisSupport = FALSE;
8828             cps->analyzing = FALSE;
8829 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8830             EditGameEvent(); // [HGM] try to preserve loaded game
8831             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8832             DisplayError(buf2, 0);
8833             return;
8834         }
8835         if (StrStr(message, "(no matching move)st")) {
8836           /* Special kludge for GNU Chess 4 only */
8837           cps->stKludge = TRUE;
8838           SendTimeControl(cps, movesPerSession, timeControl,
8839                           timeIncrement, appData.searchDepth,
8840                           searchTime);
8841           return;
8842         }
8843         if (StrStr(message, "(no matching move)sd")) {
8844           /* Special kludge for GNU Chess 4 only */
8845           cps->sdKludge = TRUE;
8846           SendTimeControl(cps, movesPerSession, timeControl,
8847                           timeIncrement, appData.searchDepth,
8848                           searchTime);
8849           return;
8850         }
8851         if (!StrStr(message, "llegal")) {
8852             return;
8853         }
8854         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8855             gameMode == IcsIdle) return;
8856         if (forwardMostMove <= backwardMostMove) return;
8857         if (pausing) PauseEvent();
8858       if(appData.forceIllegal) {
8859             // [HGM] illegal: machine refused move; force position after move into it
8860           SendToProgram("force\n", cps);
8861           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8862                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8863                 // when black is to move, while there might be nothing on a2 or black
8864                 // might already have the move. So send the board as if white has the move.
8865                 // But first we must change the stm of the engine, as it refused the last move
8866                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8867                 if(WhiteOnMove(forwardMostMove)) {
8868                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8869                     SendBoard(cps, forwardMostMove); // kludgeless board
8870                 } else {
8871                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8872                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8873                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8874                 }
8875           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8876             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8877                  gameMode == TwoMachinesPlay)
8878               SendToProgram("go\n", cps);
8879             return;
8880       } else
8881         if (gameMode == PlayFromGameFile) {
8882             /* Stop reading this game file */
8883             gameMode = EditGame;
8884             ModeHighlight();
8885         }
8886         /* [HGM] illegal-move claim should forfeit game when Xboard */
8887         /* only passes fully legal moves                            */
8888         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8889             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8890                                 "False illegal-move claim", GE_XBOARD );
8891             return; // do not take back move we tested as valid
8892         }
8893         currentMove = forwardMostMove-1;
8894         DisplayMove(currentMove-1); /* before DisplayMoveError */
8895         SwitchClocks(forwardMostMove-1); // [HGM] race
8896         DisplayBothClocks();
8897         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8898                 parseList[currentMove], _(cps->which));
8899         DisplayMoveError(buf1);
8900         DrawPosition(FALSE, boards[currentMove]);
8901
8902         SetUserThinkingEnables();
8903         return;
8904     }
8905     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8906         /* Program has a broken "time" command that
8907            outputs a string not ending in newline.
8908            Don't use it. */
8909         cps->sendTime = 0;
8910     }
8911
8912     /*
8913      * If chess program startup fails, exit with an error message.
8914      * Attempts to recover here are futile. [HGM] Well, we try anyway
8915      */
8916     if ((StrStr(message, "unknown host") != NULL)
8917         || (StrStr(message, "No remote directory") != NULL)
8918         || (StrStr(message, "not found") != NULL)
8919         || (StrStr(message, "No such file") != NULL)
8920         || (StrStr(message, "can't alloc") != NULL)
8921         || (StrStr(message, "Permission denied") != NULL)) {
8922
8923         cps->maybeThinking = FALSE;
8924         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8925                 _(cps->which), cps->program, cps->host, message);
8926         RemoveInputSource(cps->isr);
8927         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8928             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
8929             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
8930         }
8931         return;
8932     }
8933
8934     /*
8935      * Look for hint output
8936      */
8937     if (sscanf(message, "Hint: %s", buf1) == 1) {
8938         if (cps == &first && hintRequested) {
8939             hintRequested = FALSE;
8940             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8941                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8942                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8943                                     PosFlags(forwardMostMove),
8944                                     fromY, fromX, toY, toX, promoChar, buf1);
8945                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8946                 DisplayInformation(buf2);
8947             } else {
8948                 /* Hint move could not be parsed!? */
8949               snprintf(buf2, sizeof(buf2),
8950                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8951                         buf1, _(cps->which));
8952                 DisplayError(buf2, 0);
8953             }
8954         } else {
8955           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8956         }
8957         return;
8958     }
8959
8960     /*
8961      * Ignore other messages if game is not in progress
8962      */
8963     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8964         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8965
8966     /*
8967      * look for win, lose, draw, or draw offer
8968      */
8969     if (strncmp(message, "1-0", 3) == 0) {
8970         char *p, *q, *r = "";
8971         p = strchr(message, '{');
8972         if (p) {
8973             q = strchr(p, '}');
8974             if (q) {
8975                 *q = NULLCHAR;
8976                 r = p + 1;
8977             }
8978         }
8979         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8980         return;
8981     } else if (strncmp(message, "0-1", 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         /* Kludge for Arasan 4.1 bug */
8992         if (strcmp(r, "Black resigns") == 0) {
8993             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8994             return;
8995         }
8996         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8997         return;
8998     } else if (strncmp(message, "1/2", 3) == 0) {
8999         char *p, *q, *r = "";
9000         p = strchr(message, '{');
9001         if (p) {
9002             q = strchr(p, '}');
9003             if (q) {
9004                 *q = NULLCHAR;
9005                 r = p + 1;
9006             }
9007         }
9008
9009         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9010         return;
9011
9012     } else if (strncmp(message, "White resign", 12) == 0) {
9013         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9014         return;
9015     } else if (strncmp(message, "Black resign", 12) == 0) {
9016         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9017         return;
9018     } else if (strncmp(message, "White matches", 13) == 0 ||
9019                strncmp(message, "Black matches", 13) == 0   ) {
9020         /* [HGM] ignore GNUShogi noises */
9021         return;
9022     } else if (strncmp(message, "White", 5) == 0 &&
9023                message[5] != '(' &&
9024                StrStr(message, "Black") == NULL) {
9025         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9026         return;
9027     } else if (strncmp(message, "Black", 5) == 0 &&
9028                message[5] != '(') {
9029         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9030         return;
9031     } else if (strcmp(message, "resign") == 0 ||
9032                strcmp(message, "computer resigns") == 0) {
9033         switch (gameMode) {
9034           case MachinePlaysBlack:
9035           case IcsPlayingBlack:
9036             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9037             break;
9038           case MachinePlaysWhite:
9039           case IcsPlayingWhite:
9040             GameEnds(BlackWins, "White resigns", GE_ENGINE);
9041             break;
9042           case TwoMachinesPlay:
9043             if (cps->twoMachinesColor[0] == 'w')
9044               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9045             else
9046               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9047             break;
9048           default:
9049             /* can't happen */
9050             break;
9051         }
9052         return;
9053     } else if (strncmp(message, "opponent mates", 14) == 0) {
9054         switch (gameMode) {
9055           case MachinePlaysBlack:
9056           case IcsPlayingBlack:
9057             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9058             break;
9059           case MachinePlaysWhite:
9060           case IcsPlayingWhite:
9061             GameEnds(BlackWins, "Black mates", GE_ENGINE);
9062             break;
9063           case TwoMachinesPlay:
9064             if (cps->twoMachinesColor[0] == 'w')
9065               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9066             else
9067               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9068             break;
9069           default:
9070             /* can't happen */
9071             break;
9072         }
9073         return;
9074     } else if (strncmp(message, "computer mates", 14) == 0) {
9075         switch (gameMode) {
9076           case MachinePlaysBlack:
9077           case IcsPlayingBlack:
9078             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9079             break;
9080           case MachinePlaysWhite:
9081           case IcsPlayingWhite:
9082             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9083             break;
9084           case TwoMachinesPlay:
9085             if (cps->twoMachinesColor[0] == 'w')
9086               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9087             else
9088               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9089             break;
9090           default:
9091             /* can't happen */
9092             break;
9093         }
9094         return;
9095     } else if (strncmp(message, "checkmate", 9) == 0) {
9096         if (WhiteOnMove(forwardMostMove)) {
9097             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9098         } else {
9099             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9100         }
9101         return;
9102     } else if (strstr(message, "Draw") != NULL ||
9103                strstr(message, "game is a draw") != NULL) {
9104         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9105         return;
9106     } else if (strstr(message, "offer") != NULL &&
9107                strstr(message, "draw") != NULL) {
9108 #if ZIPPY
9109         if (appData.zippyPlay && first.initDone) {
9110             /* Relay offer to ICS */
9111             SendToICS(ics_prefix);
9112             SendToICS("draw\n");
9113         }
9114 #endif
9115         cps->offeredDraw = 2; /* valid until this engine moves twice */
9116         if (gameMode == TwoMachinesPlay) {
9117             if (cps->other->offeredDraw) {
9118                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9119             /* [HGM] in two-machine mode we delay relaying draw offer      */
9120             /* until after we also have move, to see if it is really claim */
9121             }
9122         } else if (gameMode == MachinePlaysWhite ||
9123                    gameMode == MachinePlaysBlack) {
9124           if (userOfferedDraw) {
9125             DisplayInformation(_("Machine accepts your draw offer"));
9126             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9127           } else {
9128             DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9129           }
9130         }
9131     }
9132
9133
9134     /*
9135      * Look for thinking output
9136      */
9137     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9138           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9139                                 ) {
9140         int plylev, mvleft, mvtot, curscore, time;
9141         char mvname[MOVE_LEN];
9142         u64 nodes; // [DM]
9143         char plyext;
9144         int ignore = FALSE;
9145         int prefixHint = FALSE;
9146         mvname[0] = NULLCHAR;
9147
9148         switch (gameMode) {
9149           case MachinePlaysBlack:
9150           case IcsPlayingBlack:
9151             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9152             break;
9153           case MachinePlaysWhite:
9154           case IcsPlayingWhite:
9155             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9156             break;
9157           case AnalyzeMode:
9158           case AnalyzeFile:
9159             break;
9160           case IcsObserving: /* [DM] icsEngineAnalyze */
9161             if (!appData.icsEngineAnalyze) ignore = TRUE;
9162             break;
9163           case TwoMachinesPlay:
9164             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9165                 ignore = TRUE;
9166             }
9167             break;
9168           default:
9169             ignore = TRUE;
9170             break;
9171         }
9172
9173         if (!ignore) {
9174             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9175             buf1[0] = NULLCHAR;
9176             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9177                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9178
9179                 if (plyext != ' ' && plyext != '\t') {
9180                     time *= 100;
9181                 }
9182
9183                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9184                 if( cps->scoreIsAbsolute &&
9185                     ( gameMode == MachinePlaysBlack ||
9186                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9187                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
9188                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9189                      !WhiteOnMove(currentMove)
9190                     ) )
9191                 {
9192                     curscore = -curscore;
9193                 }
9194
9195                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9196
9197                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9198                         char buf[MSG_SIZ];
9199                         FILE *f;
9200                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9201                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9202                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9203                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9204                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
9205                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9206                                 fclose(f);
9207                         }
9208                         else
9209                           /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9210                           DisplayError(_("failed writing PV"), 0);
9211                 }
9212
9213                 tempStats.depth = plylev;
9214                 tempStats.nodes = nodes;
9215                 tempStats.time = time;
9216                 tempStats.score = curscore;
9217                 tempStats.got_only_move = 0;
9218
9219                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9220                         int ticklen;
9221
9222                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
9223                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9224                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9225                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9226                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9227                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9228                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9229                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9230                 }
9231
9232                 /* Buffer overflow protection */
9233                 if (pv[0] != NULLCHAR) {
9234                     if (strlen(pv) >= sizeof(tempStats.movelist)
9235                         && appData.debugMode) {
9236                         fprintf(debugFP,
9237                                 "PV is too long; using the first %u bytes.\n",
9238                                 (unsigned) sizeof(tempStats.movelist) - 1);
9239                     }
9240
9241                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9242                 } else {
9243                     sprintf(tempStats.movelist, " no PV\n");
9244                 }
9245
9246                 if (tempStats.seen_stat) {
9247                     tempStats.ok_to_send = 1;
9248                 }
9249
9250                 if (strchr(tempStats.movelist, '(') != NULL) {
9251                     tempStats.line_is_book = 1;
9252                     tempStats.nr_moves = 0;
9253                     tempStats.moves_left = 0;
9254                 } else {
9255                     tempStats.line_is_book = 0;
9256                 }
9257
9258                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9259                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9260
9261                 SendProgramStatsToFrontend( cps, &tempStats );
9262
9263                 /*
9264                     [AS] Protect the thinkOutput buffer from overflow... this
9265                     is only useful if buf1 hasn't overflowed first!
9266                 */
9267                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
9268                          plylev,
9269                          (gameMode == TwoMachinesPlay ?
9270                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9271                          ((double) curscore) / 100.0,
9272                          prefixHint ? lastHint : "",
9273                          prefixHint ? " " : "" );
9274
9275                 if( buf1[0] != NULLCHAR ) {
9276                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9277
9278                     if( strlen(pv) > max_len ) {
9279                         if( appData.debugMode) {
9280                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9281                         }
9282                         pv[max_len+1] = '\0';
9283                     }
9284
9285                     strcat( thinkOutput, pv);
9286                 }
9287
9288                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9289                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9290                     DisplayMove(currentMove - 1);
9291                 }
9292                 return;
9293
9294             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9295                 /* crafty (9.25+) says "(only move) <move>"
9296                  * if there is only 1 legal move
9297                  */
9298                 sscanf(p, "(only move) %s", buf1);
9299                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9300                 sprintf(programStats.movelist, "%s (only move)", buf1);
9301                 programStats.depth = 1;
9302                 programStats.nr_moves = 1;
9303                 programStats.moves_left = 1;
9304                 programStats.nodes = 1;
9305                 programStats.time = 1;
9306                 programStats.got_only_move = 1;
9307
9308                 /* Not really, but we also use this member to
9309                    mean "line isn't going to change" (Crafty
9310                    isn't searching, so stats won't change) */
9311                 programStats.line_is_book = 1;
9312
9313                 SendProgramStatsToFrontend( cps, &programStats );
9314
9315                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9316                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9317                     DisplayMove(currentMove - 1);
9318                 }
9319                 return;
9320             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9321                               &time, &nodes, &plylev, &mvleft,
9322                               &mvtot, mvname) >= 5) {
9323                 /* The stat01: line is from Crafty (9.29+) in response
9324                    to the "." command */
9325                 programStats.seen_stat = 1;
9326                 cps->maybeThinking = TRUE;
9327
9328                 if (programStats.got_only_move || !appData.periodicUpdates)
9329                   return;
9330
9331                 programStats.depth = plylev;
9332                 programStats.time = time;
9333                 programStats.nodes = nodes;
9334                 programStats.moves_left = mvleft;
9335                 programStats.nr_moves = mvtot;
9336                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9337                 programStats.ok_to_send = 1;
9338                 programStats.movelist[0] = '\0';
9339
9340                 SendProgramStatsToFrontend( cps, &programStats );
9341
9342                 return;
9343
9344             } else if (strncmp(message,"++",2) == 0) {
9345                 /* Crafty 9.29+ outputs this */
9346                 programStats.got_fail = 2;
9347                 return;
9348
9349             } else if (strncmp(message,"--",2) == 0) {
9350                 /* Crafty 9.29+ outputs this */
9351                 programStats.got_fail = 1;
9352                 return;
9353
9354             } else if (thinkOutput[0] != NULLCHAR &&
9355                        strncmp(message, "    ", 4) == 0) {
9356                 unsigned message_len;
9357
9358                 p = message;
9359                 while (*p && *p == ' ') p++;
9360
9361                 message_len = strlen( p );
9362
9363                 /* [AS] Avoid buffer overflow */
9364                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9365                     strcat(thinkOutput, " ");
9366                     strcat(thinkOutput, p);
9367                 }
9368
9369                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9370                     strcat(programStats.movelist, " ");
9371                     strcat(programStats.movelist, p);
9372                 }
9373
9374                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9375                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9376                     DisplayMove(currentMove - 1);
9377                 }
9378                 return;
9379             }
9380         }
9381         else {
9382             buf1[0] = NULLCHAR;
9383
9384             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9385                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9386             {
9387                 ChessProgramStats cpstats;
9388
9389                 if (plyext != ' ' && plyext != '\t') {
9390                     time *= 100;
9391                 }
9392
9393                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9394                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9395                     curscore = -curscore;
9396                 }
9397
9398                 cpstats.depth = plylev;
9399                 cpstats.nodes = nodes;
9400                 cpstats.time = time;
9401                 cpstats.score = curscore;
9402                 cpstats.got_only_move = 0;
9403                 cpstats.movelist[0] = '\0';
9404
9405                 if (buf1[0] != NULLCHAR) {
9406                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9407                 }
9408
9409                 cpstats.ok_to_send = 0;
9410                 cpstats.line_is_book = 0;
9411                 cpstats.nr_moves = 0;
9412                 cpstats.moves_left = 0;
9413
9414                 SendProgramStatsToFrontend( cps, &cpstats );
9415             }
9416         }
9417     }
9418 }
9419
9420
9421 /* Parse a game score from the character string "game", and
9422    record it as the history of the current game.  The game
9423    score is NOT assumed to start from the standard position.
9424    The display is not updated in any way.
9425    */
9426 void
9427 ParseGameHistory (char *game)
9428 {
9429     ChessMove moveType;
9430     int fromX, fromY, toX, toY, boardIndex;
9431     char promoChar;
9432     char *p, *q;
9433     char buf[MSG_SIZ];
9434
9435     if (appData.debugMode)
9436       fprintf(debugFP, "Parsing game history: %s\n", game);
9437
9438     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9439     gameInfo.site = StrSave(appData.icsHost);
9440     gameInfo.date = PGNDate();
9441     gameInfo.round = StrSave("-");
9442
9443     /* Parse out names of players */
9444     while (*game == ' ') game++;
9445     p = buf;
9446     while (*game != ' ') *p++ = *game++;
9447     *p = NULLCHAR;
9448     gameInfo.white = StrSave(buf);
9449     while (*game == ' ') game++;
9450     p = buf;
9451     while (*game != ' ' && *game != '\n') *p++ = *game++;
9452     *p = NULLCHAR;
9453     gameInfo.black = StrSave(buf);
9454
9455     /* Parse moves */
9456     boardIndex = blackPlaysFirst ? 1 : 0;
9457     yynewstr(game);
9458     for (;;) {
9459         yyboardindex = boardIndex;
9460         moveType = (ChessMove) Myylex();
9461         switch (moveType) {
9462           case IllegalMove:             /* maybe suicide chess, etc. */
9463   if (appData.debugMode) {
9464     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9465     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9466     setbuf(debugFP, NULL);
9467   }
9468           case WhitePromotion:
9469           case BlackPromotion:
9470           case WhiteNonPromotion:
9471           case BlackNonPromotion:
9472           case NormalMove:
9473           case WhiteCapturesEnPassant:
9474           case BlackCapturesEnPassant:
9475           case WhiteKingSideCastle:
9476           case WhiteQueenSideCastle:
9477           case BlackKingSideCastle:
9478           case BlackQueenSideCastle:
9479           case WhiteKingSideCastleWild:
9480           case WhiteQueenSideCastleWild:
9481           case BlackKingSideCastleWild:
9482           case BlackQueenSideCastleWild:
9483           /* PUSH Fabien */
9484           case WhiteHSideCastleFR:
9485           case WhiteASideCastleFR:
9486           case BlackHSideCastleFR:
9487           case BlackASideCastleFR:
9488           /* POP Fabien */
9489             fromX = currentMoveString[0] - AAA;
9490             fromY = currentMoveString[1] - ONE;
9491             toX = currentMoveString[2] - AAA;
9492             toY = currentMoveString[3] - ONE;
9493             promoChar = currentMoveString[4];
9494             break;
9495           case WhiteDrop:
9496           case BlackDrop:
9497             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9498             fromX = moveType == WhiteDrop ?
9499               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9500             (int) CharToPiece(ToLower(currentMoveString[0]));
9501             fromY = DROP_RANK;
9502             toX = currentMoveString[2] - AAA;
9503             toY = currentMoveString[3] - ONE;
9504             promoChar = NULLCHAR;
9505             break;
9506           case AmbiguousMove:
9507             /* bug? */
9508             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9509   if (appData.debugMode) {
9510     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9511     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9512     setbuf(debugFP, NULL);
9513   }
9514             DisplayError(buf, 0);
9515             return;
9516           case ImpossibleMove:
9517             /* bug? */
9518             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9519   if (appData.debugMode) {
9520     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9521     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9522     setbuf(debugFP, NULL);
9523   }
9524             DisplayError(buf, 0);
9525             return;
9526           case EndOfFile:
9527             if (boardIndex < backwardMostMove) {
9528                 /* Oops, gap.  How did that happen? */
9529                 DisplayError(_("Gap in move list"), 0);
9530                 return;
9531             }
9532             backwardMostMove =  blackPlaysFirst ? 1 : 0;
9533             if (boardIndex > forwardMostMove) {
9534                 forwardMostMove = boardIndex;
9535             }
9536             return;
9537           case ElapsedTime:
9538             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9539                 strcat(parseList[boardIndex-1], " ");
9540                 strcat(parseList[boardIndex-1], yy_text);
9541             }
9542             continue;
9543           case Comment:
9544           case PGNTag:
9545           case NAG:
9546           default:
9547             /* ignore */
9548             continue;
9549           case WhiteWins:
9550           case BlackWins:
9551           case GameIsDrawn:
9552           case GameUnfinished:
9553             if (gameMode == IcsExamining) {
9554                 if (boardIndex < backwardMostMove) {
9555                     /* Oops, gap.  How did that happen? */
9556                     return;
9557                 }
9558                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9559                 return;
9560             }
9561             gameInfo.result = moveType;
9562             p = strchr(yy_text, '{');
9563             if (p == NULL) p = strchr(yy_text, '(');
9564             if (p == NULL) {
9565                 p = yy_text;
9566                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9567             } else {
9568                 q = strchr(p, *p == '{' ? '}' : ')');
9569                 if (q != NULL) *q = NULLCHAR;
9570                 p++;
9571             }
9572             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9573             gameInfo.resultDetails = StrSave(p);
9574             continue;
9575         }
9576         if (boardIndex >= forwardMostMove &&
9577             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9578             backwardMostMove = blackPlaysFirst ? 1 : 0;
9579             return;
9580         }
9581         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9582                                  fromY, fromX, toY, toX, promoChar,
9583                                  parseList[boardIndex]);
9584         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9585         /* currentMoveString is set as a side-effect of yylex */
9586         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9587         strcat(moveList[boardIndex], "\n");
9588         boardIndex++;
9589         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9590         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9591           case MT_NONE:
9592           case MT_STALEMATE:
9593           default:
9594             break;
9595           case MT_CHECK:
9596             if(gameInfo.variant != VariantShogi)
9597                 strcat(parseList[boardIndex - 1], "+");
9598             break;
9599           case MT_CHECKMATE:
9600           case MT_STAINMATE:
9601             strcat(parseList[boardIndex - 1], "#");
9602             break;
9603         }
9604     }
9605 }
9606
9607
9608 /* Apply a move to the given board  */
9609 void
9610 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9611 {
9612   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9613   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9614
9615     /* [HGM] compute & store e.p. status and castling rights for new position */
9616     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9617
9618       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9619       oldEP = (signed char)board[EP_STATUS];
9620       board[EP_STATUS] = EP_NONE;
9621
9622   if (fromY == DROP_RANK) {
9623         /* must be first */
9624         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9625             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9626             return;
9627         }
9628         piece = board[toY][toX] = (ChessSquare) fromX;
9629   } else {
9630       int i;
9631
9632       if( killX >= 0 && killY >= 0 ) // [HGM] lion: Lion trampled over something
9633            board[killY][killX] = EmptySquare,
9634            board[EP_STATUS] = EP_CAPTURE;
9635
9636       if( board[toY][toX] != EmptySquare )
9637            board[EP_STATUS] = EP_CAPTURE;
9638
9639       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9640            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9641                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9642       } else
9643       if( board[fromY][fromX] == WhitePawn ) {
9644            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9645                board[EP_STATUS] = EP_PAWN_MOVE;
9646            if( toY-fromY==2) {
9647                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9648                         gameInfo.variant != VariantBerolina || toX < fromX)
9649                       board[EP_STATUS] = toX | berolina;
9650                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9651                         gameInfo.variant != VariantBerolina || toX > fromX)
9652                       board[EP_STATUS] = toX;
9653            }
9654       } else
9655       if( board[fromY][fromX] == BlackPawn ) {
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] == WhitePawn &&
9660                         gameInfo.variant != VariantBerolina || toX < fromX)
9661                       board[EP_STATUS] = toX | berolina;
9662                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9663                         gameInfo.variant != VariantBerolina || toX > fromX)
9664                       board[EP_STATUS] = toX;
9665            }
9666        }
9667
9668        for(i=0; i<nrCastlingRights; i++) {
9669            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9670               board[CASTLING][i] == toX   && castlingRank[i] == toY
9671              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9672        }
9673
9674        if(gameInfo.variant == VariantSChess) { // update virginity
9675            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
9676            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
9677            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
9678            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
9679        }
9680
9681      if (fromX == toX && fromY == toY) return;
9682
9683      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9684      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9685      if(gameInfo.variant == VariantKnightmate)
9686          king += (int) WhiteUnicorn - (int) WhiteKing;
9687
9688     /* Code added by Tord: */
9689     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9690     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9691         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9692       board[fromY][fromX] = EmptySquare;
9693       board[toY][toX] = EmptySquare;
9694       if((toX > fromX) != (piece == WhiteRook)) {
9695         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9696       } else {
9697         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9698       }
9699     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9700                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9701       board[fromY][fromX] = EmptySquare;
9702       board[toY][toX] = EmptySquare;
9703       if((toX > fromX) != (piece == BlackRook)) {
9704         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9705       } else {
9706         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9707       }
9708     /* End of code added by Tord */
9709
9710     } else if (board[fromY][fromX] == king
9711         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9712         && toY == fromY && toX > fromX+1) {
9713         board[fromY][fromX] = EmptySquare;
9714         board[toY][toX] = king;
9715         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9716         board[fromY][BOARD_RGHT-1] = EmptySquare;
9717     } else if (board[fromY][fromX] == king
9718         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9719                && toY == fromY && toX < fromX-1) {
9720         board[fromY][fromX] = EmptySquare;
9721         board[toY][toX] = king;
9722         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9723         board[fromY][BOARD_LEFT] = EmptySquare;
9724     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9725                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9726                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9727                ) {
9728         /* white pawn promotion */
9729         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9730         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9731             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9732         board[fromY][fromX] = EmptySquare;
9733     } else if ((fromY >= BOARD_HEIGHT>>1)
9734                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9735                && (toX != fromX)
9736                && gameInfo.variant != VariantXiangqi
9737                && gameInfo.variant != VariantBerolina
9738                && (board[fromY][fromX] == WhitePawn)
9739                && (board[toY][toX] == EmptySquare)) {
9740         board[fromY][fromX] = EmptySquare;
9741         board[toY][toX] = WhitePawn;
9742         captured = board[toY - 1][toX];
9743         board[toY - 1][toX] = EmptySquare;
9744     } else if ((fromY == BOARD_HEIGHT-4)
9745                && (toX == fromX)
9746                && gameInfo.variant == VariantBerolina
9747                && (board[fromY][fromX] == WhitePawn)
9748                && (board[toY][toX] == EmptySquare)) {
9749         board[fromY][fromX] = EmptySquare;
9750         board[toY][toX] = WhitePawn;
9751         if(oldEP & EP_BEROLIN_A) {
9752                 captured = board[fromY][fromX-1];
9753                 board[fromY][fromX-1] = EmptySquare;
9754         }else{  captured = board[fromY][fromX+1];
9755                 board[fromY][fromX+1] = EmptySquare;
9756         }
9757     } else if (board[fromY][fromX] == king
9758         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9759                && toY == fromY && toX > fromX+1) {
9760         board[fromY][fromX] = EmptySquare;
9761         board[toY][toX] = king;
9762         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9763         board[fromY][BOARD_RGHT-1] = EmptySquare;
9764     } else if (board[fromY][fromX] == king
9765         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9766                && toY == fromY && toX < fromX-1) {
9767         board[fromY][fromX] = EmptySquare;
9768         board[toY][toX] = king;
9769         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9770         board[fromY][BOARD_LEFT] = EmptySquare;
9771     } else if (fromY == 7 && fromX == 3
9772                && board[fromY][fromX] == BlackKing
9773                && toY == 7 && toX == 5) {
9774         board[fromY][fromX] = EmptySquare;
9775         board[toY][toX] = BlackKing;
9776         board[fromY][7] = EmptySquare;
9777         board[toY][4] = BlackRook;
9778     } else if (fromY == 7 && fromX == 3
9779                && board[fromY][fromX] == BlackKing
9780                && toY == 7 && toX == 1) {
9781         board[fromY][fromX] = EmptySquare;
9782         board[toY][toX] = BlackKing;
9783         board[fromY][0] = EmptySquare;
9784         board[toY][2] = BlackRook;
9785     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9786                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9787                && toY < promoRank && promoChar
9788                ) {
9789         /* black pawn promotion */
9790         board[toY][toX] = CharToPiece(ToLower(promoChar));
9791         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9792             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9793         board[fromY][fromX] = EmptySquare;
9794     } else if ((fromY < BOARD_HEIGHT>>1)
9795                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9796                && (toX != fromX)
9797                && gameInfo.variant != VariantXiangqi
9798                && gameInfo.variant != VariantBerolina
9799                && (board[fromY][fromX] == BlackPawn)
9800                && (board[toY][toX] == EmptySquare)) {
9801         board[fromY][fromX] = EmptySquare;
9802         board[toY][toX] = BlackPawn;
9803         captured = board[toY + 1][toX];
9804         board[toY + 1][toX] = EmptySquare;
9805     } else if ((fromY == 3)
9806                && (toX == fromX)
9807                && gameInfo.variant == VariantBerolina
9808                && (board[fromY][fromX] == BlackPawn)
9809                && (board[toY][toX] == EmptySquare)) {
9810         board[fromY][fromX] = EmptySquare;
9811         board[toY][toX] = BlackPawn;
9812         if(oldEP & EP_BEROLIN_A) {
9813                 captured = board[fromY][fromX-1];
9814                 board[fromY][fromX-1] = EmptySquare;
9815         }else{  captured = board[fromY][fromX+1];
9816                 board[fromY][fromX+1] = EmptySquare;
9817         }
9818     } else {
9819         ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
9820         board[fromY][fromX] = EmptySquare;
9821         board[toY][toX] = piece;
9822     }
9823   }
9824
9825     if (gameInfo.holdingsWidth != 0) {
9826
9827       /* !!A lot more code needs to be written to support holdings  */
9828       /* [HGM] OK, so I have written it. Holdings are stored in the */
9829       /* penultimate board files, so they are automaticlly stored   */
9830       /* in the game history.                                       */
9831       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9832                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9833         /* Delete from holdings, by decreasing count */
9834         /* and erasing image if necessary            */
9835         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9836         if(p < (int) BlackPawn) { /* white drop */
9837              p -= (int)WhitePawn;
9838                  p = PieceToNumber((ChessSquare)p);
9839              if(p >= gameInfo.holdingsSize) p = 0;
9840              if(--board[p][BOARD_WIDTH-2] <= 0)
9841                   board[p][BOARD_WIDTH-1] = EmptySquare;
9842              if((int)board[p][BOARD_WIDTH-2] < 0)
9843                         board[p][BOARD_WIDTH-2] = 0;
9844         } else {                  /* black drop */
9845              p -= (int)BlackPawn;
9846                  p = PieceToNumber((ChessSquare)p);
9847              if(p >= gameInfo.holdingsSize) p = 0;
9848              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9849                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9850              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9851                         board[BOARD_HEIGHT-1-p][1] = 0;
9852         }
9853       }
9854       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9855           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9856         /* [HGM] holdings: Add to holdings, if holdings exist */
9857         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9858                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9859                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9860         }
9861         p = (int) captured;
9862         if (p >= (int) BlackPawn) {
9863           p -= (int)BlackPawn;
9864           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9865                   /* in Shogi restore piece to its original  first */
9866                   captured = (ChessSquare) (DEMOTED captured);
9867                   p = DEMOTED p;
9868           }
9869           p = PieceToNumber((ChessSquare)p);
9870           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9871           board[p][BOARD_WIDTH-2]++;
9872           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9873         } else {
9874           p -= (int)WhitePawn;
9875           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9876                   captured = (ChessSquare) (DEMOTED captured);
9877                   p = DEMOTED p;
9878           }
9879           p = PieceToNumber((ChessSquare)p);
9880           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9881           board[BOARD_HEIGHT-1-p][1]++;
9882           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9883         }
9884       }
9885     } else if (gameInfo.variant == VariantAtomic) {
9886       if (captured != EmptySquare) {
9887         int y, x;
9888         for (y = toY-1; y <= toY+1; y++) {
9889           for (x = toX-1; x <= toX+1; x++) {
9890             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9891                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9892               board[y][x] = EmptySquare;
9893             }
9894           }
9895         }
9896         board[toY][toX] = EmptySquare;
9897       }
9898     }
9899     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9900         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9901     } else
9902     if(promoChar == '+') {
9903         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
9904         board[toY][toX] = (ChessSquare) (CHUPROMOTED piece);
9905     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9906         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9907         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
9908            && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
9909         board[toY][toX] = newPiece;
9910     }
9911     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9912                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9913         // [HGM] superchess: take promotion piece out of holdings
9914         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9915         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9916             if(!--board[k][BOARD_WIDTH-2])
9917                 board[k][BOARD_WIDTH-1] = EmptySquare;
9918         } else {
9919             if(!--board[BOARD_HEIGHT-1-k][1])
9920                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9921         }
9922     }
9923
9924 }
9925
9926 /* Updates forwardMostMove */
9927 void
9928 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
9929 {
9930     int x = toX, y = toY;
9931     char *s = parseList[forwardMostMove];
9932     ChessSquare p = boards[forwardMostMove][toY][toX];
9933 //    forwardMostMove++; // [HGM] bare: moved downstream
9934
9935     if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
9936     (void) CoordsToAlgebraic(boards[forwardMostMove],
9937                              PosFlags(forwardMostMove),
9938                              fromY, fromX, y, x, promoChar,
9939                              s);
9940     if(killX >= 0 && killY >= 0)
9941         sprintf(s + strlen(s), "%c%c%d", p == EmptySquare || toX == fromX && toY == fromY ? '-' : 'x', toX + AAA, toY + ONE - '0');
9942
9943     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9944         int timeLeft; static int lastLoadFlag=0; int king, piece;
9945         piece = boards[forwardMostMove][fromY][fromX];
9946         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9947         if(gameInfo.variant == VariantKnightmate)
9948             king += (int) WhiteUnicorn - (int) WhiteKing;
9949         if(forwardMostMove == 0) {
9950             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9951                 fprintf(serverMoves, "%s;", UserName());
9952             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9953                 fprintf(serverMoves, "%s;", second.tidy);
9954             fprintf(serverMoves, "%s;", first.tidy);
9955             if(gameMode == MachinePlaysWhite)
9956                 fprintf(serverMoves, "%s;", UserName());
9957             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9958                 fprintf(serverMoves, "%s;", second.tidy);
9959         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9960         lastLoadFlag = loadFlag;
9961         // print base move
9962         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9963         // print castling suffix
9964         if( toY == fromY && piece == king ) {
9965             if(toX-fromX > 1)
9966                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9967             if(fromX-toX >1)
9968                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9969         }
9970         // e.p. suffix
9971         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9972              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9973              boards[forwardMostMove][toY][toX] == EmptySquare
9974              && fromX != toX && fromY != toY)
9975                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9976         // promotion suffix
9977         if(promoChar != NULLCHAR) {
9978             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
9979                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
9980                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
9981             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
9982         }
9983         if(!loadFlag) {
9984                 char buf[MOVE_LEN*2], *p; int len;
9985             fprintf(serverMoves, "/%d/%d",
9986                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9987             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9988             else                      timeLeft = blackTimeRemaining/1000;
9989             fprintf(serverMoves, "/%d", timeLeft);
9990                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
9991                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
9992                 if(p = strchr(buf, '=')) *p = NULLCHAR;
9993                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
9994             fprintf(serverMoves, "/%s", buf);
9995         }
9996         fflush(serverMoves);
9997     }
9998
9999     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10000         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10001       return;
10002     }
10003     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10004     if (commentList[forwardMostMove+1] != NULL) {
10005         free(commentList[forwardMostMove+1]);
10006         commentList[forwardMostMove+1] = NULL;
10007     }
10008     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10009     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10010     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10011     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10012     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10013     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10014     adjustedClock = FALSE;
10015     gameInfo.result = GameUnfinished;
10016     if (gameInfo.resultDetails != NULL) {
10017         free(gameInfo.resultDetails);
10018         gameInfo.resultDetails = NULL;
10019     }
10020     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10021                               moveList[forwardMostMove - 1]);
10022     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10023       case MT_NONE:
10024       case MT_STALEMATE:
10025       default:
10026         break;
10027       case MT_CHECK:
10028         if(gameInfo.variant != VariantShogi)
10029             strcat(parseList[forwardMostMove - 1], "+");
10030         break;
10031       case MT_CHECKMATE:
10032       case MT_STAINMATE:
10033         strcat(parseList[forwardMostMove - 1], "#");
10034         break;
10035     }
10036
10037     killX = killY = -1; // [HGM] lion: used up
10038 }
10039
10040 /* Updates currentMove if not pausing */
10041 void
10042 ShowMove (int fromX, int fromY, int toX, int toY)
10043 {
10044     int instant = (gameMode == PlayFromGameFile) ?
10045         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10046     if(appData.noGUI) return;
10047     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10048         if (!instant) {
10049             if (forwardMostMove == currentMove + 1) {
10050                 AnimateMove(boards[forwardMostMove - 1],
10051                             fromX, fromY, toX, toY);
10052             }
10053         }
10054         currentMove = forwardMostMove;
10055     }
10056
10057     if (instant) return;
10058
10059     DisplayMove(currentMove - 1);
10060     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10061             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10062                 SetHighlights(fromX, fromY, toX, toY);
10063             }
10064     }
10065     DrawPosition(FALSE, boards[currentMove]);
10066     DisplayBothClocks();
10067     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10068 }
10069
10070 void
10071 SendEgtPath (ChessProgramState *cps)
10072 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10073         char buf[MSG_SIZ], name[MSG_SIZ], *p;
10074
10075         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10076
10077         while(*p) {
10078             char c, *q = name+1, *r, *s;
10079
10080             name[0] = ','; // extract next format name from feature and copy with prefixed ','
10081             while(*p && *p != ',') *q++ = *p++;
10082             *q++ = ':'; *q = 0;
10083             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10084                 strcmp(name, ",nalimov:") == 0 ) {
10085                 // take nalimov path from the menu-changeable option first, if it is defined
10086               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10087                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
10088             } else
10089             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10090                 (s = StrStr(appData.egtFormats, name)) != NULL) {
10091                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10092                 s = r = StrStr(s, ":") + 1; // beginning of path info
10093                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10094                 c = *r; *r = 0;             // temporarily null-terminate path info
10095                     *--q = 0;               // strip of trailig ':' from name
10096                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10097                 *r = c;
10098                 SendToProgram(buf,cps);     // send egtbpath command for this format
10099             }
10100             if(*p == ',') p++; // read away comma to position for next format name
10101         }
10102 }
10103
10104 static int
10105 NonStandardBoardSize ()
10106 {
10107       /* [HGM] Awkward testing. Should really be a table */
10108       int overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
10109       if( gameInfo.variant == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10110       if( gameInfo.variant == VariantXiangqi )
10111            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
10112       if( gameInfo.variant == VariantShogi )
10113            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
10114       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
10115            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
10116       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
10117           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
10118            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
10119       if( gameInfo.variant == VariantCourier )
10120            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
10121       if( gameInfo.variant == VariantSuper )
10122            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
10123       if( gameInfo.variant == VariantGreat )
10124            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
10125       if( gameInfo.variant == VariantSChess )
10126            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
10127       if( gameInfo.variant == VariantGrand )
10128            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
10129       if( gameInfo.variant == VariantChu )
10130            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 12 || gameInfo.holdingsSize != 0;
10131       return overruled;
10132 }
10133
10134 void
10135 InitChessProgram (ChessProgramState *cps, int setup)
10136 /* setup needed to setup FRC opening position */
10137 {
10138     char buf[MSG_SIZ], b[MSG_SIZ];
10139     if (appData.noChessProgram) return;
10140     hintRequested = FALSE;
10141     bookRequested = FALSE;
10142
10143     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10144     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10145     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10146     if(cps->memSize) { /* [HGM] memory */
10147       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10148         SendToProgram(buf, cps);
10149     }
10150     SendEgtPath(cps); /* [HGM] EGT */
10151     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10152       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10153         SendToProgram(buf, cps);
10154     }
10155
10156     SendToProgram(cps->initString, cps);
10157     if (gameInfo.variant != VariantNormal &&
10158         gameInfo.variant != VariantLoadable
10159         /* [HGM] also send variant if board size non-standard */
10160         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
10161                                             ) {
10162       char *v = VariantName(gameInfo.variant);
10163       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
10164         /* [HGM] in protocol 1 we have to assume all variants valid */
10165         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
10166         DisplayFatalError(buf, 0, 1);
10167         return;
10168       }
10169
10170       if(NonStandardBoardSize()) { /* [HGM] make prefix for non-standard board size. */
10171         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
10172                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
10173            /* [HGM] varsize: try first if this defiant size variant is specifically known */
10174            if(StrStr(cps->variants, b) == NULL) {
10175                // specific sized variant not known, check if general sizing allowed
10176                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
10177                    if(StrStr(cps->variants, "boardsize") == NULL) {
10178                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10179                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
10180                        DisplayFatalError(buf, 0, 1);
10181                        return;
10182                    }
10183                    /* [HGM] here we really should compare with the maximum supported board size */
10184                }
10185            }
10186       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
10187       snprintf(buf, MSG_SIZ, "variant %s\n", b);
10188       SendToProgram(buf, cps);
10189     }
10190     currentlyInitializedVariant = gameInfo.variant;
10191
10192     /* [HGM] send opening position in FRC to first engine */
10193     if(setup) {
10194           SendToProgram("force\n", cps);
10195           SendBoard(cps, 0);
10196           /* engine is now in force mode! Set flag to wake it up after first move. */
10197           setboardSpoiledMachineBlack = 1;
10198     }
10199
10200     if (cps->sendICS) {
10201       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10202       SendToProgram(buf, cps);
10203     }
10204     cps->maybeThinking = FALSE;
10205     cps->offeredDraw = 0;
10206     if (!appData.icsActive) {
10207         SendTimeControl(cps, movesPerSession, timeControl,
10208                         timeIncrement, appData.searchDepth,
10209                         searchTime);
10210     }
10211     if (appData.showThinking
10212         // [HGM] thinking: four options require thinking output to be sent
10213         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10214                                 ) {
10215         SendToProgram("post\n", cps);
10216     }
10217     SendToProgram("hard\n", cps);
10218     if (!appData.ponderNextMove) {
10219         /* Warning: "easy" is a toggle in GNU Chess, so don't send
10220            it without being sure what state we are in first.  "hard"
10221            is not a toggle, so that one is OK.
10222          */
10223         SendToProgram("easy\n", cps);
10224     }
10225     if (cps->usePing) {
10226       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
10227       SendToProgram(buf, cps);
10228     }
10229     cps->initDone = TRUE;
10230     ClearEngineOutputPane(cps == &second);
10231 }
10232
10233
10234 void
10235 ResendOptions (ChessProgramState *cps)
10236 { // send the stored value of the options
10237   int i;
10238   char buf[MSG_SIZ];
10239   Option *opt = cps->option;
10240   for(i=0; i<cps->nrOptions; i++, opt++) {
10241       switch(opt->type) {
10242         case Spin:
10243         case Slider:
10244         case CheckBox:
10245             snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10246           break;
10247         case ComboBox:
10248           snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10249           break;
10250         default:
10251             snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10252           break;
10253         case Button:
10254         case SaveButton:
10255           continue;
10256       }
10257       SendToProgram(buf, cps);
10258   }
10259 }
10260
10261 void
10262 StartChessProgram (ChessProgramState *cps)
10263 {
10264     char buf[MSG_SIZ];
10265     int err;
10266
10267     if (appData.noChessProgram) return;
10268     cps->initDone = FALSE;
10269
10270     if (strcmp(cps->host, "localhost") == 0) {
10271         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10272     } else if (*appData.remoteShell == NULLCHAR) {
10273         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10274     } else {
10275         if (*appData.remoteUser == NULLCHAR) {
10276           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10277                     cps->program);
10278         } else {
10279           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10280                     cps->host, appData.remoteUser, cps->program);
10281         }
10282         err = StartChildProcess(buf, "", &cps->pr);
10283     }
10284
10285     if (err != 0) {
10286       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10287         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10288         if(cps != &first) return;
10289         appData.noChessProgram = TRUE;
10290         ThawUI();
10291         SetNCPMode();
10292 //      DisplayFatalError(buf, err, 1);
10293 //      cps->pr = NoProc;
10294 //      cps->isr = NULL;
10295         return;
10296     }
10297
10298     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10299     if (cps->protocolVersion > 1) {
10300       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10301       if(!cps->reload) { // do not clear options when reloading because of -xreuse
10302         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10303         cps->comboCnt = 0;  //                and values of combo boxes
10304       }
10305       SendToProgram(buf, cps);
10306       if(cps->reload) ResendOptions(cps);
10307     } else {
10308       SendToProgram("xboard\n", cps);
10309     }
10310 }
10311
10312 void
10313 TwoMachinesEventIfReady P((void))
10314 {
10315   static int curMess = 0;
10316   if (first.lastPing != first.lastPong) {
10317     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10318     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10319     return;
10320   }
10321   if (second.lastPing != second.lastPong) {
10322     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10323     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10324     return;
10325   }
10326   DisplayMessage("", ""); curMess = 0;
10327   TwoMachinesEvent();
10328 }
10329
10330 char *
10331 MakeName (char *template)
10332 {
10333     time_t clock;
10334     struct tm *tm;
10335     static char buf[MSG_SIZ];
10336     char *p = buf;
10337     int i;
10338
10339     clock = time((time_t *)NULL);
10340     tm = localtime(&clock);
10341
10342     while(*p++ = *template++) if(p[-1] == '%') {
10343         switch(*template++) {
10344           case 0:   *p = 0; return buf;
10345           case 'Y': i = tm->tm_year+1900; break;
10346           case 'y': i = tm->tm_year-100; break;
10347           case 'M': i = tm->tm_mon+1; break;
10348           case 'd': i = tm->tm_mday; break;
10349           case 'h': i = tm->tm_hour; break;
10350           case 'm': i = tm->tm_min; break;
10351           case 's': i = tm->tm_sec; break;
10352           default:  i = 0;
10353         }
10354         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10355     }
10356     return buf;
10357 }
10358
10359 int
10360 CountPlayers (char *p)
10361 {
10362     int n = 0;
10363     while(p = strchr(p, '\n')) p++, n++; // count participants
10364     return n;
10365 }
10366
10367 FILE *
10368 WriteTourneyFile (char *results, FILE *f)
10369 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10370     if(f == NULL) f = fopen(appData.tourneyFile, "w");
10371     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10372         // create a file with tournament description
10373         fprintf(f, "-participants {%s}\n", appData.participants);
10374         fprintf(f, "-seedBase %d\n", appData.seedBase);
10375         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10376         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10377         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10378         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10379         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10380         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10381         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10382         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10383         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10384         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10385         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10386         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10387         fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
10388         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10389         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10390         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10391         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10392         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10393         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10394         fprintf(f, "-smpCores %d\n", appData.smpCores);
10395         if(searchTime > 0)
10396                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10397         else {
10398                 fprintf(f, "-mps %d\n", appData.movesPerSession);
10399                 fprintf(f, "-tc %s\n", appData.timeControl);
10400                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10401         }
10402         fprintf(f, "-results \"%s\"\n", results);
10403     }
10404     return f;
10405 }
10406
10407 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10408
10409 void
10410 Substitute (char *participants, int expunge)
10411 {
10412     int i, changed, changes=0, nPlayers=0;
10413     char *p, *q, *r, buf[MSG_SIZ];
10414     if(participants == NULL) return;
10415     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10416     r = p = participants; q = appData.participants;
10417     while(*p && *p == *q) {
10418         if(*p == '\n') r = p+1, nPlayers++;
10419         p++; q++;
10420     }
10421     if(*p) { // difference
10422         while(*p && *p++ != '\n');
10423         while(*q && *q++ != '\n');
10424       changed = nPlayers;
10425         changes = 1 + (strcmp(p, q) != 0);
10426     }
10427     if(changes == 1) { // a single engine mnemonic was changed
10428         q = r; while(*q) nPlayers += (*q++ == '\n');
10429         p = buf; while(*r && (*p = *r++) != '\n') p++;
10430         *p = NULLCHAR;
10431         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10432         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10433         if(mnemonic[i]) { // The substitute is valid
10434             FILE *f;
10435             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10436                 flock(fileno(f), LOCK_EX);
10437                 ParseArgsFromFile(f);
10438                 fseek(f, 0, SEEK_SET);
10439                 FREE(appData.participants); appData.participants = participants;
10440                 if(expunge) { // erase results of replaced engine
10441                     int len = strlen(appData.results), w, b, dummy;
10442                     for(i=0; i<len; i++) {
10443                         Pairing(i, nPlayers, &w, &b, &dummy);
10444                         if((w == changed || b == changed) && appData.results[i] == '*') {
10445                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10446                             fclose(f);
10447                             return;
10448                         }
10449                     }
10450                     for(i=0; i<len; i++) {
10451                         Pairing(i, nPlayers, &w, &b, &dummy);
10452                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10453                     }
10454                 }
10455                 WriteTourneyFile(appData.results, f);
10456                 fclose(f); // release lock
10457                 return;
10458             }
10459         } else DisplayError(_("No engine with the name you gave is installed"), 0);
10460     }
10461     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10462     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
10463     free(participants);
10464     return;
10465 }
10466
10467 int
10468 CheckPlayers (char *participants)
10469 {
10470         int i;
10471         char buf[MSG_SIZ], *p;
10472         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10473         while(p = strchr(participants, '\n')) {
10474             *p = NULLCHAR;
10475             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10476             if(!mnemonic[i]) {
10477                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10478                 *p = '\n';
10479                 DisplayError(buf, 0);
10480                 return 1;
10481             }
10482             *p = '\n';
10483             participants = p + 1;
10484         }
10485         return 0;
10486 }
10487
10488 int
10489 CreateTourney (char *name)
10490 {
10491         FILE *f;
10492         if(matchMode && strcmp(name, appData.tourneyFile)) {
10493              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10494         }
10495         if(name[0] == NULLCHAR) {
10496             if(appData.participants[0])
10497                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10498             return 0;
10499         }
10500         f = fopen(name, "r");
10501         if(f) { // file exists
10502             ASSIGN(appData.tourneyFile, name);
10503             ParseArgsFromFile(f); // parse it
10504         } else {
10505             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10506             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10507                 DisplayError(_("Not enough participants"), 0);
10508                 return 0;
10509             }
10510             if(CheckPlayers(appData.participants)) return 0;
10511             ASSIGN(appData.tourneyFile, name);
10512             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10513             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10514         }
10515         fclose(f);
10516         appData.noChessProgram = FALSE;
10517         appData.clockMode = TRUE;
10518         SetGNUMode();
10519         return 1;
10520 }
10521
10522 int
10523 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10524 {
10525     char buf[MSG_SIZ], *p, *q;
10526     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10527     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10528     skip = !all && group[0]; // if group requested, we start in skip mode
10529     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10530         p = names; q = buf; header = 0;
10531         while(*p && *p != '\n') *q++ = *p++;
10532         *q = 0;
10533         if(*p == '\n') p++;
10534         if(buf[0] == '#') {
10535             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10536             depth++; // we must be entering a new group
10537             if(all) continue; // suppress printing group headers when complete list requested
10538             header = 1;
10539             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10540         }
10541         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10542         if(engineList[i]) free(engineList[i]);
10543         engineList[i] = strdup(buf);
10544         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10545         if(engineMnemonic[i]) free(engineMnemonic[i]);
10546         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10547             strcat(buf, " (");
10548             sscanf(q + 8, "%s", buf + strlen(buf));
10549             strcat(buf, ")");
10550         }
10551         engineMnemonic[i] = strdup(buf);
10552         i++;
10553     }
10554     engineList[i] = engineMnemonic[i] = NULL;
10555     return i;
10556 }
10557
10558 // following implemented as macro to avoid type limitations
10559 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10560
10561 void
10562 SwapEngines (int n)
10563 {   // swap settings for first engine and other engine (so far only some selected options)
10564     int h;
10565     char *p;
10566     if(n == 0) return;
10567     SWAP(directory, p)
10568     SWAP(chessProgram, p)
10569     SWAP(isUCI, h)
10570     SWAP(hasOwnBookUCI, h)
10571     SWAP(protocolVersion, h)
10572     SWAP(reuse, h)
10573     SWAP(scoreIsAbsolute, h)
10574     SWAP(timeOdds, h)
10575     SWAP(logo, p)
10576     SWAP(pgnName, p)
10577     SWAP(pvSAN, h)
10578     SWAP(engOptions, p)
10579     SWAP(engInitString, p)
10580     SWAP(computerString, p)
10581     SWAP(features, p)
10582     SWAP(fenOverride, p)
10583     SWAP(NPS, h)
10584     SWAP(accumulateTC, h)
10585     SWAP(host, p)
10586 }
10587
10588 int
10589 GetEngineLine (char *s, int n)
10590 {
10591     int i;
10592     char buf[MSG_SIZ];
10593     extern char *icsNames;
10594     if(!s || !*s) return 0;
10595     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
10596     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
10597     if(!mnemonic[i]) return 0;
10598     if(n == 11) return 1; // just testing if there was a match
10599     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
10600     if(n == 1) SwapEngines(n);
10601     ParseArgsFromString(buf);
10602     if(n == 1) SwapEngines(n);
10603     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
10604         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
10605         ParseArgsFromString(buf);
10606     }
10607     return 1;
10608 }
10609
10610 int
10611 SetPlayer (int player, char *p)
10612 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
10613     int i;
10614     char buf[MSG_SIZ], *engineName;
10615     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10616     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10617     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10618     if(mnemonic[i]) {
10619         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10620         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10621         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10622         ParseArgsFromString(buf);
10623     } else { // no engine with this nickname is installed!
10624         snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
10625         ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
10626         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10627         ModeHighlight();
10628         DisplayError(buf, 0);
10629         return 0;
10630     }
10631     free(engineName);
10632     return i;
10633 }
10634
10635 char *recentEngines;
10636
10637 void
10638 RecentEngineEvent (int nr)
10639 {
10640     int n;
10641 //    SwapEngines(1); // bump first to second
10642 //    ReplaceEngine(&second, 1); // and load it there
10643     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10644     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10645     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10646         ReplaceEngine(&first, 0);
10647         FloatToFront(&appData.recentEngineList, command[n]);
10648     }
10649 }
10650
10651 int
10652 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10653 {   // determine players from game number
10654     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10655
10656     if(appData.tourneyType == 0) {
10657         roundsPerCycle = (nPlayers - 1) | 1;
10658         pairingsPerRound = nPlayers / 2;
10659     } else if(appData.tourneyType > 0) {
10660         roundsPerCycle = nPlayers - appData.tourneyType;
10661         pairingsPerRound = appData.tourneyType;
10662     }
10663     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10664     gamesPerCycle = gamesPerRound * roundsPerCycle;
10665     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10666     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10667     curRound = nr / gamesPerRound; nr %= gamesPerRound;
10668     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10669     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10670     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10671
10672     if(appData.cycleSync) *syncInterval = gamesPerCycle;
10673     if(appData.roundSync) *syncInterval = gamesPerRound;
10674
10675     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10676
10677     if(appData.tourneyType == 0) {
10678         if(curPairing == (nPlayers-1)/2 ) {
10679             *whitePlayer = curRound;
10680             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10681         } else {
10682             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10683             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10684             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10685             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10686         }
10687     } else if(appData.tourneyType > 1) {
10688         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10689         *whitePlayer = curRound + appData.tourneyType;
10690     } else if(appData.tourneyType > 0) {
10691         *whitePlayer = curPairing;
10692         *blackPlayer = curRound + appData.tourneyType;
10693     }
10694
10695     // take care of white/black alternation per round.
10696     // For cycles and games this is already taken care of by default, derived from matchGame!
10697     return curRound & 1;
10698 }
10699
10700 int
10701 NextTourneyGame (int nr, int *swapColors)
10702 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10703     char *p, *q;
10704     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
10705     FILE *tf;
10706     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10707     tf = fopen(appData.tourneyFile, "r");
10708     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10709     ParseArgsFromFile(tf); fclose(tf);
10710     InitTimeControls(); // TC might be altered from tourney file
10711
10712     nPlayers = CountPlayers(appData.participants); // count participants
10713     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10714     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10715
10716     if(syncInterval) {
10717         p = q = appData.results;
10718         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10719         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10720             DisplayMessage(_("Waiting for other game(s)"),"");
10721             waitingForGame = TRUE;
10722             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10723             return 0;
10724         }
10725         waitingForGame = FALSE;
10726     }
10727
10728     if(appData.tourneyType < 0) {
10729         if(nr>=0 && !pairingReceived) {
10730             char buf[1<<16];
10731             if(pairing.pr == NoProc) {
10732                 if(!appData.pairingEngine[0]) {
10733                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10734                     return 0;
10735                 }
10736                 StartChessProgram(&pairing); // starts the pairing engine
10737             }
10738             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10739             SendToProgram(buf, &pairing);
10740             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10741             SendToProgram(buf, &pairing);
10742             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10743         }
10744         pairingReceived = 0;                              // ... so we continue here
10745         *swapColors = 0;
10746         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10747         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10748         matchGame = 1; roundNr = nr / syncInterval + 1;
10749     }
10750
10751     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10752
10753     // redefine engines, engine dir, etc.
10754     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10755     if(first.pr == NoProc) {
10756       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
10757       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10758     }
10759     if(second.pr == NoProc) {
10760       SwapEngines(1);
10761       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
10762       SwapEngines(1);         // and make that valid for second engine by swapping
10763       InitEngine(&second, 1);
10764     }
10765     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10766     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10767     return OK;
10768 }
10769
10770 void
10771 NextMatchGame ()
10772 {   // performs game initialization that does not invoke engines, and then tries to start the game
10773     int res, firstWhite, swapColors = 0;
10774     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10775     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
10776         char buf[MSG_SIZ];
10777         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
10778         if(strcmp(buf, currentDebugFile)) { // name has changed
10779             FILE *f = fopen(buf, "w");
10780             if(f) { // if opening the new file failed, just keep using the old one
10781                 ASSIGN(currentDebugFile, buf);
10782                 fclose(debugFP);
10783                 debugFP = f;
10784             }
10785             if(appData.serverFileName) {
10786                 if(serverFP) fclose(serverFP);
10787                 serverFP = fopen(appData.serverFileName, "w");
10788                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
10789                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
10790             }
10791         }
10792     }
10793     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10794     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10795     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
10796     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10797     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10798     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10799     Reset(FALSE, first.pr != NoProc);
10800     res = LoadGameOrPosition(matchGame); // setup game
10801     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10802     if(!res) return; // abort when bad game/pos file
10803     TwoMachinesEvent();
10804 }
10805
10806 void
10807 UserAdjudicationEvent (int result)
10808 {
10809     ChessMove gameResult = GameIsDrawn;
10810
10811     if( result > 0 ) {
10812         gameResult = WhiteWins;
10813     }
10814     else if( result < 0 ) {
10815         gameResult = BlackWins;
10816     }
10817
10818     if( gameMode == TwoMachinesPlay ) {
10819         GameEnds( gameResult, "User adjudication", GE_XBOARD );
10820     }
10821 }
10822
10823
10824 // [HGM] save: calculate checksum of game to make games easily identifiable
10825 int
10826 StringCheckSum (char *s)
10827 {
10828         int i = 0;
10829         if(s==NULL) return 0;
10830         while(*s) i = i*259 + *s++;
10831         return i;
10832 }
10833
10834 int
10835 GameCheckSum ()
10836 {
10837         int i, sum=0;
10838         for(i=backwardMostMove; i<forwardMostMove; i++) {
10839                 sum += pvInfoList[i].depth;
10840                 sum += StringCheckSum(parseList[i]);
10841                 sum += StringCheckSum(commentList[i]);
10842                 sum *= 261;
10843         }
10844         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10845         return sum + StringCheckSum(commentList[i]);
10846 } // end of save patch
10847
10848 void
10849 GameEnds (ChessMove result, char *resultDetails, int whosays)
10850 {
10851     GameMode nextGameMode;
10852     int isIcsGame;
10853     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10854
10855     if(endingGame) return; /* [HGM] crash: forbid recursion */
10856     endingGame = 1;
10857     if(twoBoards) { // [HGM] dual: switch back to one board
10858         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10859         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10860     }
10861     if (appData.debugMode) {
10862       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10863               result, resultDetails ? resultDetails : "(null)", whosays);
10864     }
10865
10866     fromX = fromY = -1; // [HGM] abort any move the user is entering.
10867
10868     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
10869
10870     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10871         /* If we are playing on ICS, the server decides when the
10872            game is over, but the engine can offer to draw, claim
10873            a draw, or resign.
10874          */
10875 #if ZIPPY
10876         if (appData.zippyPlay && first.initDone) {
10877             if (result == GameIsDrawn) {
10878                 /* In case draw still needs to be claimed */
10879                 SendToICS(ics_prefix);
10880                 SendToICS("draw\n");
10881             } else if (StrCaseStr(resultDetails, "resign")) {
10882                 SendToICS(ics_prefix);
10883                 SendToICS("resign\n");
10884             }
10885         }
10886 #endif
10887         endingGame = 0; /* [HGM] crash */
10888         return;
10889     }
10890
10891     /* If we're loading the game from a file, stop */
10892     if (whosays == GE_FILE) {
10893       (void) StopLoadGameTimer();
10894       gameFileFP = NULL;
10895     }
10896
10897     /* Cancel draw offers */
10898     first.offeredDraw = second.offeredDraw = 0;
10899
10900     /* If this is an ICS game, only ICS can really say it's done;
10901        if not, anyone can. */
10902     isIcsGame = (gameMode == IcsPlayingWhite ||
10903                  gameMode == IcsPlayingBlack ||
10904                  gameMode == IcsObserving    ||
10905                  gameMode == IcsExamining);
10906
10907     if (!isIcsGame || whosays == GE_ICS) {
10908         /* OK -- not an ICS game, or ICS said it was done */
10909         StopClocks();
10910         if (!isIcsGame && !appData.noChessProgram)
10911           SetUserThinkingEnables();
10912
10913         /* [HGM] if a machine claims the game end we verify this claim */
10914         if(gameMode == TwoMachinesPlay && appData.testClaims) {
10915             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10916                 char claimer;
10917                 ChessMove trueResult = (ChessMove) -1;
10918
10919                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
10920                                             first.twoMachinesColor[0] :
10921                                             second.twoMachinesColor[0] ;
10922
10923                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10924                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10925                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10926                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10927                 } else
10928                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10929                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10930                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10931                 } else
10932                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10933                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10934                 }
10935
10936                 // now verify win claims, but not in drop games, as we don't understand those yet
10937                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10938                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10939                     (result == WhiteWins && claimer == 'w' ||
10940                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
10941                       if (appData.debugMode) {
10942                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
10943                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10944                       }
10945                       if(result != trueResult) {
10946                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10947                               result = claimer == 'w' ? BlackWins : WhiteWins;
10948                               resultDetails = buf;
10949                       }
10950                 } else
10951                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10952                     && (forwardMostMove <= backwardMostMove ||
10953                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10954                         (claimer=='b')==(forwardMostMove&1))
10955                                                                                   ) {
10956                       /* [HGM] verify: draws that were not flagged are false claims */
10957                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10958                       result = claimer == 'w' ? BlackWins : WhiteWins;
10959                       resultDetails = buf;
10960                 }
10961                 /* (Claiming a loss is accepted no questions asked!) */
10962             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
10963                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
10964                 result = GameUnfinished;
10965                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
10966             }
10967             /* [HGM] bare: don't allow bare King to win */
10968             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10969                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10970                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10971                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10972                && result != GameIsDrawn)
10973             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10974                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10975                         int p = (signed char)boards[forwardMostMove][i][j] - color;
10976                         if(p >= 0 && p <= (int)WhiteKing) k++;
10977                 }
10978                 if (appData.debugMode) {
10979                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10980                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10981                 }
10982                 if(k <= 1) {
10983                         result = GameIsDrawn;
10984                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10985                         resultDetails = buf;
10986                 }
10987             }
10988         }
10989
10990
10991         if(serverMoves != NULL && !loadFlag) { char c = '=';
10992             if(result==WhiteWins) c = '+';
10993             if(result==BlackWins) c = '-';
10994             if(resultDetails != NULL)
10995                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
10996         }
10997         if (resultDetails != NULL) {
10998             gameInfo.result = result;
10999             gameInfo.resultDetails = StrSave(resultDetails);
11000
11001             /* display last move only if game was not loaded from file */
11002             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11003                 DisplayMove(currentMove - 1);
11004
11005             if (forwardMostMove != 0) {
11006                 if (gameMode != PlayFromGameFile && gameMode != EditGame
11007                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11008                                                                 ) {
11009                     if (*appData.saveGameFile != NULLCHAR) {
11010                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11011                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11012                         else
11013                         SaveGameToFile(appData.saveGameFile, TRUE);
11014                     } else if (appData.autoSaveGames) {
11015                         if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11016                     }
11017                     if (*appData.savePositionFile != NULLCHAR) {
11018                         SavePositionToFile(appData.savePositionFile);
11019                     }
11020                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11021                 }
11022             }
11023
11024             /* Tell program how game ended in case it is learning */
11025             /* [HGM] Moved this to after saving the PGN, just in case */
11026             /* engine died and we got here through time loss. In that */
11027             /* case we will get a fatal error writing the pipe, which */
11028             /* would otherwise lose us the PGN.                       */
11029             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
11030             /* output during GameEnds should never be fatal anymore   */
11031             if (gameMode == MachinePlaysWhite ||
11032                 gameMode == MachinePlaysBlack ||
11033                 gameMode == TwoMachinesPlay ||
11034                 gameMode == IcsPlayingWhite ||
11035                 gameMode == IcsPlayingBlack ||
11036                 gameMode == BeginningOfGame) {
11037                 char buf[MSG_SIZ];
11038                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11039                         resultDetails);
11040                 if (first.pr != NoProc) {
11041                     SendToProgram(buf, &first);
11042                 }
11043                 if (second.pr != NoProc &&
11044                     gameMode == TwoMachinesPlay) {
11045                     SendToProgram(buf, &second);
11046                 }
11047             }
11048         }
11049
11050         if (appData.icsActive) {
11051             if (appData.quietPlay &&
11052                 (gameMode == IcsPlayingWhite ||
11053                  gameMode == IcsPlayingBlack)) {
11054                 SendToICS(ics_prefix);
11055                 SendToICS("set shout 1\n");
11056             }
11057             nextGameMode = IcsIdle;
11058             ics_user_moved = FALSE;
11059             /* clean up premove.  It's ugly when the game has ended and the
11060              * premove highlights are still on the board.
11061              */
11062             if (gotPremove) {
11063               gotPremove = FALSE;
11064               ClearPremoveHighlights();
11065               DrawPosition(FALSE, boards[currentMove]);
11066             }
11067             if (whosays == GE_ICS) {
11068                 switch (result) {
11069                 case WhiteWins:
11070                     if (gameMode == IcsPlayingWhite)
11071                         PlayIcsWinSound();
11072                     else if(gameMode == IcsPlayingBlack)
11073                         PlayIcsLossSound();
11074                     break;
11075                 case BlackWins:
11076                     if (gameMode == IcsPlayingBlack)
11077                         PlayIcsWinSound();
11078                     else if(gameMode == IcsPlayingWhite)
11079                         PlayIcsLossSound();
11080                     break;
11081                 case GameIsDrawn:
11082                     PlayIcsDrawSound();
11083                     break;
11084                 default:
11085                     PlayIcsUnfinishedSound();
11086                 }
11087             }
11088             if(appData.quitNext) { ExitEvent(0); return; }
11089         } else if (gameMode == EditGame ||
11090                    gameMode == PlayFromGameFile ||
11091                    gameMode == AnalyzeMode ||
11092                    gameMode == AnalyzeFile) {
11093             nextGameMode = gameMode;
11094         } else {
11095             nextGameMode = EndOfGame;
11096         }
11097         pausing = FALSE;
11098         ModeHighlight();
11099     } else {
11100         nextGameMode = gameMode;
11101     }
11102
11103     if (appData.noChessProgram) {
11104         gameMode = nextGameMode;
11105         ModeHighlight();
11106         endingGame = 0; /* [HGM] crash */
11107         return;
11108     }
11109
11110     if (first.reuse) {
11111         /* Put first chess program into idle state */
11112         if (first.pr != NoProc &&
11113             (gameMode == MachinePlaysWhite ||
11114              gameMode == MachinePlaysBlack ||
11115              gameMode == TwoMachinesPlay ||
11116              gameMode == IcsPlayingWhite ||
11117              gameMode == IcsPlayingBlack ||
11118              gameMode == BeginningOfGame)) {
11119             SendToProgram("force\n", &first);
11120             if (first.usePing) {
11121               char buf[MSG_SIZ];
11122               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11123               SendToProgram(buf, &first);
11124             }
11125         }
11126     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11127         /* Kill off first chess program */
11128         if (first.isr != NULL)
11129           RemoveInputSource(first.isr);
11130         first.isr = NULL;
11131
11132         if (first.pr != NoProc) {
11133             ExitAnalyzeMode();
11134             DoSleep( appData.delayBeforeQuit );
11135             SendToProgram("quit\n", &first);
11136             DoSleep( appData.delayAfterQuit );
11137             DestroyChildProcess(first.pr, first.useSigterm);
11138             first.reload = TRUE;
11139         }
11140         first.pr = NoProc;
11141     }
11142     if (second.reuse) {
11143         /* Put second chess program into idle state */
11144         if (second.pr != NoProc &&
11145             gameMode == TwoMachinesPlay) {
11146             SendToProgram("force\n", &second);
11147             if (second.usePing) {
11148               char buf[MSG_SIZ];
11149               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11150               SendToProgram(buf, &second);
11151             }
11152         }
11153     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11154         /* Kill off second chess program */
11155         if (second.isr != NULL)
11156           RemoveInputSource(second.isr);
11157         second.isr = NULL;
11158
11159         if (second.pr != NoProc) {
11160             DoSleep( appData.delayBeforeQuit );
11161             SendToProgram("quit\n", &second);
11162             DoSleep( appData.delayAfterQuit );
11163             DestroyChildProcess(second.pr, second.useSigterm);
11164             second.reload = TRUE;
11165         }
11166         second.pr = NoProc;
11167     }
11168
11169     if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11170         char resChar = '=';
11171         switch (result) {
11172         case WhiteWins:
11173           resChar = '+';
11174           if (first.twoMachinesColor[0] == 'w') {
11175             first.matchWins++;
11176           } else {
11177             second.matchWins++;
11178           }
11179           break;
11180         case BlackWins:
11181           resChar = '-';
11182           if (first.twoMachinesColor[0] == 'b') {
11183             first.matchWins++;
11184           } else {
11185             second.matchWins++;
11186           }
11187           break;
11188         case GameUnfinished:
11189           resChar = ' ';
11190         default:
11191           break;
11192         }
11193
11194         if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11195         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11196             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11197             ReserveGame(nextGame, resChar); // sets nextGame
11198             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11199             else ranking = strdup("busy"); //suppress popup when aborted but not finished
11200         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11201
11202         if (nextGame <= appData.matchGames && !abortMatch) {
11203             gameMode = nextGameMode;
11204             matchGame = nextGame; // this will be overruled in tourney mode!
11205             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11206             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11207             endingGame = 0; /* [HGM] crash */
11208             return;
11209         } else {
11210             gameMode = nextGameMode;
11211             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11212                      first.tidy, second.tidy,
11213                      first.matchWins, second.matchWins,
11214                      appData.matchGames - (first.matchWins + second.matchWins));
11215             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11216             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11217             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11218             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11219                 first.twoMachinesColor = "black\n";
11220                 second.twoMachinesColor = "white\n";
11221             } else {
11222                 first.twoMachinesColor = "white\n";
11223                 second.twoMachinesColor = "black\n";
11224             }
11225         }
11226     }
11227     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11228         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11229       ExitAnalyzeMode();
11230     gameMode = nextGameMode;
11231     ModeHighlight();
11232     endingGame = 0;  /* [HGM] crash */
11233     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11234         if(matchMode == TRUE) { // match through command line: exit with or without popup
11235             if(ranking) {
11236                 ToNrEvent(forwardMostMove);
11237                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11238                 else ExitEvent(0);
11239             } else DisplayFatalError(buf, 0, 0);
11240         } else { // match through menu; just stop, with or without popup
11241             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11242             ModeHighlight();
11243             if(ranking){
11244                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11245             } else DisplayNote(buf);
11246       }
11247       if(ranking) free(ranking);
11248     }
11249 }
11250
11251 /* Assumes program was just initialized (initString sent).
11252    Leaves program in force mode. */
11253 void
11254 FeedMovesToProgram (ChessProgramState *cps, int upto)
11255 {
11256     int i;
11257
11258     if (appData.debugMode)
11259       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11260               startedFromSetupPosition ? "position and " : "",
11261               backwardMostMove, upto, cps->which);
11262     if(currentlyInitializedVariant != gameInfo.variant) {
11263       char buf[MSG_SIZ];
11264         // [HGM] variantswitch: make engine aware of new variant
11265         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
11266                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11267         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11268         SendToProgram(buf, cps);
11269         currentlyInitializedVariant = gameInfo.variant;
11270     }
11271     SendToProgram("force\n", cps);
11272     if (startedFromSetupPosition) {
11273         SendBoard(cps, backwardMostMove);
11274     if (appData.debugMode) {
11275         fprintf(debugFP, "feedMoves\n");
11276     }
11277     }
11278     for (i = backwardMostMove; i < upto; i++) {
11279         SendMoveToProgram(i, cps);
11280     }
11281 }
11282
11283
11284 int
11285 ResurrectChessProgram ()
11286 {
11287      /* The chess program may have exited.
11288         If so, restart it and feed it all the moves made so far. */
11289     static int doInit = 0;
11290
11291     if (appData.noChessProgram) return 1;
11292
11293     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11294         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11295         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11296         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11297     } else {
11298         if (first.pr != NoProc) return 1;
11299         StartChessProgram(&first);
11300     }
11301     InitChessProgram(&first, FALSE);
11302     FeedMovesToProgram(&first, currentMove);
11303
11304     if (!first.sendTime) {
11305         /* can't tell gnuchess what its clock should read,
11306            so we bow to its notion. */
11307         ResetClocks();
11308         timeRemaining[0][currentMove] = whiteTimeRemaining;
11309         timeRemaining[1][currentMove] = blackTimeRemaining;
11310     }
11311
11312     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11313                 appData.icsEngineAnalyze) && first.analysisSupport) {
11314       SendToProgram("analyze\n", &first);
11315       first.analyzing = TRUE;
11316     }
11317     return 1;
11318 }
11319
11320 /*
11321  * Button procedures
11322  */
11323 void
11324 Reset (int redraw, int init)
11325 {
11326     int i;
11327
11328     if (appData.debugMode) {
11329         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11330                 redraw, init, gameMode);
11331     }
11332     CleanupTail(); // [HGM] vari: delete any stored variations
11333     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11334     pausing = pauseExamInvalid = FALSE;
11335     startedFromSetupPosition = blackPlaysFirst = FALSE;
11336     firstMove = TRUE;
11337     whiteFlag = blackFlag = FALSE;
11338     userOfferedDraw = FALSE;
11339     hintRequested = bookRequested = FALSE;
11340     first.maybeThinking = FALSE;
11341     second.maybeThinking = FALSE;
11342     first.bookSuspend = FALSE; // [HGM] book
11343     second.bookSuspend = FALSE;
11344     thinkOutput[0] = NULLCHAR;
11345     lastHint[0] = NULLCHAR;
11346     ClearGameInfo(&gameInfo);
11347     gameInfo.variant = StringToVariant(appData.variant);
11348     if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) gameInfo.variant = VariantUnknown;
11349     ics_user_moved = ics_clock_paused = FALSE;
11350     ics_getting_history = H_FALSE;
11351     ics_gamenum = -1;
11352     white_holding[0] = black_holding[0] = NULLCHAR;
11353     ClearProgramStats();
11354     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11355
11356     ResetFrontEnd();
11357     ClearHighlights();
11358     flipView = appData.flipView;
11359     ClearPremoveHighlights();
11360     gotPremove = FALSE;
11361     alarmSounded = FALSE;
11362
11363     GameEnds(EndOfFile, NULL, GE_PLAYER);
11364     if(appData.serverMovesName != NULL) {
11365         /* [HGM] prepare to make moves file for broadcasting */
11366         clock_t t = clock();
11367         if(serverMoves != NULL) fclose(serverMoves);
11368         serverMoves = fopen(appData.serverMovesName, "r");
11369         if(serverMoves != NULL) {
11370             fclose(serverMoves);
11371             /* delay 15 sec before overwriting, so all clients can see end */
11372             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11373         }
11374         serverMoves = fopen(appData.serverMovesName, "w");
11375     }
11376
11377     ExitAnalyzeMode();
11378     gameMode = BeginningOfGame;
11379     ModeHighlight();
11380     if(appData.icsActive) gameInfo.variant = VariantNormal;
11381     currentMove = forwardMostMove = backwardMostMove = 0;
11382     MarkTargetSquares(1);
11383     InitPosition(redraw);
11384     for (i = 0; i < MAX_MOVES; i++) {
11385         if (commentList[i] != NULL) {
11386             free(commentList[i]);
11387             commentList[i] = NULL;
11388         }
11389     }
11390     ResetClocks();
11391     timeRemaining[0][0] = whiteTimeRemaining;
11392     timeRemaining[1][0] = blackTimeRemaining;
11393
11394     if (first.pr == NoProc) {
11395         StartChessProgram(&first);
11396     }
11397     if (init) {
11398             InitChessProgram(&first, startedFromSetupPosition);
11399     }
11400     DisplayTitle("");
11401     DisplayMessage("", "");
11402     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11403     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11404     ClearMap();        // [HGM] exclude: invalidate map
11405 }
11406
11407 void
11408 AutoPlayGameLoop ()
11409 {
11410     for (;;) {
11411         if (!AutoPlayOneMove())
11412           return;
11413         if (matchMode || appData.timeDelay == 0)
11414           continue;
11415         if (appData.timeDelay < 0)
11416           return;
11417         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11418         break;
11419     }
11420 }
11421
11422 void
11423 AnalyzeNextGame()
11424 {
11425     ReloadGame(1); // next game
11426 }
11427
11428 int
11429 AutoPlayOneMove ()
11430 {
11431     int fromX, fromY, toX, toY;
11432
11433     if (appData.debugMode) {
11434       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11435     }
11436
11437     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11438       return FALSE;
11439
11440     if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
11441       pvInfoList[currentMove].depth = programStats.depth;
11442       pvInfoList[currentMove].score = programStats.score;
11443       pvInfoList[currentMove].time  = 0;
11444       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11445       else { // append analysis of final position as comment
11446         char buf[MSG_SIZ];
11447         snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
11448         AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
11449       }
11450       programStats.depth = 0;
11451     }
11452
11453     if (currentMove >= forwardMostMove) {
11454       if(gameMode == AnalyzeFile) {
11455           if(appData.loadGameIndex == -1) {
11456             GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
11457           ScheduleDelayedEvent(AnalyzeNextGame, 10);
11458           } else {
11459           ExitAnalyzeMode(); SendToProgram("force\n", &first);
11460         }
11461       }
11462 //      gameMode = EndOfGame;
11463 //      ModeHighlight();
11464
11465       /* [AS] Clear current move marker at the end of a game */
11466       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11467
11468       return FALSE;
11469     }
11470
11471     toX = moveList[currentMove][2] - AAA;
11472     toY = moveList[currentMove][3] - ONE;
11473
11474     if (moveList[currentMove][1] == '@') {
11475         if (appData.highlightLastMove) {
11476             SetHighlights(-1, -1, toX, toY);
11477         }
11478     } else {
11479         fromX = moveList[currentMove][0] - AAA;
11480         fromY = moveList[currentMove][1] - ONE;
11481
11482         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11483
11484         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11485
11486         if (appData.highlightLastMove) {
11487             SetHighlights(fromX, fromY, toX, toY);
11488         }
11489     }
11490     DisplayMove(currentMove);
11491     SendMoveToProgram(currentMove++, &first);
11492     DisplayBothClocks();
11493     DrawPosition(FALSE, boards[currentMove]);
11494     // [HGM] PV info: always display, routine tests if empty
11495     DisplayComment(currentMove - 1, commentList[currentMove]);
11496     return TRUE;
11497 }
11498
11499
11500 int
11501 LoadGameOneMove (ChessMove readAhead)
11502 {
11503     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11504     char promoChar = NULLCHAR;
11505     ChessMove moveType;
11506     char move[MSG_SIZ];
11507     char *p, *q;
11508
11509     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11510         gameMode != AnalyzeMode && gameMode != Training) {
11511         gameFileFP = NULL;
11512         return FALSE;
11513     }
11514
11515     yyboardindex = forwardMostMove;
11516     if (readAhead != EndOfFile) {
11517       moveType = readAhead;
11518     } else {
11519       if (gameFileFP == NULL)
11520           return FALSE;
11521       moveType = (ChessMove) Myylex();
11522     }
11523
11524     done = FALSE;
11525     switch (moveType) {
11526       case Comment:
11527         if (appData.debugMode)
11528           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11529         p = yy_text;
11530
11531         /* append the comment but don't display it */
11532         AppendComment(currentMove, p, FALSE);
11533         return TRUE;
11534
11535       case WhiteCapturesEnPassant:
11536       case BlackCapturesEnPassant:
11537       case WhitePromotion:
11538       case BlackPromotion:
11539       case WhiteNonPromotion:
11540       case BlackNonPromotion:
11541       case NormalMove:
11542       case WhiteKingSideCastle:
11543       case WhiteQueenSideCastle:
11544       case BlackKingSideCastle:
11545       case BlackQueenSideCastle:
11546       case WhiteKingSideCastleWild:
11547       case WhiteQueenSideCastleWild:
11548       case BlackKingSideCastleWild:
11549       case BlackQueenSideCastleWild:
11550       /* PUSH Fabien */
11551       case WhiteHSideCastleFR:
11552       case WhiteASideCastleFR:
11553       case BlackHSideCastleFR:
11554       case BlackASideCastleFR:
11555       /* POP Fabien */
11556         if (appData.debugMode)
11557           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11558         fromX = currentMoveString[0] - AAA;
11559         fromY = currentMoveString[1] - ONE;
11560         toX = currentMoveString[2] - AAA;
11561         toY = currentMoveString[3] - ONE;
11562         promoChar = currentMoveString[4];
11563         break;
11564
11565       case WhiteDrop:
11566       case BlackDrop:
11567         if (appData.debugMode)
11568           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11569         fromX = moveType == WhiteDrop ?
11570           (int) CharToPiece(ToUpper(currentMoveString[0])) :
11571         (int) CharToPiece(ToLower(currentMoveString[0]));
11572         fromY = DROP_RANK;
11573         toX = currentMoveString[2] - AAA;
11574         toY = currentMoveString[3] - ONE;
11575         break;
11576
11577       case WhiteWins:
11578       case BlackWins:
11579       case GameIsDrawn:
11580       case GameUnfinished:
11581         if (appData.debugMode)
11582           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11583         p = strchr(yy_text, '{');
11584         if (p == NULL) p = strchr(yy_text, '(');
11585         if (p == NULL) {
11586             p = yy_text;
11587             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11588         } else {
11589             q = strchr(p, *p == '{' ? '}' : ')');
11590             if (q != NULL) *q = NULLCHAR;
11591             p++;
11592         }
11593         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11594         GameEnds(moveType, p, GE_FILE);
11595         done = TRUE;
11596         if (cmailMsgLoaded) {
11597             ClearHighlights();
11598             flipView = WhiteOnMove(currentMove);
11599             if (moveType == GameUnfinished) flipView = !flipView;
11600             if (appData.debugMode)
11601               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11602         }
11603         break;
11604
11605       case EndOfFile:
11606         if (appData.debugMode)
11607           fprintf(debugFP, "Parser hit end of file\n");
11608         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11609           case MT_NONE:
11610           case MT_CHECK:
11611             break;
11612           case MT_CHECKMATE:
11613           case MT_STAINMATE:
11614             if (WhiteOnMove(currentMove)) {
11615                 GameEnds(BlackWins, "Black mates", GE_FILE);
11616             } else {
11617                 GameEnds(WhiteWins, "White mates", GE_FILE);
11618             }
11619             break;
11620           case MT_STALEMATE:
11621             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11622             break;
11623         }
11624         done = TRUE;
11625         break;
11626
11627       case MoveNumberOne:
11628         if (lastLoadGameStart == GNUChessGame) {
11629             /* GNUChessGames have numbers, but they aren't move numbers */
11630             if (appData.debugMode)
11631               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11632                       yy_text, (int) moveType);
11633             return LoadGameOneMove(EndOfFile); /* tail recursion */
11634         }
11635         /* else fall thru */
11636
11637       case XBoardGame:
11638       case GNUChessGame:
11639       case PGNTag:
11640         /* Reached start of next game in file */
11641         if (appData.debugMode)
11642           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11643         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11644           case MT_NONE:
11645           case MT_CHECK:
11646             break;
11647           case MT_CHECKMATE:
11648           case MT_STAINMATE:
11649             if (WhiteOnMove(currentMove)) {
11650                 GameEnds(BlackWins, "Black mates", GE_FILE);
11651             } else {
11652                 GameEnds(WhiteWins, "White mates", GE_FILE);
11653             }
11654             break;
11655           case MT_STALEMATE:
11656             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11657             break;
11658         }
11659         done = TRUE;
11660         break;
11661
11662       case PositionDiagram:     /* should not happen; ignore */
11663       case ElapsedTime:         /* ignore */
11664       case NAG:                 /* ignore */
11665         if (appData.debugMode)
11666           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11667                   yy_text, (int) moveType);
11668         return LoadGameOneMove(EndOfFile); /* tail recursion */
11669
11670       case IllegalMove:
11671         if (appData.testLegality) {
11672             if (appData.debugMode)
11673               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11674             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11675                     (forwardMostMove / 2) + 1,
11676                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11677             DisplayError(move, 0);
11678             done = TRUE;
11679         } else {
11680             if (appData.debugMode)
11681               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11682                       yy_text, currentMoveString);
11683             fromX = currentMoveString[0] - AAA;
11684             fromY = currentMoveString[1] - ONE;
11685             toX = currentMoveString[2] - AAA;
11686             toY = currentMoveString[3] - ONE;
11687             promoChar = currentMoveString[4];
11688         }
11689         break;
11690
11691       case AmbiguousMove:
11692         if (appData.debugMode)
11693           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11694         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11695                 (forwardMostMove / 2) + 1,
11696                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11697         DisplayError(move, 0);
11698         done = TRUE;
11699         break;
11700
11701       default:
11702       case ImpossibleMove:
11703         if (appData.debugMode)
11704           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11705         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11706                 (forwardMostMove / 2) + 1,
11707                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11708         DisplayError(move, 0);
11709         done = TRUE;
11710         break;
11711     }
11712
11713     if (done) {
11714         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11715             DrawPosition(FALSE, boards[currentMove]);
11716             DisplayBothClocks();
11717             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11718               DisplayComment(currentMove - 1, commentList[currentMove]);
11719         }
11720         (void) StopLoadGameTimer();
11721         gameFileFP = NULL;
11722         cmailOldMove = forwardMostMove;
11723         return FALSE;
11724     } else {
11725         /* currentMoveString is set as a side-effect of yylex */
11726
11727         thinkOutput[0] = NULLCHAR;
11728         MakeMove(fromX, fromY, toX, toY, promoChar);
11729         currentMove = forwardMostMove;
11730         return TRUE;
11731     }
11732 }
11733
11734 /* Load the nth game from the given file */
11735 int
11736 LoadGameFromFile (char *filename, int n, char *title, int useList)
11737 {
11738     FILE *f;
11739     char buf[MSG_SIZ];
11740
11741     if (strcmp(filename, "-") == 0) {
11742         f = stdin;
11743         title = "stdin";
11744     } else {
11745         f = fopen(filename, "rb");
11746         if (f == NULL) {
11747           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
11748             DisplayError(buf, errno);
11749             return FALSE;
11750         }
11751     }
11752     if (fseek(f, 0, 0) == -1) {
11753         /* f is not seekable; probably a pipe */
11754         useList = FALSE;
11755     }
11756     if (useList && n == 0) {
11757         int error = GameListBuild(f);
11758         if (error) {
11759             DisplayError(_("Cannot build game list"), error);
11760         } else if (!ListEmpty(&gameList) &&
11761                    ((ListGame *) gameList.tailPred)->number > 1) {
11762             GameListPopUp(f, title);
11763             return TRUE;
11764         }
11765         GameListDestroy();
11766         n = 1;
11767     }
11768     if (n == 0) n = 1;
11769     return LoadGame(f, n, title, FALSE);
11770 }
11771
11772
11773 void
11774 MakeRegisteredMove ()
11775 {
11776     int fromX, fromY, toX, toY;
11777     char promoChar;
11778     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11779         switch (cmailMoveType[lastLoadGameNumber - 1]) {
11780           case CMAIL_MOVE:
11781           case CMAIL_DRAW:
11782             if (appData.debugMode)
11783               fprintf(debugFP, "Restoring %s for game %d\n",
11784                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11785
11786             thinkOutput[0] = NULLCHAR;
11787             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11788             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11789             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11790             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11791             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11792             promoChar = cmailMove[lastLoadGameNumber - 1][4];
11793             MakeMove(fromX, fromY, toX, toY, promoChar);
11794             ShowMove(fromX, fromY, toX, toY);
11795
11796             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11797               case MT_NONE:
11798               case MT_CHECK:
11799                 break;
11800
11801               case MT_CHECKMATE:
11802               case MT_STAINMATE:
11803                 if (WhiteOnMove(currentMove)) {
11804                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
11805                 } else {
11806                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
11807                 }
11808                 break;
11809
11810               case MT_STALEMATE:
11811                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11812                 break;
11813             }
11814
11815             break;
11816
11817           case CMAIL_RESIGN:
11818             if (WhiteOnMove(currentMove)) {
11819                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11820             } else {
11821                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11822             }
11823             break;
11824
11825           case CMAIL_ACCEPT:
11826             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11827             break;
11828
11829           default:
11830             break;
11831         }
11832     }
11833
11834     return;
11835 }
11836
11837 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11838 int
11839 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11840 {
11841     int retVal;
11842
11843     if (gameNumber > nCmailGames) {
11844         DisplayError(_("No more games in this message"), 0);
11845         return FALSE;
11846     }
11847     if (f == lastLoadGameFP) {
11848         int offset = gameNumber - lastLoadGameNumber;
11849         if (offset == 0) {
11850             cmailMsg[0] = NULLCHAR;
11851             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11852                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11853                 nCmailMovesRegistered--;
11854             }
11855             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11856             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11857                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11858             }
11859         } else {
11860             if (! RegisterMove()) return FALSE;
11861         }
11862     }
11863
11864     retVal = LoadGame(f, gameNumber, title, useList);
11865
11866     /* Make move registered during previous look at this game, if any */
11867     MakeRegisteredMove();
11868
11869     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11870         commentList[currentMove]
11871           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11872         DisplayComment(currentMove - 1, commentList[currentMove]);
11873     }
11874
11875     return retVal;
11876 }
11877
11878 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11879 int
11880 ReloadGame (int offset)
11881 {
11882     int gameNumber = lastLoadGameNumber + offset;
11883     if (lastLoadGameFP == NULL) {
11884         DisplayError(_("No game has been loaded yet"), 0);
11885         return FALSE;
11886     }
11887     if (gameNumber <= 0) {
11888         DisplayError(_("Can't back up any further"), 0);
11889         return FALSE;
11890     }
11891     if (cmailMsgLoaded) {
11892         return CmailLoadGame(lastLoadGameFP, gameNumber,
11893                              lastLoadGameTitle, lastLoadGameUseList);
11894     } else {
11895         return LoadGame(lastLoadGameFP, gameNumber,
11896                         lastLoadGameTitle, lastLoadGameUseList);
11897     }
11898 }
11899
11900 int keys[EmptySquare+1];
11901
11902 int
11903 PositionMatches (Board b1, Board b2)
11904 {
11905     int r, f, sum=0;
11906     switch(appData.searchMode) {
11907         case 1: return CompareWithRights(b1, b2);
11908         case 2:
11909             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11910                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11911             }
11912             return TRUE;
11913         case 3:
11914             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11915               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11916                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11917             }
11918             return sum==0;
11919         case 4:
11920             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11921                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11922             }
11923             return sum==0;
11924     }
11925     return TRUE;
11926 }
11927
11928 #define Q_PROMO  4
11929 #define Q_EP     3
11930 #define Q_BCASTL 2
11931 #define Q_WCASTL 1
11932
11933 int pieceList[256], quickBoard[256];
11934 ChessSquare pieceType[256] = { EmptySquare };
11935 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11936 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11937 int soughtTotal, turn;
11938 Boolean epOK, flipSearch;
11939
11940 typedef struct {
11941     unsigned char piece, to;
11942 } Move;
11943
11944 #define DSIZE (250000)
11945
11946 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11947 Move *moveDatabase = initialSpace;
11948 unsigned int movePtr, dataSize = DSIZE;
11949
11950 int
11951 MakePieceList (Board board, int *counts)
11952 {
11953     int r, f, n=Q_PROMO, total=0;
11954     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11955     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11956         int sq = f + (r<<4);
11957         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
11958             quickBoard[sq] = ++n;
11959             pieceList[n] = sq;
11960             pieceType[n] = board[r][f];
11961             counts[board[r][f]]++;
11962             if(board[r][f] == WhiteKing) pieceList[1] = n; else
11963             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
11964             total++;
11965         }
11966     }
11967     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
11968     return total;
11969 }
11970
11971 void
11972 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
11973 {
11974     int sq = fromX + (fromY<<4);
11975     int piece = quickBoard[sq];
11976     quickBoard[sq] = 0;
11977     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
11978     if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11979         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
11980         moveDatabase[movePtr++].piece = Q_WCASTL;
11981         quickBoard[sq] = piece;
11982         piece = quickBoard[from]; quickBoard[from] = 0;
11983         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11984     } else
11985     if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11986         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
11987         moveDatabase[movePtr++].piece = Q_BCASTL;
11988         quickBoard[sq] = piece;
11989         piece = quickBoard[from]; quickBoard[from] = 0;
11990         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11991     } else
11992     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
11993         quickBoard[(fromY<<4)+toX] = 0;
11994         moveDatabase[movePtr].piece = Q_EP;
11995         moveDatabase[movePtr++].to = (fromY<<4)+toX;
11996         moveDatabase[movePtr].to = sq;
11997     } else
11998     if(promoPiece != pieceType[piece]) {
11999         moveDatabase[movePtr++].piece = Q_PROMO;
12000         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12001     }
12002     moveDatabase[movePtr].piece = piece;
12003     quickBoard[sq] = piece;
12004     movePtr++;
12005 }
12006
12007 int
12008 PackGame (Board board)
12009 {
12010     Move *newSpace = NULL;
12011     moveDatabase[movePtr].piece = 0; // terminate previous game
12012     if(movePtr > dataSize) {
12013         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12014         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12015         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12016         if(newSpace) {
12017             int i;
12018             Move *p = moveDatabase, *q = newSpace;
12019             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
12020             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12021             moveDatabase = newSpace;
12022         } else { // calloc failed, we must be out of memory. Too bad...
12023             dataSize = 0; // prevent calloc events for all subsequent games
12024             return 0;     // and signal this one isn't cached
12025         }
12026     }
12027     movePtr++;
12028     MakePieceList(board, counts);
12029     return movePtr;
12030 }
12031
12032 int
12033 QuickCompare (Board board, int *minCounts, int *maxCounts)
12034 {   // compare according to search mode
12035     int r, f;
12036     switch(appData.searchMode)
12037     {
12038       case 1: // exact position match
12039         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12040         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12041             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12042         }
12043         break;
12044       case 2: // can have extra material on empty squares
12045         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12046             if(board[r][f] == EmptySquare) continue;
12047             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12048         }
12049         break;
12050       case 3: // material with exact Pawn structure
12051         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12052             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12053             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12054         } // fall through to material comparison
12055       case 4: // exact material
12056         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12057         break;
12058       case 6: // material range with given imbalance
12059         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12060         // fall through to range comparison
12061       case 5: // material range
12062         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12063     }
12064     return TRUE;
12065 }
12066
12067 int
12068 QuickScan (Board board, Move *move)
12069 {   // reconstruct game,and compare all positions in it
12070     int cnt=0, stretch=0, total = MakePieceList(board, counts);
12071     do {
12072         int piece = move->piece;
12073         int to = move->to, from = pieceList[piece];
12074         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
12075           if(!piece) return -1;
12076           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
12077             piece = (++move)->piece;
12078             from = pieceList[piece];
12079             counts[pieceType[piece]]--;
12080             pieceType[piece] = (ChessSquare) move->to;
12081             counts[move->to]++;
12082           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
12083             counts[pieceType[quickBoard[to]]]--;
12084             quickBoard[to] = 0; total--;
12085             move++;
12086             continue;
12087           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
12088             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
12089             from  = pieceList[piece]; // so this must be King
12090             quickBoard[from] = 0;
12091             pieceList[piece] = to;
12092             from = pieceList[(++move)->piece]; // for FRC this has to be done here
12093             quickBoard[from] = 0; // rook
12094             quickBoard[to] = piece;
12095             to = move->to; piece = move->piece;
12096             goto aftercastle;
12097           }
12098         }
12099         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12100         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
12101         quickBoard[from] = 0;
12102       aftercastle:
12103         quickBoard[to] = piece;
12104         pieceList[piece] = to;
12105         cnt++; turn ^= 3;
12106         if(QuickCompare(soughtBoard, minSought, maxSought) ||
12107            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12108            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12109                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12110           ) {
12111             static int lastCounts[EmptySquare+1];
12112             int i;
12113             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12114             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12115         } else stretch = 0;
12116         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
12117         move++;
12118     } while(1);
12119 }
12120
12121 void
12122 InitSearch ()
12123 {
12124     int r, f;
12125     flipSearch = FALSE;
12126     CopyBoard(soughtBoard, boards[currentMove]);
12127     soughtTotal = MakePieceList(soughtBoard, maxSought);
12128     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12129     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12130     CopyBoard(reverseBoard, boards[currentMove]);
12131     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12132         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12133         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12134         reverseBoard[r][f] = piece;
12135     }
12136     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12137     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12138     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12139                  || (boards[currentMove][CASTLING][2] == NoRights ||
12140                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12141                  && (boards[currentMove][CASTLING][5] == NoRights ||
12142                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12143       ) {
12144         flipSearch = TRUE;
12145         CopyBoard(flipBoard, soughtBoard);
12146         CopyBoard(rotateBoard, reverseBoard);
12147         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12148             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
12149             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12150         }
12151     }
12152     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12153     if(appData.searchMode >= 5) {
12154         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12155         MakePieceList(soughtBoard, minSought);
12156         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12157     }
12158     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12159         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12160 }
12161
12162 GameInfo dummyInfo;
12163 static int creatingBook;
12164
12165 int
12166 GameContainsPosition (FILE *f, ListGame *lg)
12167 {
12168     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12169     int fromX, fromY, toX, toY;
12170     char promoChar;
12171     static int initDone=FALSE;
12172
12173     // weed out games based on numerical tag comparison
12174     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12175     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12176     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12177     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12178     if(!initDone) {
12179         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12180         initDone = TRUE;
12181     }
12182     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
12183     else CopyBoard(boards[scratch], initialPosition); // default start position
12184     if(lg->moves) {
12185         turn = btm + 1;
12186         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12187         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12188     }
12189     if(btm) plyNr++;
12190     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12191     fseek(f, lg->offset, 0);
12192     yynewfile(f);
12193     while(1) {
12194         yyboardindex = scratch;
12195         quickFlag = plyNr+1;
12196         next = Myylex();
12197         quickFlag = 0;
12198         switch(next) {
12199             case PGNTag:
12200                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12201             default:
12202                 continue;
12203
12204             case XBoardGame:
12205             case GNUChessGame:
12206                 if(plyNr) return -1; // after we have seen moves, this is for new game
12207               continue;
12208
12209             case AmbiguousMove: // we cannot reconstruct the game beyond these two
12210             case ImpossibleMove:
12211             case WhiteWins: // game ends here with these four
12212             case BlackWins:
12213             case GameIsDrawn:
12214             case GameUnfinished:
12215                 return -1;
12216
12217             case IllegalMove:
12218                 if(appData.testLegality) return -1;
12219             case WhiteCapturesEnPassant:
12220             case BlackCapturesEnPassant:
12221             case WhitePromotion:
12222             case BlackPromotion:
12223             case WhiteNonPromotion:
12224             case BlackNonPromotion:
12225             case NormalMove:
12226             case WhiteKingSideCastle:
12227             case WhiteQueenSideCastle:
12228             case BlackKingSideCastle:
12229             case BlackQueenSideCastle:
12230             case WhiteKingSideCastleWild:
12231             case WhiteQueenSideCastleWild:
12232             case BlackKingSideCastleWild:
12233             case BlackQueenSideCastleWild:
12234             case WhiteHSideCastleFR:
12235             case WhiteASideCastleFR:
12236             case BlackHSideCastleFR:
12237             case BlackASideCastleFR:
12238                 fromX = currentMoveString[0] - AAA;
12239                 fromY = currentMoveString[1] - ONE;
12240                 toX = currentMoveString[2] - AAA;
12241                 toY = currentMoveString[3] - ONE;
12242                 promoChar = currentMoveString[4];
12243                 break;
12244             case WhiteDrop:
12245             case BlackDrop:
12246                 fromX = next == WhiteDrop ?
12247                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
12248                   (int) CharToPiece(ToLower(currentMoveString[0]));
12249                 fromY = DROP_RANK;
12250                 toX = currentMoveString[2] - AAA;
12251                 toY = currentMoveString[3] - ONE;
12252                 promoChar = 0;
12253                 break;
12254         }
12255         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12256         plyNr++;
12257         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12258         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12259         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12260         if(appData.findMirror) {
12261             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12262             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12263         }
12264     }
12265 }
12266
12267 /* Load the nth game from open file f */
12268 int
12269 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12270 {
12271     ChessMove cm;
12272     char buf[MSG_SIZ];
12273     int gn = gameNumber;
12274     ListGame *lg = NULL;
12275     int numPGNTags = 0;
12276     int err, pos = -1;
12277     GameMode oldGameMode;
12278     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12279
12280     if (appData.debugMode)
12281         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12282
12283     if (gameMode == Training )
12284         SetTrainingModeOff();
12285
12286     oldGameMode = gameMode;
12287     if (gameMode != BeginningOfGame) {
12288       Reset(FALSE, TRUE);
12289     }
12290
12291     gameFileFP = f;
12292     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12293         fclose(lastLoadGameFP);
12294     }
12295
12296     if (useList) {
12297         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12298
12299         if (lg) {
12300             fseek(f, lg->offset, 0);
12301             GameListHighlight(gameNumber);
12302             pos = lg->position;
12303             gn = 1;
12304         }
12305         else {
12306             if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
12307               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12308             else
12309             DisplayError(_("Game number out of range"), 0);
12310             return FALSE;
12311         }
12312     } else {
12313         GameListDestroy();
12314         if (fseek(f, 0, 0) == -1) {
12315             if (f == lastLoadGameFP ?
12316                 gameNumber == lastLoadGameNumber + 1 :
12317                 gameNumber == 1) {
12318                 gn = 1;
12319             } else {
12320                 DisplayError(_("Can't seek on game file"), 0);
12321                 return FALSE;
12322             }
12323         }
12324     }
12325     lastLoadGameFP = f;
12326     lastLoadGameNumber = gameNumber;
12327     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12328     lastLoadGameUseList = useList;
12329
12330     yynewfile(f);
12331
12332     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12333       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12334                 lg->gameInfo.black);
12335             DisplayTitle(buf);
12336     } else if (*title != NULLCHAR) {
12337         if (gameNumber > 1) {
12338           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12339             DisplayTitle(buf);
12340         } else {
12341             DisplayTitle(title);
12342         }
12343     }
12344
12345     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12346         gameMode = PlayFromGameFile;
12347         ModeHighlight();
12348     }
12349
12350     currentMove = forwardMostMove = backwardMostMove = 0;
12351     CopyBoard(boards[0], initialPosition);
12352     StopClocks();
12353
12354     /*
12355      * Skip the first gn-1 games in the file.
12356      * Also skip over anything that precedes an identifiable
12357      * start of game marker, to avoid being confused by
12358      * garbage at the start of the file.  Currently
12359      * recognized start of game markers are the move number "1",
12360      * the pattern "gnuchess .* game", the pattern
12361      * "^[#;%] [^ ]* game file", and a PGN tag block.
12362      * A game that starts with one of the latter two patterns
12363      * will also have a move number 1, possibly
12364      * following a position diagram.
12365      * 5-4-02: Let's try being more lenient and allowing a game to
12366      * start with an unnumbered move.  Does that break anything?
12367      */
12368     cm = lastLoadGameStart = EndOfFile;
12369     while (gn > 0) {
12370         yyboardindex = forwardMostMove;
12371         cm = (ChessMove) Myylex();
12372         switch (cm) {
12373           case EndOfFile:
12374             if (cmailMsgLoaded) {
12375                 nCmailGames = CMAIL_MAX_GAMES - gn;
12376             } else {
12377                 Reset(TRUE, TRUE);
12378                 DisplayError(_("Game not found in file"), 0);
12379             }
12380             return FALSE;
12381
12382           case GNUChessGame:
12383           case XBoardGame:
12384             gn--;
12385             lastLoadGameStart = cm;
12386             break;
12387
12388           case MoveNumberOne:
12389             switch (lastLoadGameStart) {
12390               case GNUChessGame:
12391               case XBoardGame:
12392               case PGNTag:
12393                 break;
12394               case MoveNumberOne:
12395               case EndOfFile:
12396                 gn--;           /* count this game */
12397                 lastLoadGameStart = cm;
12398                 break;
12399               default:
12400                 /* impossible */
12401                 break;
12402             }
12403             break;
12404
12405           case PGNTag:
12406             switch (lastLoadGameStart) {
12407               case GNUChessGame:
12408               case PGNTag:
12409               case MoveNumberOne:
12410               case EndOfFile:
12411                 gn--;           /* count this game */
12412                 lastLoadGameStart = cm;
12413                 break;
12414               case XBoardGame:
12415                 lastLoadGameStart = cm; /* game counted already */
12416                 break;
12417               default:
12418                 /* impossible */
12419                 break;
12420             }
12421             if (gn > 0) {
12422                 do {
12423                     yyboardindex = forwardMostMove;
12424                     cm = (ChessMove) Myylex();
12425                 } while (cm == PGNTag || cm == Comment);
12426             }
12427             break;
12428
12429           case WhiteWins:
12430           case BlackWins:
12431           case GameIsDrawn:
12432             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
12433                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
12434                     != CMAIL_OLD_RESULT) {
12435                     nCmailResults ++ ;
12436                     cmailResult[  CMAIL_MAX_GAMES
12437                                 - gn - 1] = CMAIL_OLD_RESULT;
12438                 }
12439             }
12440             break;
12441
12442           case NormalMove:
12443             /* Only a NormalMove can be at the start of a game
12444              * without a position diagram. */
12445             if (lastLoadGameStart == EndOfFile ) {
12446               gn--;
12447               lastLoadGameStart = MoveNumberOne;
12448             }
12449             break;
12450
12451           default:
12452             break;
12453         }
12454     }
12455
12456     if (appData.debugMode)
12457       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
12458
12459     if (cm == XBoardGame) {
12460         /* Skip any header junk before position diagram and/or move 1 */
12461         for (;;) {
12462             yyboardindex = forwardMostMove;
12463             cm = (ChessMove) Myylex();
12464
12465             if (cm == EndOfFile ||
12466                 cm == GNUChessGame || cm == XBoardGame) {
12467                 /* Empty game; pretend end-of-file and handle later */
12468                 cm = EndOfFile;
12469                 break;
12470             }
12471
12472             if (cm == MoveNumberOne || cm == PositionDiagram ||
12473                 cm == PGNTag || cm == Comment)
12474               break;
12475         }
12476     } else if (cm == GNUChessGame) {
12477         if (gameInfo.event != NULL) {
12478             free(gameInfo.event);
12479         }
12480         gameInfo.event = StrSave(yy_text);
12481     }
12482
12483     startedFromSetupPosition = FALSE;
12484     while (cm == PGNTag) {
12485         if (appData.debugMode)
12486           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
12487         err = ParsePGNTag(yy_text, &gameInfo);
12488         if (!err) numPGNTags++;
12489
12490         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
12491         if(gameInfo.variant != oldVariant) {
12492             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
12493             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
12494             InitPosition(TRUE);
12495             oldVariant = gameInfo.variant;
12496             if (appData.debugMode)
12497               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
12498         }
12499
12500
12501         if (gameInfo.fen != NULL) {
12502           Board initial_position;
12503           startedFromSetupPosition = TRUE;
12504           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
12505             Reset(TRUE, TRUE);
12506             DisplayError(_("Bad FEN position in file"), 0);
12507             return FALSE;
12508           }
12509           CopyBoard(boards[0], initial_position);
12510           if (blackPlaysFirst) {
12511             currentMove = forwardMostMove = backwardMostMove = 1;
12512             CopyBoard(boards[1], initial_position);
12513             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12514             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12515             timeRemaining[0][1] = whiteTimeRemaining;
12516             timeRemaining[1][1] = blackTimeRemaining;
12517             if (commentList[0] != NULL) {
12518               commentList[1] = commentList[0];
12519               commentList[0] = NULL;
12520             }
12521           } else {
12522             currentMove = forwardMostMove = backwardMostMove = 0;
12523           }
12524           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12525           {   int i;
12526               initialRulePlies = FENrulePlies;
12527               for( i=0; i< nrCastlingRights; i++ )
12528                   initialRights[i] = initial_position[CASTLING][i];
12529           }
12530           yyboardindex = forwardMostMove;
12531           free(gameInfo.fen);
12532           gameInfo.fen = NULL;
12533         }
12534
12535         yyboardindex = forwardMostMove;
12536         cm = (ChessMove) Myylex();
12537
12538         /* Handle comments interspersed among the tags */
12539         while (cm == Comment) {
12540             char *p;
12541             if (appData.debugMode)
12542               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12543             p = yy_text;
12544             AppendComment(currentMove, p, FALSE);
12545             yyboardindex = forwardMostMove;
12546             cm = (ChessMove) Myylex();
12547         }
12548     }
12549
12550     /* don't rely on existence of Event tag since if game was
12551      * pasted from clipboard the Event tag may not exist
12552      */
12553     if (numPGNTags > 0){
12554         char *tags;
12555         if (gameInfo.variant == VariantNormal) {
12556           VariantClass v = StringToVariant(gameInfo.event);
12557           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12558           if(v < VariantShogi) gameInfo.variant = v;
12559         }
12560         if (!matchMode) {
12561           if( appData.autoDisplayTags ) {
12562             tags = PGNTags(&gameInfo);
12563             TagsPopUp(tags, CmailMsg());
12564             free(tags);
12565           }
12566         }
12567     } else {
12568         /* Make something up, but don't display it now */
12569         SetGameInfo();
12570         TagsPopDown();
12571     }
12572
12573     if (cm == PositionDiagram) {
12574         int i, j;
12575         char *p;
12576         Board initial_position;
12577
12578         if (appData.debugMode)
12579           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12580
12581         if (!startedFromSetupPosition) {
12582             p = yy_text;
12583             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12584               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12585                 switch (*p) {
12586                   case '{':
12587                   case '[':
12588                   case '-':
12589                   case ' ':
12590                   case '\t':
12591                   case '\n':
12592                   case '\r':
12593                     break;
12594                   default:
12595                     initial_position[i][j++] = CharToPiece(*p);
12596                     break;
12597                 }
12598             while (*p == ' ' || *p == '\t' ||
12599                    *p == '\n' || *p == '\r') p++;
12600
12601             if (strncmp(p, "black", strlen("black"))==0)
12602               blackPlaysFirst = TRUE;
12603             else
12604               blackPlaysFirst = FALSE;
12605             startedFromSetupPosition = TRUE;
12606
12607             CopyBoard(boards[0], initial_position);
12608             if (blackPlaysFirst) {
12609                 currentMove = forwardMostMove = backwardMostMove = 1;
12610                 CopyBoard(boards[1], initial_position);
12611                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12612                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12613                 timeRemaining[0][1] = whiteTimeRemaining;
12614                 timeRemaining[1][1] = blackTimeRemaining;
12615                 if (commentList[0] != NULL) {
12616                     commentList[1] = commentList[0];
12617                     commentList[0] = NULL;
12618                 }
12619             } else {
12620                 currentMove = forwardMostMove = backwardMostMove = 0;
12621             }
12622         }
12623         yyboardindex = forwardMostMove;
12624         cm = (ChessMove) Myylex();
12625     }
12626
12627   if(!creatingBook) {
12628     if (first.pr == NoProc) {
12629         StartChessProgram(&first);
12630     }
12631     InitChessProgram(&first, FALSE);
12632     SendToProgram("force\n", &first);
12633     if (startedFromSetupPosition) {
12634         SendBoard(&first, forwardMostMove);
12635     if (appData.debugMode) {
12636         fprintf(debugFP, "Load Game\n");
12637     }
12638         DisplayBothClocks();
12639     }
12640   }
12641
12642     /* [HGM] server: flag to write setup moves in broadcast file as one */
12643     loadFlag = appData.suppressLoadMoves;
12644
12645     while (cm == Comment) {
12646         char *p;
12647         if (appData.debugMode)
12648           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12649         p = yy_text;
12650         AppendComment(currentMove, p, FALSE);
12651         yyboardindex = forwardMostMove;
12652         cm = (ChessMove) Myylex();
12653     }
12654
12655     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12656         cm == WhiteWins || cm == BlackWins ||
12657         cm == GameIsDrawn || cm == GameUnfinished) {
12658         DisplayMessage("", _("No moves in game"));
12659         if (cmailMsgLoaded) {
12660             if (appData.debugMode)
12661               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12662             ClearHighlights();
12663             flipView = FALSE;
12664         }
12665         DrawPosition(FALSE, boards[currentMove]);
12666         DisplayBothClocks();
12667         gameMode = EditGame;
12668         ModeHighlight();
12669         gameFileFP = NULL;
12670         cmailOldMove = 0;
12671         return TRUE;
12672     }
12673
12674     // [HGM] PV info: routine tests if comment empty
12675     if (!matchMode && (pausing || appData.timeDelay != 0)) {
12676         DisplayComment(currentMove - 1, commentList[currentMove]);
12677     }
12678     if (!matchMode && appData.timeDelay != 0)
12679       DrawPosition(FALSE, boards[currentMove]);
12680
12681     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12682       programStats.ok_to_send = 1;
12683     }
12684
12685     /* if the first token after the PGN tags is a move
12686      * and not move number 1, retrieve it from the parser
12687      */
12688     if (cm != MoveNumberOne)
12689         LoadGameOneMove(cm);
12690
12691     /* load the remaining moves from the file */
12692     while (LoadGameOneMove(EndOfFile)) {
12693       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12694       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12695     }
12696
12697     /* rewind to the start of the game */
12698     currentMove = backwardMostMove;
12699
12700     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12701
12702     if (oldGameMode == AnalyzeFile) {
12703       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
12704       AnalyzeFileEvent();
12705     } else
12706     if (oldGameMode == AnalyzeMode) {
12707       AnalyzeFileEvent();
12708     }
12709
12710     if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
12711         long int w, b; // [HGM] adjourn: restore saved clock times
12712         char *p = strstr(gameInfo.resultDetails, "(Clocks:");
12713         if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
12714             timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
12715             timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
12716         }
12717     }
12718
12719     if(creatingBook) return TRUE;
12720     if (!matchMode && pos > 0) {
12721         ToNrEvent(pos); // [HGM] no autoplay if selected on position
12722     } else
12723     if (matchMode || appData.timeDelay == 0) {
12724       ToEndEvent();
12725     } else if (appData.timeDelay > 0) {
12726       AutoPlayGameLoop();
12727     }
12728
12729     if (appData.debugMode)
12730         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
12731
12732     loadFlag = 0; /* [HGM] true game starts */
12733     return TRUE;
12734 }
12735
12736 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
12737 int
12738 ReloadPosition (int offset)
12739 {
12740     int positionNumber = lastLoadPositionNumber + offset;
12741     if (lastLoadPositionFP == NULL) {
12742         DisplayError(_("No position has been loaded yet"), 0);
12743         return FALSE;
12744     }
12745     if (positionNumber <= 0) {
12746         DisplayError(_("Can't back up any further"), 0);
12747         return FALSE;
12748     }
12749     return LoadPosition(lastLoadPositionFP, positionNumber,
12750                         lastLoadPositionTitle);
12751 }
12752
12753 /* Load the nth position from the given file */
12754 int
12755 LoadPositionFromFile (char *filename, int n, char *title)
12756 {
12757     FILE *f;
12758     char buf[MSG_SIZ];
12759
12760     if (strcmp(filename, "-") == 0) {
12761         return LoadPosition(stdin, n, "stdin");
12762     } else {
12763         f = fopen(filename, "rb");
12764         if (f == NULL) {
12765             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12766             DisplayError(buf, errno);
12767             return FALSE;
12768         } else {
12769             return LoadPosition(f, n, title);
12770         }
12771     }
12772 }
12773
12774 /* Load the nth position from the given open file, and close it */
12775 int
12776 LoadPosition (FILE *f, int positionNumber, char *title)
12777 {
12778     char *p, line[MSG_SIZ];
12779     Board initial_position;
12780     int i, j, fenMode, pn;
12781
12782     if (gameMode == Training )
12783         SetTrainingModeOff();
12784
12785     if (gameMode != BeginningOfGame) {
12786         Reset(FALSE, TRUE);
12787     }
12788     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12789         fclose(lastLoadPositionFP);
12790     }
12791     if (positionNumber == 0) positionNumber = 1;
12792     lastLoadPositionFP = f;
12793     lastLoadPositionNumber = positionNumber;
12794     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12795     if (first.pr == NoProc && !appData.noChessProgram) {
12796       StartChessProgram(&first);
12797       InitChessProgram(&first, FALSE);
12798     }
12799     pn = positionNumber;
12800     if (positionNumber < 0) {
12801         /* Negative position number means to seek to that byte offset */
12802         if (fseek(f, -positionNumber, 0) == -1) {
12803             DisplayError(_("Can't seek on position file"), 0);
12804             return FALSE;
12805         };
12806         pn = 1;
12807     } else {
12808         if (fseek(f, 0, 0) == -1) {
12809             if (f == lastLoadPositionFP ?
12810                 positionNumber == lastLoadPositionNumber + 1 :
12811                 positionNumber == 1) {
12812                 pn = 1;
12813             } else {
12814                 DisplayError(_("Can't seek on position file"), 0);
12815                 return FALSE;
12816             }
12817         }
12818     }
12819     /* See if this file is FEN or old-style xboard */
12820     if (fgets(line, MSG_SIZ, f) == NULL) {
12821         DisplayError(_("Position not found in file"), 0);
12822         return FALSE;
12823     }
12824     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12825     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12826
12827     if (pn >= 2) {
12828         if (fenMode || line[0] == '#') pn--;
12829         while (pn > 0) {
12830             /* skip positions before number pn */
12831             if (fgets(line, MSG_SIZ, f) == NULL) {
12832                 Reset(TRUE, TRUE);
12833                 DisplayError(_("Position not found in file"), 0);
12834                 return FALSE;
12835             }
12836             if (fenMode || line[0] == '#') pn--;
12837         }
12838     }
12839
12840     if (fenMode) {
12841         if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
12842             DisplayError(_("Bad FEN position in file"), 0);
12843             return FALSE;
12844         }
12845     } else {
12846         (void) fgets(line, MSG_SIZ, f);
12847         (void) fgets(line, MSG_SIZ, f);
12848
12849         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12850             (void) fgets(line, MSG_SIZ, f);
12851             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12852                 if (*p == ' ')
12853                   continue;
12854                 initial_position[i][j++] = CharToPiece(*p);
12855             }
12856         }
12857
12858         blackPlaysFirst = FALSE;
12859         if (!feof(f)) {
12860             (void) fgets(line, MSG_SIZ, f);
12861             if (strncmp(line, "black", strlen("black"))==0)
12862               blackPlaysFirst = TRUE;
12863         }
12864     }
12865     startedFromSetupPosition = TRUE;
12866
12867     CopyBoard(boards[0], initial_position);
12868     if (blackPlaysFirst) {
12869         currentMove = forwardMostMove = backwardMostMove = 1;
12870         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12871         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12872         CopyBoard(boards[1], initial_position);
12873         DisplayMessage("", _("Black to play"));
12874     } else {
12875         currentMove = forwardMostMove = backwardMostMove = 0;
12876         DisplayMessage("", _("White to play"));
12877     }
12878     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12879     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12880         SendToProgram("force\n", &first);
12881         SendBoard(&first, forwardMostMove);
12882     }
12883     if (appData.debugMode) {
12884 int i, j;
12885   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12886   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12887         fprintf(debugFP, "Load Position\n");
12888     }
12889
12890     if (positionNumber > 1) {
12891       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12892         DisplayTitle(line);
12893     } else {
12894         DisplayTitle(title);
12895     }
12896     gameMode = EditGame;
12897     ModeHighlight();
12898     ResetClocks();
12899     timeRemaining[0][1] = whiteTimeRemaining;
12900     timeRemaining[1][1] = blackTimeRemaining;
12901     DrawPosition(FALSE, boards[currentMove]);
12902
12903     return TRUE;
12904 }
12905
12906
12907 void
12908 CopyPlayerNameIntoFileName (char **dest, char *src)
12909 {
12910     while (*src != NULLCHAR && *src != ',') {
12911         if (*src == ' ') {
12912             *(*dest)++ = '_';
12913             src++;
12914         } else {
12915             *(*dest)++ = *src++;
12916         }
12917     }
12918 }
12919
12920 char *
12921 DefaultFileName (char *ext)
12922 {
12923     static char def[MSG_SIZ];
12924     char *p;
12925
12926     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12927         p = def;
12928         CopyPlayerNameIntoFileName(&p, gameInfo.white);
12929         *p++ = '-';
12930         CopyPlayerNameIntoFileName(&p, gameInfo.black);
12931         *p++ = '.';
12932         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12933     } else {
12934         def[0] = NULLCHAR;
12935     }
12936     return def;
12937 }
12938
12939 /* Save the current game to the given file */
12940 int
12941 SaveGameToFile (char *filename, int append)
12942 {
12943     FILE *f;
12944     char buf[MSG_SIZ];
12945     int result, i, t,tot=0;
12946
12947     if (strcmp(filename, "-") == 0) {
12948         return SaveGame(stdout, 0, NULL);
12949     } else {
12950         for(i=0; i<10; i++) { // upto 10 tries
12951              f = fopen(filename, append ? "a" : "w");
12952              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12953              if(f || errno != 13) break;
12954              DoSleep(t = 5 + random()%11); // wait 5-15 msec
12955              tot += t;
12956         }
12957         if (f == NULL) {
12958             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12959             DisplayError(buf, errno);
12960             return FALSE;
12961         } else {
12962             safeStrCpy(buf, lastMsg, MSG_SIZ);
12963             DisplayMessage(_("Waiting for access to save file"), "");
12964             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
12965             DisplayMessage(_("Saving game"), "");
12966             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
12967             result = SaveGame(f, 0, NULL);
12968             DisplayMessage(buf, "");
12969             return result;
12970         }
12971     }
12972 }
12973
12974 char *
12975 SavePart (char *str)
12976 {
12977     static char buf[MSG_SIZ];
12978     char *p;
12979
12980     p = strchr(str, ' ');
12981     if (p == NULL) return str;
12982     strncpy(buf, str, p - str);
12983     buf[p - str] = NULLCHAR;
12984     return buf;
12985 }
12986
12987 #define PGN_MAX_LINE 75
12988
12989 #define PGN_SIDE_WHITE  0
12990 #define PGN_SIDE_BLACK  1
12991
12992 static int
12993 FindFirstMoveOutOfBook (int side)
12994 {
12995     int result = -1;
12996
12997     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12998         int index = backwardMostMove;
12999         int has_book_hit = 0;
13000
13001         if( (index % 2) != side ) {
13002             index++;
13003         }
13004
13005         while( index < forwardMostMove ) {
13006             /* Check to see if engine is in book */
13007             int depth = pvInfoList[index].depth;
13008             int score = pvInfoList[index].score;
13009             int in_book = 0;
13010
13011             if( depth <= 2 ) {
13012                 in_book = 1;
13013             }
13014             else if( score == 0 && depth == 63 ) {
13015                 in_book = 1; /* Zappa */
13016             }
13017             else if( score == 2 && depth == 99 ) {
13018                 in_book = 1; /* Abrok */
13019             }
13020
13021             has_book_hit += in_book;
13022
13023             if( ! in_book ) {
13024                 result = index;
13025
13026                 break;
13027             }
13028
13029             index += 2;
13030         }
13031     }
13032
13033     return result;
13034 }
13035
13036 void
13037 GetOutOfBookInfo (char * buf)
13038 {
13039     int oob[2];
13040     int i;
13041     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13042
13043     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13044     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13045
13046     *buf = '\0';
13047
13048     if( oob[0] >= 0 || oob[1] >= 0 ) {
13049         for( i=0; i<2; i++ ) {
13050             int idx = oob[i];
13051
13052             if( idx >= 0 ) {
13053                 if( i > 0 && oob[0] >= 0 ) {
13054                     strcat( buf, "   " );
13055                 }
13056
13057                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
13058                 sprintf( buf+strlen(buf), "%s%.2f",
13059                     pvInfoList[idx].score >= 0 ? "+" : "",
13060                     pvInfoList[idx].score / 100.0 );
13061             }
13062         }
13063     }
13064 }
13065
13066 /* Save game in PGN style and close the file */
13067 int
13068 SaveGamePGN (FILE *f)
13069 {
13070     int i, offset, linelen, newblock;
13071 //    char *movetext;
13072     char numtext[32];
13073     int movelen, numlen, blank;
13074     char move_buffer[100]; /* [AS] Buffer for move+PV info */
13075
13076     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13077
13078     PrintPGNTags(f, &gameInfo);
13079
13080     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
13081
13082     if (backwardMostMove > 0 || startedFromSetupPosition) {
13083         char *fen = PositionToFEN(backwardMostMove, NULL, 1);
13084         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
13085         fprintf(f, "\n{--------------\n");
13086         PrintPosition(f, backwardMostMove);
13087         fprintf(f, "--------------}\n");
13088         free(fen);
13089     }
13090     else {
13091         /* [AS] Out of book annotation */
13092         if( appData.saveOutOfBookInfo ) {
13093             char buf[64];
13094
13095             GetOutOfBookInfo( buf );
13096
13097             if( buf[0] != '\0' ) {
13098                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
13099             }
13100         }
13101
13102         fprintf(f, "\n");
13103     }
13104
13105     i = backwardMostMove;
13106     linelen = 0;
13107     newblock = TRUE;
13108
13109     while (i < forwardMostMove) {
13110         /* Print comments preceding this move */
13111         if (commentList[i] != NULL) {
13112             if (linelen > 0) fprintf(f, "\n");
13113             fprintf(f, "%s", commentList[i]);
13114             linelen = 0;
13115             newblock = TRUE;
13116         }
13117
13118         /* Format move number */
13119         if ((i % 2) == 0)
13120           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13121         else
13122           if (newblock)
13123             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13124           else
13125             numtext[0] = NULLCHAR;
13126
13127         numlen = strlen(numtext);
13128         newblock = FALSE;
13129
13130         /* Print move number */
13131         blank = linelen > 0 && numlen > 0;
13132         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13133             fprintf(f, "\n");
13134             linelen = 0;
13135             blank = 0;
13136         }
13137         if (blank) {
13138             fprintf(f, " ");
13139             linelen++;
13140         }
13141         fprintf(f, "%s", numtext);
13142         linelen += numlen;
13143
13144         /* Get move */
13145         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13146         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13147
13148         /* Print move */
13149         blank = linelen > 0 && movelen > 0;
13150         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13151             fprintf(f, "\n");
13152             linelen = 0;
13153             blank = 0;
13154         }
13155         if (blank) {
13156             fprintf(f, " ");
13157             linelen++;
13158         }
13159         fprintf(f, "%s", move_buffer);
13160         linelen += movelen;
13161
13162         /* [AS] Add PV info if present */
13163         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13164             /* [HGM] add time */
13165             char buf[MSG_SIZ]; int seconds;
13166
13167             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13168
13169             if( seconds <= 0)
13170               buf[0] = 0;
13171             else
13172               if( seconds < 30 )
13173                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13174               else
13175                 {
13176                   seconds = (seconds + 4)/10; // round to full seconds
13177                   if( seconds < 60 )
13178                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
13179                   else
13180                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
13181                 }
13182
13183             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
13184                       pvInfoList[i].score >= 0 ? "+" : "",
13185                       pvInfoList[i].score / 100.0,
13186                       pvInfoList[i].depth,
13187                       buf );
13188
13189             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
13190
13191             /* Print score/depth */
13192             blank = linelen > 0 && movelen > 0;
13193             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13194                 fprintf(f, "\n");
13195                 linelen = 0;
13196                 blank = 0;
13197             }
13198             if (blank) {
13199                 fprintf(f, " ");
13200                 linelen++;
13201             }
13202             fprintf(f, "%s", move_buffer);
13203             linelen += movelen;
13204         }
13205
13206         i++;
13207     }
13208
13209     /* Start a new line */
13210     if (linelen > 0) fprintf(f, "\n");
13211
13212     /* Print comments after last move */
13213     if (commentList[i] != NULL) {
13214         fprintf(f, "%s\n", commentList[i]);
13215     }
13216
13217     /* Print result */
13218     if (gameInfo.resultDetails != NULL &&
13219         gameInfo.resultDetails[0] != NULLCHAR) {
13220         char buf[MSG_SIZ], *p = gameInfo.resultDetails;
13221         if(gameInfo.result == GameUnfinished && appData.clockMode &&
13222            (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
13223             snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
13224         fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
13225     } else {
13226         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13227     }
13228
13229     fclose(f);
13230     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13231     return TRUE;
13232 }
13233
13234 /* Save game in old style and close the file */
13235 int
13236 SaveGameOldStyle (FILE *f)
13237 {
13238     int i, offset;
13239     time_t tm;
13240
13241     tm = time((time_t *) NULL);
13242
13243     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
13244     PrintOpponents(f);
13245
13246     if (backwardMostMove > 0 || startedFromSetupPosition) {
13247         fprintf(f, "\n[--------------\n");
13248         PrintPosition(f, backwardMostMove);
13249         fprintf(f, "--------------]\n");
13250     } else {
13251         fprintf(f, "\n");
13252     }
13253
13254     i = backwardMostMove;
13255     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13256
13257     while (i < forwardMostMove) {
13258         if (commentList[i] != NULL) {
13259             fprintf(f, "[%s]\n", commentList[i]);
13260         }
13261
13262         if ((i % 2) == 1) {
13263             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
13264             i++;
13265         } else {
13266             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
13267             i++;
13268             if (commentList[i] != NULL) {
13269                 fprintf(f, "\n");
13270                 continue;
13271             }
13272             if (i >= forwardMostMove) {
13273                 fprintf(f, "\n");
13274                 break;
13275             }
13276             fprintf(f, "%s\n", parseList[i]);
13277             i++;
13278         }
13279     }
13280
13281     if (commentList[i] != NULL) {
13282         fprintf(f, "[%s]\n", commentList[i]);
13283     }
13284
13285     /* This isn't really the old style, but it's close enough */
13286     if (gameInfo.resultDetails != NULL &&
13287         gameInfo.resultDetails[0] != NULLCHAR) {
13288         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13289                 gameInfo.resultDetails);
13290     } else {
13291         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13292     }
13293
13294     fclose(f);
13295     return TRUE;
13296 }
13297
13298 /* Save the current game to open file f and close the file */
13299 int
13300 SaveGame (FILE *f, int dummy, char *dummy2)
13301 {
13302     if (gameMode == EditPosition) EditPositionDone(TRUE);
13303     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13304     if (appData.oldSaveStyle)
13305       return SaveGameOldStyle(f);
13306     else
13307       return SaveGamePGN(f);
13308 }
13309
13310 /* Save the current position to the given file */
13311 int
13312 SavePositionToFile (char *filename)
13313 {
13314     FILE *f;
13315     char buf[MSG_SIZ];
13316
13317     if (strcmp(filename, "-") == 0) {
13318         return SavePosition(stdout, 0, NULL);
13319     } else {
13320         f = fopen(filename, "a");
13321         if (f == NULL) {
13322             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13323             DisplayError(buf, errno);
13324             return FALSE;
13325         } else {
13326             safeStrCpy(buf, lastMsg, MSG_SIZ);
13327             DisplayMessage(_("Waiting for access to save file"), "");
13328             flock(fileno(f), LOCK_EX); // [HGM] lock
13329             DisplayMessage(_("Saving position"), "");
13330             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
13331             SavePosition(f, 0, NULL);
13332             DisplayMessage(buf, "");
13333             return TRUE;
13334         }
13335     }
13336 }
13337
13338 /* Save the current position to the given open file and close the file */
13339 int
13340 SavePosition (FILE *f, int dummy, char *dummy2)
13341 {
13342     time_t tm;
13343     char *fen;
13344
13345     if (gameMode == EditPosition) EditPositionDone(TRUE);
13346     if (appData.oldSaveStyle) {
13347         tm = time((time_t *) NULL);
13348
13349         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
13350         PrintOpponents(f);
13351         fprintf(f, "[--------------\n");
13352         PrintPosition(f, currentMove);
13353         fprintf(f, "--------------]\n");
13354     } else {
13355         fen = PositionToFEN(currentMove, NULL, 1);
13356         fprintf(f, "%s\n", fen);
13357         free(fen);
13358     }
13359     fclose(f);
13360     return TRUE;
13361 }
13362
13363 void
13364 ReloadCmailMsgEvent (int unregister)
13365 {
13366 #if !WIN32
13367     static char *inFilename = NULL;
13368     static char *outFilename;
13369     int i;
13370     struct stat inbuf, outbuf;
13371     int status;
13372
13373     /* Any registered moves are unregistered if unregister is set, */
13374     /* i.e. invoked by the signal handler */
13375     if (unregister) {
13376         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13377             cmailMoveRegistered[i] = FALSE;
13378             if (cmailCommentList[i] != NULL) {
13379                 free(cmailCommentList[i]);
13380                 cmailCommentList[i] = NULL;
13381             }
13382         }
13383         nCmailMovesRegistered = 0;
13384     }
13385
13386     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13387         cmailResult[i] = CMAIL_NOT_RESULT;
13388     }
13389     nCmailResults = 0;
13390
13391     if (inFilename == NULL) {
13392         /* Because the filenames are static they only get malloced once  */
13393         /* and they never get freed                                      */
13394         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
13395         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
13396
13397         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
13398         sprintf(outFilename, "%s.out", appData.cmailGameName);
13399     }
13400
13401     status = stat(outFilename, &outbuf);
13402     if (status < 0) {
13403         cmailMailedMove = FALSE;
13404     } else {
13405         status = stat(inFilename, &inbuf);
13406         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
13407     }
13408
13409     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
13410        counts the games, notes how each one terminated, etc.
13411
13412        It would be nice to remove this kludge and instead gather all
13413        the information while building the game list.  (And to keep it
13414        in the game list nodes instead of having a bunch of fixed-size
13415        parallel arrays.)  Note this will require getting each game's
13416        termination from the PGN tags, as the game list builder does
13417        not process the game moves.  --mann
13418        */
13419     cmailMsgLoaded = TRUE;
13420     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
13421
13422     /* Load first game in the file or popup game menu */
13423     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
13424
13425 #endif /* !WIN32 */
13426     return;
13427 }
13428
13429 int
13430 RegisterMove ()
13431 {
13432     FILE *f;
13433     char string[MSG_SIZ];
13434
13435     if (   cmailMailedMove
13436         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
13437         return TRUE;            /* Allow free viewing  */
13438     }
13439
13440     /* Unregister move to ensure that we don't leave RegisterMove        */
13441     /* with the move registered when the conditions for registering no   */
13442     /* longer hold                                                       */
13443     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
13444         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
13445         nCmailMovesRegistered --;
13446
13447         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
13448           {
13449               free(cmailCommentList[lastLoadGameNumber - 1]);
13450               cmailCommentList[lastLoadGameNumber - 1] = NULL;
13451           }
13452     }
13453
13454     if (cmailOldMove == -1) {
13455         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
13456         return FALSE;
13457     }
13458
13459     if (currentMove > cmailOldMove + 1) {
13460         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
13461         return FALSE;
13462     }
13463
13464     if (currentMove < cmailOldMove) {
13465         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
13466         return FALSE;
13467     }
13468
13469     if (forwardMostMove > currentMove) {
13470         /* Silently truncate extra moves */
13471         TruncateGame();
13472     }
13473
13474     if (   (currentMove == cmailOldMove + 1)
13475         || (   (currentMove == cmailOldMove)
13476             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
13477                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
13478         if (gameInfo.result != GameUnfinished) {
13479             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
13480         }
13481
13482         if (commentList[currentMove] != NULL) {
13483             cmailCommentList[lastLoadGameNumber - 1]
13484               = StrSave(commentList[currentMove]);
13485         }
13486         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
13487
13488         if (appData.debugMode)
13489           fprintf(debugFP, "Saving %s for game %d\n",
13490                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
13491
13492         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
13493
13494         f = fopen(string, "w");
13495         if (appData.oldSaveStyle) {
13496             SaveGameOldStyle(f); /* also closes the file */
13497
13498             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
13499             f = fopen(string, "w");
13500             SavePosition(f, 0, NULL); /* also closes the file */
13501         } else {
13502             fprintf(f, "{--------------\n");
13503             PrintPosition(f, currentMove);
13504             fprintf(f, "--------------}\n\n");
13505
13506             SaveGame(f, 0, NULL); /* also closes the file*/
13507         }
13508
13509         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
13510         nCmailMovesRegistered ++;
13511     } else if (nCmailGames == 1) {
13512         DisplayError(_("You have not made a move yet"), 0);
13513         return FALSE;
13514     }
13515
13516     return TRUE;
13517 }
13518
13519 void
13520 MailMoveEvent ()
13521 {
13522 #if !WIN32
13523     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
13524     FILE *commandOutput;
13525     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
13526     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
13527     int nBuffers;
13528     int i;
13529     int archived;
13530     char *arcDir;
13531
13532     if (! cmailMsgLoaded) {
13533         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
13534         return;
13535     }
13536
13537     if (nCmailGames == nCmailResults) {
13538         DisplayError(_("No unfinished games"), 0);
13539         return;
13540     }
13541
13542 #if CMAIL_PROHIBIT_REMAIL
13543     if (cmailMailedMove) {
13544       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);
13545         DisplayError(msg, 0);
13546         return;
13547     }
13548 #endif
13549
13550     if (! (cmailMailedMove || RegisterMove())) return;
13551
13552     if (   cmailMailedMove
13553         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13554       snprintf(string, MSG_SIZ, partCommandString,
13555                appData.debugMode ? " -v" : "", appData.cmailGameName);
13556         commandOutput = popen(string, "r");
13557
13558         if (commandOutput == NULL) {
13559             DisplayError(_("Failed to invoke cmail"), 0);
13560         } else {
13561             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13562                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13563             }
13564             if (nBuffers > 1) {
13565                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13566                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13567                 nBytes = MSG_SIZ - 1;
13568             } else {
13569                 (void) memcpy(msg, buffer, nBytes);
13570             }
13571             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13572
13573             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13574                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
13575
13576                 archived = TRUE;
13577                 for (i = 0; i < nCmailGames; i ++) {
13578                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
13579                         archived = FALSE;
13580                     }
13581                 }
13582                 if (   archived
13583                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13584                         != NULL)) {
13585                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13586                            arcDir,
13587                            appData.cmailGameName,
13588                            gameInfo.date);
13589                     LoadGameFromFile(buffer, 1, buffer, FALSE);
13590                     cmailMsgLoaded = FALSE;
13591                 }
13592             }
13593
13594             DisplayInformation(msg);
13595             pclose(commandOutput);
13596         }
13597     } else {
13598         if ((*cmailMsg) != '\0') {
13599             DisplayInformation(cmailMsg);
13600         }
13601     }
13602
13603     return;
13604 #endif /* !WIN32 */
13605 }
13606
13607 char *
13608 CmailMsg ()
13609 {
13610 #if WIN32
13611     return NULL;
13612 #else
13613     int  prependComma = 0;
13614     char number[5];
13615     char string[MSG_SIZ];       /* Space for game-list */
13616     int  i;
13617
13618     if (!cmailMsgLoaded) return "";
13619
13620     if (cmailMailedMove) {
13621       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13622     } else {
13623         /* Create a list of games left */
13624       snprintf(string, MSG_SIZ, "[");
13625         for (i = 0; i < nCmailGames; i ++) {
13626             if (! (   cmailMoveRegistered[i]
13627                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13628                 if (prependComma) {
13629                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13630                 } else {
13631                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13632                     prependComma = 1;
13633                 }
13634
13635                 strcat(string, number);
13636             }
13637         }
13638         strcat(string, "]");
13639
13640         if (nCmailMovesRegistered + nCmailResults == 0) {
13641             switch (nCmailGames) {
13642               case 1:
13643                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13644                 break;
13645
13646               case 2:
13647                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13648                 break;
13649
13650               default:
13651                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13652                          nCmailGames);
13653                 break;
13654             }
13655         } else {
13656             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13657               case 1:
13658                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13659                          string);
13660                 break;
13661
13662               case 0:
13663                 if (nCmailResults == nCmailGames) {
13664                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13665                 } else {
13666                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13667                 }
13668                 break;
13669
13670               default:
13671                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13672                          string);
13673             }
13674         }
13675     }
13676     return cmailMsg;
13677 #endif /* WIN32 */
13678 }
13679
13680 void
13681 ResetGameEvent ()
13682 {
13683     if (gameMode == Training)
13684       SetTrainingModeOff();
13685
13686     Reset(TRUE, TRUE);
13687     cmailMsgLoaded = FALSE;
13688     if (appData.icsActive) {
13689       SendToICS(ics_prefix);
13690       SendToICS("refresh\n");
13691     }
13692 }
13693
13694 void
13695 ExitEvent (int status)
13696 {
13697     exiting++;
13698     if (exiting > 2) {
13699       /* Give up on clean exit */
13700       exit(status);
13701     }
13702     if (exiting > 1) {
13703       /* Keep trying for clean exit */
13704       return;
13705     }
13706
13707     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
13708
13709     if (telnetISR != NULL) {
13710       RemoveInputSource(telnetISR);
13711     }
13712     if (icsPR != NoProc) {
13713       DestroyChildProcess(icsPR, TRUE);
13714     }
13715
13716     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
13717     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
13718
13719     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
13720     /* make sure this other one finishes before killing it!                  */
13721     if(endingGame) { int count = 0;
13722         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
13723         while(endingGame && count++ < 10) DoSleep(1);
13724         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
13725     }
13726
13727     /* Kill off chess programs */
13728     if (first.pr != NoProc) {
13729         ExitAnalyzeMode();
13730
13731         DoSleep( appData.delayBeforeQuit );
13732         SendToProgram("quit\n", &first);
13733         DoSleep( appData.delayAfterQuit );
13734         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
13735     }
13736     if (second.pr != NoProc) {
13737         DoSleep( appData.delayBeforeQuit );
13738         SendToProgram("quit\n", &second);
13739         DoSleep( appData.delayAfterQuit );
13740         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
13741     }
13742     if (first.isr != NULL) {
13743         RemoveInputSource(first.isr);
13744     }
13745     if (second.isr != NULL) {
13746         RemoveInputSource(second.isr);
13747     }
13748
13749     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13750     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13751
13752     ShutDownFrontEnd();
13753     exit(status);
13754 }
13755
13756 void
13757 PauseEngine (ChessProgramState *cps)
13758 {
13759     SendToProgram("pause\n", cps);
13760     cps->pause = 2;
13761 }
13762
13763 void
13764 UnPauseEngine (ChessProgramState *cps)
13765 {
13766     SendToProgram("resume\n", cps);
13767     cps->pause = 1;
13768 }
13769
13770 void
13771 PauseEvent ()
13772 {
13773     if (appData.debugMode)
13774         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13775     if (pausing) {
13776         pausing = FALSE;
13777         ModeHighlight();
13778         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
13779             StartClocks();
13780             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
13781                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
13782                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
13783             }
13784             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
13785             HandleMachineMove(stashedInputMove, stalledEngine);
13786             stalledEngine = NULL;
13787             return;
13788         }
13789         if (gameMode == MachinePlaysWhite ||
13790             gameMode == TwoMachinesPlay   ||
13791             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
13792             if(first.pause)  UnPauseEngine(&first);
13793             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
13794             if(second.pause) UnPauseEngine(&second);
13795             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
13796             StartClocks();
13797         } else {
13798             DisplayBothClocks();
13799         }
13800         if (gameMode == PlayFromGameFile) {
13801             if (appData.timeDelay >= 0)
13802                 AutoPlayGameLoop();
13803         } else if (gameMode == IcsExamining && pauseExamInvalid) {
13804             Reset(FALSE, TRUE);
13805             SendToICS(ics_prefix);
13806             SendToICS("refresh\n");
13807         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
13808             ForwardInner(forwardMostMove);
13809         }
13810         pauseExamInvalid = FALSE;
13811     } else {
13812         switch (gameMode) {
13813           default:
13814             return;
13815           case IcsExamining:
13816             pauseExamForwardMostMove = forwardMostMove;
13817             pauseExamInvalid = FALSE;
13818             /* fall through */
13819           case IcsObserving:
13820           case IcsPlayingWhite:
13821           case IcsPlayingBlack:
13822             pausing = TRUE;
13823             ModeHighlight();
13824             return;
13825           case PlayFromGameFile:
13826             (void) StopLoadGameTimer();
13827             pausing = TRUE;
13828             ModeHighlight();
13829             break;
13830           case BeginningOfGame:
13831             if (appData.icsActive) return;
13832             /* else fall through */
13833           case MachinePlaysWhite:
13834           case MachinePlaysBlack:
13835           case TwoMachinesPlay:
13836             if (forwardMostMove == 0)
13837               return;           /* don't pause if no one has moved */
13838             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
13839                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
13840                 if(onMove->pause) {           // thinking engine can be paused
13841                     PauseEngine(onMove);      // do it
13842                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
13843                         PauseEngine(onMove->other);
13844                     else
13845                         SendToProgram("easy\n", onMove->other);
13846                     StopClocks();
13847                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
13848             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
13849                 if(first.pause) {
13850                     PauseEngine(&first);
13851                     StopClocks();
13852                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
13853             } else { // human on move, pause pondering by either method
13854                 if(first.pause)
13855                     PauseEngine(&first);
13856                 else if(appData.ponderNextMove)
13857                     SendToProgram("easy\n", &first);
13858                 StopClocks();
13859             }
13860             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
13861           case AnalyzeMode:
13862             pausing = TRUE;
13863             ModeHighlight();
13864             break;
13865         }
13866     }
13867 }
13868
13869 void
13870 EditCommentEvent ()
13871 {
13872     char title[MSG_SIZ];
13873
13874     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13875       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13876     } else {
13877       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13878                WhiteOnMove(currentMove - 1) ? " " : ".. ",
13879                parseList[currentMove - 1]);
13880     }
13881
13882     EditCommentPopUp(currentMove, title, commentList[currentMove]);
13883 }
13884
13885
13886 void
13887 EditTagsEvent ()
13888 {
13889     char *tags = PGNTags(&gameInfo);
13890     bookUp = FALSE;
13891     EditTagsPopUp(tags, NULL);
13892     free(tags);
13893 }
13894
13895 void
13896 ToggleSecond ()
13897 {
13898   if(second.analyzing) {
13899     SendToProgram("exit\n", &second);
13900     second.analyzing = FALSE;
13901   } else {
13902     if (second.pr == NoProc) StartChessProgram(&second);
13903     InitChessProgram(&second, FALSE);
13904     FeedMovesToProgram(&second, currentMove);
13905
13906     SendToProgram("analyze\n", &second);
13907     second.analyzing = TRUE;
13908   }
13909 }
13910
13911 /* Toggle ShowThinking */
13912 void
13913 ToggleShowThinking()
13914 {
13915   appData.showThinking = !appData.showThinking;
13916   ShowThinkingEvent();
13917 }
13918
13919 int
13920 AnalyzeModeEvent ()
13921 {
13922     char buf[MSG_SIZ];
13923
13924     if (!first.analysisSupport) {
13925       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
13926       DisplayError(buf, 0);
13927       return 0;
13928     }
13929     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
13930     if (appData.icsActive) {
13931         if (gameMode != IcsObserving) {
13932           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
13933             DisplayError(buf, 0);
13934             /* secure check */
13935             if (appData.icsEngineAnalyze) {
13936                 if (appData.debugMode)
13937                     fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
13938                 ExitAnalyzeMode();
13939                 ModeHighlight();
13940             }
13941             return 0;
13942         }
13943         /* if enable, user wants to disable icsEngineAnalyze */
13944         if (appData.icsEngineAnalyze) {
13945                 ExitAnalyzeMode();
13946                 ModeHighlight();
13947                 return 0;
13948         }
13949         appData.icsEngineAnalyze = TRUE;
13950         if (appData.debugMode)
13951             fprintf(debugFP, "ICS engine analyze starting... \n");
13952     }
13953
13954     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
13955     if (appData.noChessProgram || gameMode == AnalyzeMode)
13956       return 0;
13957
13958     if (gameMode != AnalyzeFile) {
13959         if (!appData.icsEngineAnalyze) {
13960                EditGameEvent();
13961                if (gameMode != EditGame) return 0;
13962         }
13963         if (!appData.showThinking) ToggleShowThinking();
13964         ResurrectChessProgram();
13965         SendToProgram("analyze\n", &first);
13966         first.analyzing = TRUE;
13967         /*first.maybeThinking = TRUE;*/
13968         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13969         EngineOutputPopUp();
13970     }
13971     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
13972     pausing = FALSE;
13973     ModeHighlight();
13974     SetGameInfo();
13975
13976     StartAnalysisClock();
13977     GetTimeMark(&lastNodeCountTime);
13978     lastNodeCount = 0;
13979     return 1;
13980 }
13981
13982 void
13983 AnalyzeFileEvent ()
13984 {
13985     if (appData.noChessProgram || gameMode == AnalyzeFile)
13986       return;
13987
13988     if (!first.analysisSupport) {
13989       char buf[MSG_SIZ];
13990       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
13991       DisplayError(buf, 0);
13992       return;
13993     }
13994
13995     if (gameMode != AnalyzeMode) {
13996         keepInfo = 1; // mere annotating should not alter PGN tags
13997         EditGameEvent();
13998         keepInfo = 0;
13999         if (gameMode != EditGame) return;
14000         if (!appData.showThinking) ToggleShowThinking();
14001         ResurrectChessProgram();
14002         SendToProgram("analyze\n", &first);
14003         first.analyzing = TRUE;
14004         /*first.maybeThinking = TRUE;*/
14005         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14006         EngineOutputPopUp();
14007     }
14008     gameMode = AnalyzeFile;
14009     pausing = FALSE;
14010     ModeHighlight();
14011
14012     StartAnalysisClock();
14013     GetTimeMark(&lastNodeCountTime);
14014     lastNodeCount = 0;
14015     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14016     AnalysisPeriodicEvent(1);
14017 }
14018
14019 void
14020 MachineWhiteEvent ()
14021 {
14022     char buf[MSG_SIZ];
14023     char *bookHit = NULL;
14024
14025     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14026       return;
14027
14028
14029     if (gameMode == PlayFromGameFile ||
14030         gameMode == TwoMachinesPlay  ||
14031         gameMode == Training         ||
14032         gameMode == AnalyzeMode      ||
14033         gameMode == EndOfGame)
14034         EditGameEvent();
14035
14036     if (gameMode == EditPosition)
14037         EditPositionDone(TRUE);
14038
14039     if (!WhiteOnMove(currentMove)) {
14040         DisplayError(_("It is not White's turn"), 0);
14041         return;
14042     }
14043
14044     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14045       ExitAnalyzeMode();
14046
14047     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14048         gameMode == AnalyzeFile)
14049         TruncateGame();
14050
14051     ResurrectChessProgram();    /* in case it isn't running */
14052     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
14053         gameMode = MachinePlaysWhite;
14054         ResetClocks();
14055     } else
14056     gameMode = MachinePlaysWhite;
14057     pausing = FALSE;
14058     ModeHighlight();
14059     SetGameInfo();
14060     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14061     DisplayTitle(buf);
14062     if (first.sendName) {
14063       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
14064       SendToProgram(buf, &first);
14065     }
14066     if (first.sendTime) {
14067       if (first.useColors) {
14068         SendToProgram("black\n", &first); /*gnu kludge*/
14069       }
14070       SendTimeRemaining(&first, TRUE);
14071     }
14072     if (first.useColors) {
14073       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
14074     }
14075     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14076     SetMachineThinkingEnables();
14077     first.maybeThinking = TRUE;
14078     StartClocks();
14079     firstMove = FALSE;
14080
14081     if (appData.autoFlipView && !flipView) {
14082       flipView = !flipView;
14083       DrawPosition(FALSE, NULL);
14084       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14085     }
14086
14087     if(bookHit) { // [HGM] book: simulate book reply
14088         static char bookMove[MSG_SIZ]; // a bit generous?
14089
14090         programStats.nodes = programStats.depth = programStats.time =
14091         programStats.score = programStats.got_only_move = 0;
14092         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14093
14094         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14095         strcat(bookMove, bookHit);
14096         HandleMachineMove(bookMove, &first);
14097     }
14098 }
14099
14100 void
14101 MachineBlackEvent ()
14102 {
14103   char buf[MSG_SIZ];
14104   char *bookHit = NULL;
14105
14106     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
14107         return;
14108
14109
14110     if (gameMode == PlayFromGameFile ||
14111         gameMode == TwoMachinesPlay  ||
14112         gameMode == Training         ||
14113         gameMode == AnalyzeMode      ||
14114         gameMode == EndOfGame)
14115         EditGameEvent();
14116
14117     if (gameMode == EditPosition)
14118         EditPositionDone(TRUE);
14119
14120     if (WhiteOnMove(currentMove)) {
14121         DisplayError(_("It is not Black's turn"), 0);
14122         return;
14123     }
14124
14125     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14126       ExitAnalyzeMode();
14127
14128     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14129         gameMode == AnalyzeFile)
14130         TruncateGame();
14131
14132     ResurrectChessProgram();    /* in case it isn't running */
14133     gameMode = MachinePlaysBlack;
14134     pausing = FALSE;
14135     ModeHighlight();
14136     SetGameInfo();
14137     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14138     DisplayTitle(buf);
14139     if (first.sendName) {
14140       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14141       SendToProgram(buf, &first);
14142     }
14143     if (first.sendTime) {
14144       if (first.useColors) {
14145         SendToProgram("white\n", &first); /*gnu kludge*/
14146       }
14147       SendTimeRemaining(&first, FALSE);
14148     }
14149     if (first.useColors) {
14150       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14151     }
14152     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14153     SetMachineThinkingEnables();
14154     first.maybeThinking = TRUE;
14155     StartClocks();
14156
14157     if (appData.autoFlipView && flipView) {
14158       flipView = !flipView;
14159       DrawPosition(FALSE, NULL);
14160       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14161     }
14162     if(bookHit) { // [HGM] book: simulate book reply
14163         static char bookMove[MSG_SIZ]; // a bit generous?
14164
14165         programStats.nodes = programStats.depth = programStats.time =
14166         programStats.score = programStats.got_only_move = 0;
14167         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14168
14169         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14170         strcat(bookMove, bookHit);
14171         HandleMachineMove(bookMove, &first);
14172     }
14173 }
14174
14175
14176 void
14177 DisplayTwoMachinesTitle ()
14178 {
14179     char buf[MSG_SIZ];
14180     if (appData.matchGames > 0) {
14181         if(appData.tourneyFile[0]) {
14182           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14183                    gameInfo.white, _("vs."), gameInfo.black,
14184                    nextGame+1, appData.matchGames+1,
14185                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14186         } else
14187         if (first.twoMachinesColor[0] == 'w') {
14188           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14189                    gameInfo.white, _("vs."),  gameInfo.black,
14190                    first.matchWins, second.matchWins,
14191                    matchGame - 1 - (first.matchWins + second.matchWins));
14192         } else {
14193           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14194                    gameInfo.white, _("vs."), gameInfo.black,
14195                    second.matchWins, first.matchWins,
14196                    matchGame - 1 - (first.matchWins + second.matchWins));
14197         }
14198     } else {
14199       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14200     }
14201     DisplayTitle(buf);
14202 }
14203
14204 void
14205 SettingsMenuIfReady ()
14206 {
14207   if (second.lastPing != second.lastPong) {
14208     DisplayMessage("", _("Waiting for second chess program"));
14209     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14210     return;
14211   }
14212   ThawUI();
14213   DisplayMessage("", "");
14214   SettingsPopUp(&second);
14215 }
14216
14217 int
14218 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
14219 {
14220     char buf[MSG_SIZ];
14221     if (cps->pr == NoProc) {
14222         StartChessProgram(cps);
14223         if (cps->protocolVersion == 1) {
14224           retry();
14225           ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
14226         } else {
14227           /* kludge: allow timeout for initial "feature" command */
14228           if(retry != TwoMachinesEventIfReady) FreezeUI();
14229           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
14230           DisplayMessage("", buf);
14231           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
14232         }
14233         return 1;
14234     }
14235     return 0;
14236 }
14237
14238 void
14239 TwoMachinesEvent P((void))
14240 {
14241     int i;
14242     char buf[MSG_SIZ];
14243     ChessProgramState *onmove;
14244     char *bookHit = NULL;
14245     static int stalling = 0;
14246     TimeMark now;
14247     long wait;
14248
14249     if (appData.noChessProgram) return;
14250
14251     switch (gameMode) {
14252       case TwoMachinesPlay:
14253         return;
14254       case MachinePlaysWhite:
14255       case MachinePlaysBlack:
14256         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14257             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
14258             return;
14259         }
14260         /* fall through */
14261       case BeginningOfGame:
14262       case PlayFromGameFile:
14263       case EndOfGame:
14264         EditGameEvent();
14265         if (gameMode != EditGame) return;
14266         break;
14267       case EditPosition:
14268         EditPositionDone(TRUE);
14269         break;
14270       case AnalyzeMode:
14271       case AnalyzeFile:
14272         ExitAnalyzeMode();
14273         break;
14274       case EditGame:
14275       default:
14276         break;
14277     }
14278
14279 //    forwardMostMove = currentMove;
14280     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
14281     startingEngine = TRUE;
14282
14283     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
14284
14285     if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14286     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14287       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14288       return;
14289     }
14290     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14291
14292     if(second.protocolVersion >= 2 && !strstr(second.variants, VariantName(gameInfo.variant))) {
14293         startingEngine = FALSE;
14294         DisplayError("second engine does not play this", 0);
14295         return;
14296     }
14297
14298     if(!stalling) {
14299       InitChessProgram(&second, FALSE); // unbalances ping of second engine
14300       SendToProgram("force\n", &second);
14301       stalling = 1;
14302       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14303       return;
14304     }
14305     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14306     if(appData.matchPause>10000 || appData.matchPause<10)
14307                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14308     wait = SubtractTimeMarks(&now, &pauseStart);
14309     if(wait < appData.matchPause) {
14310         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14311         return;
14312     }
14313     // we are now committed to starting the game
14314     stalling = 0;
14315     DisplayMessage("", "");
14316     if (startedFromSetupPosition) {
14317         SendBoard(&second, backwardMostMove);
14318     if (appData.debugMode) {
14319         fprintf(debugFP, "Two Machines\n");
14320     }
14321     }
14322     for (i = backwardMostMove; i < forwardMostMove; i++) {
14323         SendMoveToProgram(i, &second);
14324     }
14325
14326     gameMode = TwoMachinesPlay;
14327     pausing = startingEngine = FALSE;
14328     ModeHighlight(); // [HGM] logo: this triggers display update of logos
14329     SetGameInfo();
14330     DisplayTwoMachinesTitle();
14331     firstMove = TRUE;
14332     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14333         onmove = &first;
14334     } else {
14335         onmove = &second;
14336     }
14337     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14338     SendToProgram(first.computerString, &first);
14339     if (first.sendName) {
14340       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14341       SendToProgram(buf, &first);
14342     }
14343     SendToProgram(second.computerString, &second);
14344     if (second.sendName) {
14345       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
14346       SendToProgram(buf, &second);
14347     }
14348
14349     ResetClocks();
14350     if (!first.sendTime || !second.sendTime) {
14351         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14352         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14353     }
14354     if (onmove->sendTime) {
14355       if (onmove->useColors) {
14356         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
14357       }
14358       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
14359     }
14360     if (onmove->useColors) {
14361       SendToProgram(onmove->twoMachinesColor, onmove);
14362     }
14363     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
14364 //    SendToProgram("go\n", onmove);
14365     onmove->maybeThinking = TRUE;
14366     SetMachineThinkingEnables();
14367
14368     StartClocks();
14369
14370     if(bookHit) { // [HGM] book: simulate book reply
14371         static char bookMove[MSG_SIZ]; // a bit generous?
14372
14373         programStats.nodes = programStats.depth = programStats.time =
14374         programStats.score = programStats.got_only_move = 0;
14375         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14376
14377         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14378         strcat(bookMove, bookHit);
14379         savedMessage = bookMove; // args for deferred call
14380         savedState = onmove;
14381         ScheduleDelayedEvent(DeferredBookMove, 1);
14382     }
14383 }
14384
14385 void
14386 TrainingEvent ()
14387 {
14388     if (gameMode == Training) {
14389       SetTrainingModeOff();
14390       gameMode = PlayFromGameFile;
14391       DisplayMessage("", _("Training mode off"));
14392     } else {
14393       gameMode = Training;
14394       animateTraining = appData.animate;
14395
14396       /* make sure we are not already at the end of the game */
14397       if (currentMove < forwardMostMove) {
14398         SetTrainingModeOn();
14399         DisplayMessage("", _("Training mode on"));
14400       } else {
14401         gameMode = PlayFromGameFile;
14402         DisplayError(_("Already at end of game"), 0);
14403       }
14404     }
14405     ModeHighlight();
14406 }
14407
14408 void
14409 IcsClientEvent ()
14410 {
14411     if (!appData.icsActive) return;
14412     switch (gameMode) {
14413       case IcsPlayingWhite:
14414       case IcsPlayingBlack:
14415       case IcsObserving:
14416       case IcsIdle:
14417       case BeginningOfGame:
14418       case IcsExamining:
14419         return;
14420
14421       case EditGame:
14422         break;
14423
14424       case EditPosition:
14425         EditPositionDone(TRUE);
14426         break;
14427
14428       case AnalyzeMode:
14429       case AnalyzeFile:
14430         ExitAnalyzeMode();
14431         break;
14432
14433       default:
14434         EditGameEvent();
14435         break;
14436     }
14437
14438     gameMode = IcsIdle;
14439     ModeHighlight();
14440     return;
14441 }
14442
14443 void
14444 EditGameEvent ()
14445 {
14446     int i;
14447
14448     switch (gameMode) {
14449       case Training:
14450         SetTrainingModeOff();
14451         break;
14452       case MachinePlaysWhite:
14453       case MachinePlaysBlack:
14454       case BeginningOfGame:
14455         SendToProgram("force\n", &first);
14456         SetUserThinkingEnables();
14457         break;
14458       case PlayFromGameFile:
14459         (void) StopLoadGameTimer();
14460         if (gameFileFP != NULL) {
14461             gameFileFP = NULL;
14462         }
14463         break;
14464       case EditPosition:
14465         EditPositionDone(TRUE);
14466         break;
14467       case AnalyzeMode:
14468       case AnalyzeFile:
14469         ExitAnalyzeMode();
14470         SendToProgram("force\n", &first);
14471         break;
14472       case TwoMachinesPlay:
14473         GameEnds(EndOfFile, NULL, GE_PLAYER);
14474         ResurrectChessProgram();
14475         SetUserThinkingEnables();
14476         break;
14477       case EndOfGame:
14478         ResurrectChessProgram();
14479         break;
14480       case IcsPlayingBlack:
14481       case IcsPlayingWhite:
14482         DisplayError(_("Warning: You are still playing a game"), 0);
14483         break;
14484       case IcsObserving:
14485         DisplayError(_("Warning: You are still observing a game"), 0);
14486         break;
14487       case IcsExamining:
14488         DisplayError(_("Warning: You are still examining a game"), 0);
14489         break;
14490       case IcsIdle:
14491         break;
14492       case EditGame:
14493       default:
14494         return;
14495     }
14496
14497     pausing = FALSE;
14498     StopClocks();
14499     first.offeredDraw = second.offeredDraw = 0;
14500
14501     if (gameMode == PlayFromGameFile) {
14502         whiteTimeRemaining = timeRemaining[0][currentMove];
14503         blackTimeRemaining = timeRemaining[1][currentMove];
14504         DisplayTitle("");
14505     }
14506
14507     if (gameMode == MachinePlaysWhite ||
14508         gameMode == MachinePlaysBlack ||
14509         gameMode == TwoMachinesPlay ||
14510         gameMode == EndOfGame) {
14511         i = forwardMostMove;
14512         while (i > currentMove) {
14513             SendToProgram("undo\n", &first);
14514             i--;
14515         }
14516         if(!adjustedClock) {
14517         whiteTimeRemaining = timeRemaining[0][currentMove];
14518         blackTimeRemaining = timeRemaining[1][currentMove];
14519         DisplayBothClocks();
14520         }
14521         if (whiteFlag || blackFlag) {
14522             whiteFlag = blackFlag = 0;
14523         }
14524         DisplayTitle("");
14525     }
14526
14527     gameMode = EditGame;
14528     ModeHighlight();
14529     SetGameInfo();
14530 }
14531
14532
14533 void
14534 EditPositionEvent ()
14535 {
14536     if (gameMode == EditPosition) {
14537         EditGameEvent();
14538         return;
14539     }
14540
14541     EditGameEvent();
14542     if (gameMode != EditGame) return;
14543
14544     gameMode = EditPosition;
14545     ModeHighlight();
14546     SetGameInfo();
14547     if (currentMove > 0)
14548       CopyBoard(boards[0], boards[currentMove]);
14549
14550     blackPlaysFirst = !WhiteOnMove(currentMove);
14551     ResetClocks();
14552     currentMove = forwardMostMove = backwardMostMove = 0;
14553     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14554     DisplayMove(-1);
14555     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
14556 }
14557
14558 void
14559 ExitAnalyzeMode ()
14560 {
14561     /* [DM] icsEngineAnalyze - possible call from other functions */
14562     if (appData.icsEngineAnalyze) {
14563         appData.icsEngineAnalyze = FALSE;
14564
14565         DisplayMessage("",_("Close ICS engine analyze..."));
14566     }
14567     if (first.analysisSupport && first.analyzing) {
14568       SendToBoth("exit\n");
14569       first.analyzing = second.analyzing = FALSE;
14570     }
14571     thinkOutput[0] = NULLCHAR;
14572 }
14573
14574 void
14575 EditPositionDone (Boolean fakeRights)
14576 {
14577     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
14578
14579     startedFromSetupPosition = TRUE;
14580     InitChessProgram(&first, FALSE);
14581     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
14582       boards[0][EP_STATUS] = EP_NONE;
14583       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
14584       if(boards[0][0][BOARD_WIDTH>>1] == king) {
14585         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
14586         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
14587       } else boards[0][CASTLING][2] = NoRights;
14588       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
14589         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
14590         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
14591       } else boards[0][CASTLING][5] = NoRights;
14592       if(gameInfo.variant == VariantSChess) {
14593         int i;
14594         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
14595           boards[0][VIRGIN][i] = 0;
14596           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
14597           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
14598         }
14599       }
14600     }
14601     SendToProgram("force\n", &first);
14602     if (blackPlaysFirst) {
14603         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
14604         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
14605         currentMove = forwardMostMove = backwardMostMove = 1;
14606         CopyBoard(boards[1], boards[0]);
14607     } else {
14608         currentMove = forwardMostMove = backwardMostMove = 0;
14609     }
14610     SendBoard(&first, forwardMostMove);
14611     if (appData.debugMode) {
14612         fprintf(debugFP, "EditPosDone\n");
14613     }
14614     DisplayTitle("");
14615     DisplayMessage("", "");
14616     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14617     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14618     gameMode = EditGame;
14619     ModeHighlight();
14620     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14621     ClearHighlights(); /* [AS] */
14622 }
14623
14624 /* Pause for `ms' milliseconds */
14625 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14626 void
14627 TimeDelay (long ms)
14628 {
14629     TimeMark m1, m2;
14630
14631     GetTimeMark(&m1);
14632     do {
14633         GetTimeMark(&m2);
14634     } while (SubtractTimeMarks(&m2, &m1) < ms);
14635 }
14636
14637 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14638 void
14639 SendMultiLineToICS (char *buf)
14640 {
14641     char temp[MSG_SIZ+1], *p;
14642     int len;
14643
14644     len = strlen(buf);
14645     if (len > MSG_SIZ)
14646       len = MSG_SIZ;
14647
14648     strncpy(temp, buf, len);
14649     temp[len] = 0;
14650
14651     p = temp;
14652     while (*p) {
14653         if (*p == '\n' || *p == '\r')
14654           *p = ' ';
14655         ++p;
14656     }
14657
14658     strcat(temp, "\n");
14659     SendToICS(temp);
14660     SendToPlayer(temp, strlen(temp));
14661 }
14662
14663 void
14664 SetWhiteToPlayEvent ()
14665 {
14666     if (gameMode == EditPosition) {
14667         blackPlaysFirst = FALSE;
14668         DisplayBothClocks();    /* works because currentMove is 0 */
14669     } else if (gameMode == IcsExamining) {
14670         SendToICS(ics_prefix);
14671         SendToICS("tomove white\n");
14672     }
14673 }
14674
14675 void
14676 SetBlackToPlayEvent ()
14677 {
14678     if (gameMode == EditPosition) {
14679         blackPlaysFirst = TRUE;
14680         currentMove = 1;        /* kludge */
14681         DisplayBothClocks();
14682         currentMove = 0;
14683     } else if (gameMode == IcsExamining) {
14684         SendToICS(ics_prefix);
14685         SendToICS("tomove black\n");
14686     }
14687 }
14688
14689 void
14690 EditPositionMenuEvent (ChessSquare selection, int x, int y)
14691 {
14692     char buf[MSG_SIZ];
14693     ChessSquare piece = boards[0][y][x];
14694     static Board erasedBoard, currentBoard, menuBoard, nullBoard;
14695     static int lastVariant;
14696
14697     if (gameMode != EditPosition && gameMode != IcsExamining) return;
14698
14699     switch (selection) {
14700       case ClearBoard:
14701         CopyBoard(currentBoard, boards[0]);
14702         CopyBoard(menuBoard, initialPosition);
14703         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
14704             SendToICS(ics_prefix);
14705             SendToICS("bsetup clear\n");
14706         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
14707             SendToICS(ics_prefix);
14708             SendToICS("clearboard\n");
14709         } else {
14710             int nonEmpty = 0;
14711             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
14712                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
14713                 for (y = 0; y < BOARD_HEIGHT; y++) {
14714                     if (gameMode == IcsExamining) {
14715                         if (boards[currentMove][y][x] != EmptySquare) {
14716                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
14717                                     AAA + x, ONE + y);
14718                             SendToICS(buf);
14719                         }
14720                     } else {
14721                         if(boards[0][y][x] != p) nonEmpty++;
14722                         boards[0][y][x] = p;
14723                     }
14724                 }
14725                 menuBoard[1][x] = menuBoard[BOARD_HEIGHT-2][x] = p;
14726             }
14727             if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
14728                 for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates 
14729                     ChessSquare p = menuBoard[0][x];
14730                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[0][y] == p) menuBoard[0][y] = EmptySquare;
14731                     p = menuBoard[BOARD_HEIGHT-1][x];
14732                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[BOARD_HEIGHT-1][y] == p) menuBoard[BOARD_HEIGHT-1][y] = EmptySquare;
14733                 }
14734                 DisplayMessage("Clicking clock again restores position", "");
14735                 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
14736                 if(!nonEmpty) { // asked to clear an empty board
14737                     CopyBoard(boards[0], menuBoard);
14738                 } else
14739                 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
14740                     CopyBoard(boards[0], initialPosition);
14741                 } else
14742                 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
14743                                                                  && !CompareBoards(nullBoard, erasedBoard)) {
14744                     CopyBoard(boards[0], erasedBoard);
14745                 } else
14746                     CopyBoard(erasedBoard, currentBoard);
14747
14748             }
14749         }
14750         if (gameMode == EditPosition) {
14751             DrawPosition(FALSE, boards[0]);
14752         }
14753         break;
14754
14755       case WhitePlay:
14756         SetWhiteToPlayEvent();
14757         break;
14758
14759       case BlackPlay:
14760         SetBlackToPlayEvent();
14761         break;
14762
14763       case EmptySquare:
14764         if (gameMode == IcsExamining) {
14765             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14766             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
14767             SendToICS(buf);
14768         } else {
14769             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14770                 if(x == BOARD_LEFT-2) {
14771                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
14772                     boards[0][y][1] = 0;
14773                 } else
14774                 if(x == BOARD_RGHT+1) {
14775                     if(y >= gameInfo.holdingsSize) break;
14776                     boards[0][y][BOARD_WIDTH-2] = 0;
14777                 } else break;
14778             }
14779             boards[0][y][x] = EmptySquare;
14780             DrawPosition(FALSE, boards[0]);
14781         }
14782         break;
14783
14784       case PromotePiece:
14785         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
14786            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
14787             selection = (ChessSquare) (PROMOTED piece);
14788         } else if(piece == EmptySquare) selection = WhiteSilver;
14789         else selection = (ChessSquare)((int)piece - 1);
14790         goto defaultlabel;
14791
14792       case DemotePiece:
14793         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
14794            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
14795             selection = (ChessSquare) (DEMOTED piece);
14796         } else if(piece == EmptySquare) selection = BlackSilver;
14797         else selection = (ChessSquare)((int)piece + 1);
14798         goto defaultlabel;
14799
14800       case WhiteQueen:
14801       case BlackQueen:
14802         if(gameInfo.variant == VariantShatranj ||
14803            gameInfo.variant == VariantXiangqi  ||
14804            gameInfo.variant == VariantCourier  ||
14805            gameInfo.variant == VariantASEAN    ||
14806            gameInfo.variant == VariantMakruk     )
14807             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
14808         goto defaultlabel;
14809
14810       case WhiteKing:
14811       case BlackKing:
14812         if(gameInfo.variant == VariantXiangqi)
14813             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
14814         if(gameInfo.variant == VariantKnightmate)
14815             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
14816       default:
14817         defaultlabel:
14818         if (gameMode == IcsExamining) {
14819             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14820             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
14821                      PieceToChar(selection), AAA + x, ONE + y);
14822             SendToICS(buf);
14823         } else {
14824             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14825                 int n;
14826                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
14827                     n = PieceToNumber(selection - BlackPawn);
14828                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
14829                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
14830                     boards[0][BOARD_HEIGHT-1-n][1]++;
14831                 } else
14832                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
14833                     n = PieceToNumber(selection);
14834                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
14835                     boards[0][n][BOARD_WIDTH-1] = selection;
14836                     boards[0][n][BOARD_WIDTH-2]++;
14837                 }
14838             } else
14839             boards[0][y][x] = selection;
14840             DrawPosition(TRUE, boards[0]);
14841             ClearHighlights();
14842             fromX = fromY = -1;
14843         }
14844         break;
14845     }
14846 }
14847
14848
14849 void
14850 DropMenuEvent (ChessSquare selection, int x, int y)
14851 {
14852     ChessMove moveType;
14853
14854     switch (gameMode) {
14855       case IcsPlayingWhite:
14856       case MachinePlaysBlack:
14857         if (!WhiteOnMove(currentMove)) {
14858             DisplayMoveError(_("It is Black's turn"));
14859             return;
14860         }
14861         moveType = WhiteDrop;
14862         break;
14863       case IcsPlayingBlack:
14864       case MachinePlaysWhite:
14865         if (WhiteOnMove(currentMove)) {
14866             DisplayMoveError(_("It is White's turn"));
14867             return;
14868         }
14869         moveType = BlackDrop;
14870         break;
14871       case EditGame:
14872         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
14873         break;
14874       default:
14875         return;
14876     }
14877
14878     if (moveType == BlackDrop && selection < BlackPawn) {
14879       selection = (ChessSquare) ((int) selection
14880                                  + (int) BlackPawn - (int) WhitePawn);
14881     }
14882     if (boards[currentMove][y][x] != EmptySquare) {
14883         DisplayMoveError(_("That square is occupied"));
14884         return;
14885     }
14886
14887     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
14888 }
14889
14890 void
14891 AcceptEvent ()
14892 {
14893     /* Accept a pending offer of any kind from opponent */
14894
14895     if (appData.icsActive) {
14896         SendToICS(ics_prefix);
14897         SendToICS("accept\n");
14898     } else if (cmailMsgLoaded) {
14899         if (currentMove == cmailOldMove &&
14900             commentList[cmailOldMove] != NULL &&
14901             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14902                    "Black offers a draw" : "White offers a draw")) {
14903             TruncateGame();
14904             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14905             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14906         } else {
14907             DisplayError(_("There is no pending offer on this move"), 0);
14908             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14909         }
14910     } else {
14911         /* Not used for offers from chess program */
14912     }
14913 }
14914
14915 void
14916 DeclineEvent ()
14917 {
14918     /* Decline a pending offer of any kind from opponent */
14919
14920     if (appData.icsActive) {
14921         SendToICS(ics_prefix);
14922         SendToICS("decline\n");
14923     } else if (cmailMsgLoaded) {
14924         if (currentMove == cmailOldMove &&
14925             commentList[cmailOldMove] != NULL &&
14926             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14927                    "Black offers a draw" : "White offers a draw")) {
14928 #ifdef NOTDEF
14929             AppendComment(cmailOldMove, "Draw declined", TRUE);
14930             DisplayComment(cmailOldMove - 1, "Draw declined");
14931 #endif /*NOTDEF*/
14932         } else {
14933             DisplayError(_("There is no pending offer on this move"), 0);
14934         }
14935     } else {
14936         /* Not used for offers from chess program */
14937     }
14938 }
14939
14940 void
14941 RematchEvent ()
14942 {
14943     /* Issue ICS rematch command */
14944     if (appData.icsActive) {
14945         SendToICS(ics_prefix);
14946         SendToICS("rematch\n");
14947     }
14948 }
14949
14950 void
14951 CallFlagEvent ()
14952 {
14953     /* Call your opponent's flag (claim a win on time) */
14954     if (appData.icsActive) {
14955         SendToICS(ics_prefix);
14956         SendToICS("flag\n");
14957     } else {
14958         switch (gameMode) {
14959           default:
14960             return;
14961           case MachinePlaysWhite:
14962             if (whiteFlag) {
14963                 if (blackFlag)
14964                   GameEnds(GameIsDrawn, "Both players ran out of time",
14965                            GE_PLAYER);
14966                 else
14967                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
14968             } else {
14969                 DisplayError(_("Your opponent is not out of time"), 0);
14970             }
14971             break;
14972           case MachinePlaysBlack:
14973             if (blackFlag) {
14974                 if (whiteFlag)
14975                   GameEnds(GameIsDrawn, "Both players ran out of time",
14976                            GE_PLAYER);
14977                 else
14978                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
14979             } else {
14980                 DisplayError(_("Your opponent is not out of time"), 0);
14981             }
14982             break;
14983         }
14984     }
14985 }
14986
14987 void
14988 ClockClick (int which)
14989 {       // [HGM] code moved to back-end from winboard.c
14990         if(which) { // black clock
14991           if (gameMode == EditPosition || gameMode == IcsExamining) {
14992             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14993             SetBlackToPlayEvent();
14994           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
14995           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
14996           } else if (shiftKey) {
14997             AdjustClock(which, -1);
14998           } else if (gameMode == IcsPlayingWhite ||
14999                      gameMode == MachinePlaysBlack) {
15000             CallFlagEvent();
15001           }
15002         } else { // white clock
15003           if (gameMode == EditPosition || gameMode == IcsExamining) {
15004             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15005             SetWhiteToPlayEvent();
15006           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
15007           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
15008           } else if (shiftKey) {
15009             AdjustClock(which, -1);
15010           } else if (gameMode == IcsPlayingBlack ||
15011                    gameMode == MachinePlaysWhite) {
15012             CallFlagEvent();
15013           }
15014         }
15015 }
15016
15017 void
15018 DrawEvent ()
15019 {
15020     /* Offer draw or accept pending draw offer from opponent */
15021
15022     if (appData.icsActive) {
15023         /* Note: tournament rules require draw offers to be
15024            made after you make your move but before you punch
15025            your clock.  Currently ICS doesn't let you do that;
15026            instead, you immediately punch your clock after making
15027            a move, but you can offer a draw at any time. */
15028
15029         SendToICS(ics_prefix);
15030         SendToICS("draw\n");
15031         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
15032     } else if (cmailMsgLoaded) {
15033         if (currentMove == cmailOldMove &&
15034             commentList[cmailOldMove] != NULL &&
15035             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15036                    "Black offers a draw" : "White offers a draw")) {
15037             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15038             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15039         } else if (currentMove == cmailOldMove + 1) {
15040             char *offer = WhiteOnMove(cmailOldMove) ?
15041               "White offers a draw" : "Black offers a draw";
15042             AppendComment(currentMove, offer, TRUE);
15043             DisplayComment(currentMove - 1, offer);
15044             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
15045         } else {
15046             DisplayError(_("You must make your move before offering a draw"), 0);
15047             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15048         }
15049     } else if (first.offeredDraw) {
15050         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
15051     } else {
15052         if (first.sendDrawOffers) {
15053             SendToProgram("draw\n", &first);
15054             userOfferedDraw = TRUE;
15055         }
15056     }
15057 }
15058
15059 void
15060 AdjournEvent ()
15061 {
15062     /* Offer Adjourn or accept pending Adjourn offer from opponent */
15063
15064     if (appData.icsActive) {
15065         SendToICS(ics_prefix);
15066         SendToICS("adjourn\n");
15067     } else {
15068         /* Currently GNU Chess doesn't offer or accept Adjourns */
15069     }
15070 }
15071
15072
15073 void
15074 AbortEvent ()
15075 {
15076     /* Offer Abort or accept pending Abort offer from opponent */
15077
15078     if (appData.icsActive) {
15079         SendToICS(ics_prefix);
15080         SendToICS("abort\n");
15081     } else {
15082         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
15083     }
15084 }
15085
15086 void
15087 ResignEvent ()
15088 {
15089     /* Resign.  You can do this even if it's not your turn. */
15090
15091     if (appData.icsActive) {
15092         SendToICS(ics_prefix);
15093         SendToICS("resign\n");
15094     } else {
15095         switch (gameMode) {
15096           case MachinePlaysWhite:
15097             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15098             break;
15099           case MachinePlaysBlack:
15100             GameEnds(BlackWins, "White resigns", GE_PLAYER);
15101             break;
15102           case EditGame:
15103             if (cmailMsgLoaded) {
15104                 TruncateGame();
15105                 if (WhiteOnMove(cmailOldMove)) {
15106                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
15107                 } else {
15108                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15109                 }
15110                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
15111             }
15112             break;
15113           default:
15114             break;
15115         }
15116     }
15117 }
15118
15119
15120 void
15121 StopObservingEvent ()
15122 {
15123     /* Stop observing current games */
15124     SendToICS(ics_prefix);
15125     SendToICS("unobserve\n");
15126 }
15127
15128 void
15129 StopExaminingEvent ()
15130 {
15131     /* Stop observing current game */
15132     SendToICS(ics_prefix);
15133     SendToICS("unexamine\n");
15134 }
15135
15136 void
15137 ForwardInner (int target)
15138 {
15139     int limit; int oldSeekGraphUp = seekGraphUp;
15140
15141     if (appData.debugMode)
15142         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
15143                 target, currentMove, forwardMostMove);
15144
15145     if (gameMode == EditPosition)
15146       return;
15147
15148     seekGraphUp = FALSE;
15149     MarkTargetSquares(1);
15150
15151     if (gameMode == PlayFromGameFile && !pausing)
15152       PauseEvent();
15153
15154     if (gameMode == IcsExamining && pausing)
15155       limit = pauseExamForwardMostMove;
15156     else
15157       limit = forwardMostMove;
15158
15159     if (target > limit) target = limit;
15160
15161     if (target > 0 && moveList[target - 1][0]) {
15162         int fromX, fromY, toX, toY;
15163         toX = moveList[target - 1][2] - AAA;
15164         toY = moveList[target - 1][3] - ONE;
15165         if (moveList[target - 1][1] == '@') {
15166             if (appData.highlightLastMove) {
15167                 SetHighlights(-1, -1, toX, toY);
15168             }
15169         } else {
15170             fromX = moveList[target - 1][0] - AAA;
15171             fromY = moveList[target - 1][1] - ONE;
15172             if (target == currentMove + 1) {
15173                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
15174             }
15175             if (appData.highlightLastMove) {
15176                 SetHighlights(fromX, fromY, toX, toY);
15177             }
15178         }
15179     }
15180     if (gameMode == EditGame || gameMode == AnalyzeMode ||
15181         gameMode == Training || gameMode == PlayFromGameFile ||
15182         gameMode == AnalyzeFile) {
15183         while (currentMove < target) {
15184             if(second.analyzing) SendMoveToProgram(currentMove, &second);
15185             SendMoveToProgram(currentMove++, &first);
15186         }
15187     } else {
15188         currentMove = target;
15189     }
15190
15191     if (gameMode == EditGame || gameMode == EndOfGame) {
15192         whiteTimeRemaining = timeRemaining[0][currentMove];
15193         blackTimeRemaining = timeRemaining[1][currentMove];
15194     }
15195     DisplayBothClocks();
15196     DisplayMove(currentMove - 1);
15197     DrawPosition(oldSeekGraphUp, boards[currentMove]);
15198     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15199     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
15200         DisplayComment(currentMove - 1, commentList[currentMove]);
15201     }
15202     ClearMap(); // [HGM] exclude: invalidate map
15203 }
15204
15205
15206 void
15207 ForwardEvent ()
15208 {
15209     if (gameMode == IcsExamining && !pausing) {
15210         SendToICS(ics_prefix);
15211         SendToICS("forward\n");
15212     } else {
15213         ForwardInner(currentMove + 1);
15214     }
15215 }
15216
15217 void
15218 ToEndEvent ()
15219 {
15220     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15221         /* to optimze, we temporarily turn off analysis mode while we feed
15222          * the remaining moves to the engine. Otherwise we get analysis output
15223          * after each move.
15224          */
15225         if (first.analysisSupport) {
15226           SendToProgram("exit\nforce\n", &first);
15227           first.analyzing = FALSE;
15228         }
15229     }
15230
15231     if (gameMode == IcsExamining && !pausing) {
15232         SendToICS(ics_prefix);
15233         SendToICS("forward 999999\n");
15234     } else {
15235         ForwardInner(forwardMostMove);
15236     }
15237
15238     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15239         /* we have fed all the moves, so reactivate analysis mode */
15240         SendToProgram("analyze\n", &first);
15241         first.analyzing = TRUE;
15242         /*first.maybeThinking = TRUE;*/
15243         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15244     }
15245 }
15246
15247 void
15248 BackwardInner (int target)
15249 {
15250     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
15251
15252     if (appData.debugMode)
15253         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
15254                 target, currentMove, forwardMostMove);
15255
15256     if (gameMode == EditPosition) return;
15257     seekGraphUp = FALSE;
15258     MarkTargetSquares(1);
15259     if (currentMove <= backwardMostMove) {
15260         ClearHighlights();
15261         DrawPosition(full_redraw, boards[currentMove]);
15262         return;
15263     }
15264     if (gameMode == PlayFromGameFile && !pausing)
15265       PauseEvent();
15266
15267     if (moveList[target][0]) {
15268         int fromX, fromY, toX, toY;
15269         toX = moveList[target][2] - AAA;
15270         toY = moveList[target][3] - ONE;
15271         if (moveList[target][1] == '@') {
15272             if (appData.highlightLastMove) {
15273                 SetHighlights(-1, -1, toX, toY);
15274             }
15275         } else {
15276             fromX = moveList[target][0] - AAA;
15277             fromY = moveList[target][1] - ONE;
15278             if (target == currentMove - 1) {
15279                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
15280             }
15281             if (appData.highlightLastMove) {
15282                 SetHighlights(fromX, fromY, toX, toY);
15283             }
15284         }
15285     }
15286     if (gameMode == EditGame || gameMode==AnalyzeMode ||
15287         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
15288         while (currentMove > target) {
15289             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
15290                 // null move cannot be undone. Reload program with move history before it.
15291                 int i;
15292                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
15293                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
15294                 }
15295                 SendBoard(&first, i);
15296               if(second.analyzing) SendBoard(&second, i);
15297                 for(currentMove=i; currentMove<target; currentMove++) {
15298                     SendMoveToProgram(currentMove, &first);
15299                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
15300                 }
15301                 break;
15302             }
15303             SendToBoth("undo\n");
15304             currentMove--;
15305         }
15306     } else {
15307         currentMove = target;
15308     }
15309
15310     if (gameMode == EditGame || gameMode == EndOfGame) {
15311         whiteTimeRemaining = timeRemaining[0][currentMove];
15312         blackTimeRemaining = timeRemaining[1][currentMove];
15313     }
15314     DisplayBothClocks();
15315     DisplayMove(currentMove - 1);
15316     DrawPosition(full_redraw, boards[currentMove]);
15317     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15318     // [HGM] PV info: routine tests if comment empty
15319     DisplayComment(currentMove - 1, commentList[currentMove]);
15320     ClearMap(); // [HGM] exclude: invalidate map
15321 }
15322
15323 void
15324 BackwardEvent ()
15325 {
15326     if (gameMode == IcsExamining && !pausing) {
15327         SendToICS(ics_prefix);
15328         SendToICS("backward\n");
15329     } else {
15330         BackwardInner(currentMove - 1);
15331     }
15332 }
15333
15334 void
15335 ToStartEvent ()
15336 {
15337     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15338         /* to optimize, we temporarily turn off analysis mode while we undo
15339          * all the moves. Otherwise we get analysis output after each undo.
15340          */
15341         if (first.analysisSupport) {
15342           SendToProgram("exit\nforce\n", &first);
15343           first.analyzing = FALSE;
15344         }
15345     }
15346
15347     if (gameMode == IcsExamining && !pausing) {
15348         SendToICS(ics_prefix);
15349         SendToICS("backward 999999\n");
15350     } else {
15351         BackwardInner(backwardMostMove);
15352     }
15353
15354     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15355         /* we have fed all the moves, so reactivate analysis mode */
15356         SendToProgram("analyze\n", &first);
15357         first.analyzing = TRUE;
15358         /*first.maybeThinking = TRUE;*/
15359         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15360     }
15361 }
15362
15363 void
15364 ToNrEvent (int to)
15365 {
15366   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
15367   if (to >= forwardMostMove) to = forwardMostMove;
15368   if (to <= backwardMostMove) to = backwardMostMove;
15369   if (to < currentMove) {
15370     BackwardInner(to);
15371   } else {
15372     ForwardInner(to);
15373   }
15374 }
15375
15376 void
15377 RevertEvent (Boolean annotate)
15378 {
15379     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
15380         return;
15381     }
15382     if (gameMode != IcsExamining) {
15383         DisplayError(_("You are not examining a game"), 0);
15384         return;
15385     }
15386     if (pausing) {
15387         DisplayError(_("You can't revert while pausing"), 0);
15388         return;
15389     }
15390     SendToICS(ics_prefix);
15391     SendToICS("revert\n");
15392 }
15393
15394 void
15395 RetractMoveEvent ()
15396 {
15397     switch (gameMode) {
15398       case MachinePlaysWhite:
15399       case MachinePlaysBlack:
15400         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15401             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
15402             return;
15403         }
15404         if (forwardMostMove < 2) return;
15405         currentMove = forwardMostMove = forwardMostMove - 2;
15406         whiteTimeRemaining = timeRemaining[0][currentMove];
15407         blackTimeRemaining = timeRemaining[1][currentMove];
15408         DisplayBothClocks();
15409         DisplayMove(currentMove - 1);
15410         ClearHighlights();/*!! could figure this out*/
15411         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
15412         SendToProgram("remove\n", &first);
15413         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
15414         break;
15415
15416       case BeginningOfGame:
15417       default:
15418         break;
15419
15420       case IcsPlayingWhite:
15421       case IcsPlayingBlack:
15422         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
15423             SendToICS(ics_prefix);
15424             SendToICS("takeback 2\n");
15425         } else {
15426             SendToICS(ics_prefix);
15427             SendToICS("takeback 1\n");
15428         }
15429         break;
15430     }
15431 }
15432
15433 void
15434 MoveNowEvent ()
15435 {
15436     ChessProgramState *cps;
15437
15438     switch (gameMode) {
15439       case MachinePlaysWhite:
15440         if (!WhiteOnMove(forwardMostMove)) {
15441             DisplayError(_("It is your turn"), 0);
15442             return;
15443         }
15444         cps = &first;
15445         break;
15446       case MachinePlaysBlack:
15447         if (WhiteOnMove(forwardMostMove)) {
15448             DisplayError(_("It is your turn"), 0);
15449             return;
15450         }
15451         cps = &first;
15452         break;
15453       case TwoMachinesPlay:
15454         if (WhiteOnMove(forwardMostMove) ==
15455             (first.twoMachinesColor[0] == 'w')) {
15456             cps = &first;
15457         } else {
15458             cps = &second;
15459         }
15460         break;
15461       case BeginningOfGame:
15462       default:
15463         return;
15464     }
15465     SendToProgram("?\n", cps);
15466 }
15467
15468 void
15469 TruncateGameEvent ()
15470 {
15471     EditGameEvent();
15472     if (gameMode != EditGame) return;
15473     TruncateGame();
15474 }
15475
15476 void
15477 TruncateGame ()
15478 {
15479     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
15480     if (forwardMostMove > currentMove) {
15481         if (gameInfo.resultDetails != NULL) {
15482             free(gameInfo.resultDetails);
15483             gameInfo.resultDetails = NULL;
15484             gameInfo.result = GameUnfinished;
15485         }
15486         forwardMostMove = currentMove;
15487         HistorySet(parseList, backwardMostMove, forwardMostMove,
15488                    currentMove-1);
15489     }
15490 }
15491
15492 void
15493 HintEvent ()
15494 {
15495     if (appData.noChessProgram) return;
15496     switch (gameMode) {
15497       case MachinePlaysWhite:
15498         if (WhiteOnMove(forwardMostMove)) {
15499             DisplayError(_("Wait until your turn."), 0);
15500             return;
15501         }
15502         break;
15503       case BeginningOfGame:
15504       case MachinePlaysBlack:
15505         if (!WhiteOnMove(forwardMostMove)) {
15506             DisplayError(_("Wait until your turn."), 0);
15507             return;
15508         }
15509         break;
15510       default:
15511         DisplayError(_("No hint available"), 0);
15512         return;
15513     }
15514     SendToProgram("hint\n", &first);
15515     hintRequested = TRUE;
15516 }
15517
15518 void
15519 CreateBookEvent ()
15520 {
15521     ListGame * lg = (ListGame *) gameList.head;
15522     FILE *f, *g;
15523     int nItem;
15524     static int secondTime = FALSE;
15525
15526     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
15527         DisplayError(_("Game list not loaded or empty"), 0);
15528         return;
15529     }
15530
15531     if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
15532         fclose(g);
15533         secondTime++;
15534         DisplayNote(_("Book file exists! Try again for overwrite."));
15535         return;
15536     }
15537
15538     creatingBook = TRUE;
15539     secondTime = FALSE;
15540
15541     /* Get list size */
15542     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
15543         LoadGame(f, nItem, "", TRUE);
15544         AddGameToBook(TRUE);
15545         lg = (ListGame *) lg->node.succ;
15546     }
15547
15548     creatingBook = FALSE;
15549     FlushBook();
15550 }
15551
15552 void
15553 BookEvent ()
15554 {
15555     if (appData.noChessProgram) return;
15556     switch (gameMode) {
15557       case MachinePlaysWhite:
15558         if (WhiteOnMove(forwardMostMove)) {
15559             DisplayError(_("Wait until your turn."), 0);
15560             return;
15561         }
15562         break;
15563       case BeginningOfGame:
15564       case MachinePlaysBlack:
15565         if (!WhiteOnMove(forwardMostMove)) {
15566             DisplayError(_("Wait until your turn."), 0);
15567             return;
15568         }
15569         break;
15570       case EditPosition:
15571         EditPositionDone(TRUE);
15572         break;
15573       case TwoMachinesPlay:
15574         return;
15575       default:
15576         break;
15577     }
15578     SendToProgram("bk\n", &first);
15579     bookOutput[0] = NULLCHAR;
15580     bookRequested = TRUE;
15581 }
15582
15583 void
15584 AboutGameEvent ()
15585 {
15586     char *tags = PGNTags(&gameInfo);
15587     TagsPopUp(tags, CmailMsg());
15588     free(tags);
15589 }
15590
15591 /* end button procedures */
15592
15593 void
15594 PrintPosition (FILE *fp, int move)
15595 {
15596     int i, j;
15597
15598     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15599         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15600             char c = PieceToChar(boards[move][i][j]);
15601             fputc(c == 'x' ? '.' : c, fp);
15602             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
15603         }
15604     }
15605     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
15606       fprintf(fp, "white to play\n");
15607     else
15608       fprintf(fp, "black to play\n");
15609 }
15610
15611 void
15612 PrintOpponents (FILE *fp)
15613 {
15614     if (gameInfo.white != NULL) {
15615         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
15616     } else {
15617         fprintf(fp, "\n");
15618     }
15619 }
15620
15621 /* Find last component of program's own name, using some heuristics */
15622 void
15623 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
15624 {
15625     char *p, *q, c;
15626     int local = (strcmp(host, "localhost") == 0);
15627     while (!local && (p = strchr(prog, ';')) != NULL) {
15628         p++;
15629         while (*p == ' ') p++;
15630         prog = p;
15631     }
15632     if (*prog == '"' || *prog == '\'') {
15633         q = strchr(prog + 1, *prog);
15634     } else {
15635         q = strchr(prog, ' ');
15636     }
15637     if (q == NULL) q = prog + strlen(prog);
15638     p = q;
15639     while (p >= prog && *p != '/' && *p != '\\') p--;
15640     p++;
15641     if(p == prog && *p == '"') p++;
15642     c = *q; *q = 0;
15643     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
15644     memcpy(buf, p, q - p);
15645     buf[q - p] = NULLCHAR;
15646     if (!local) {
15647         strcat(buf, "@");
15648         strcat(buf, host);
15649     }
15650 }
15651
15652 char *
15653 TimeControlTagValue ()
15654 {
15655     char buf[MSG_SIZ];
15656     if (!appData.clockMode) {
15657       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
15658     } else if (movesPerSession > 0) {
15659       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
15660     } else if (timeIncrement == 0) {
15661       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
15662     } else {
15663       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
15664     }
15665     return StrSave(buf);
15666 }
15667
15668 void
15669 SetGameInfo ()
15670 {
15671     /* This routine is used only for certain modes */
15672     VariantClass v = gameInfo.variant;
15673     ChessMove r = GameUnfinished;
15674     char *p = NULL;
15675
15676     if(keepInfo) return;
15677
15678     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
15679         r = gameInfo.result;
15680         p = gameInfo.resultDetails;
15681         gameInfo.resultDetails = NULL;
15682     }
15683     ClearGameInfo(&gameInfo);
15684     gameInfo.variant = v;
15685
15686     switch (gameMode) {
15687       case MachinePlaysWhite:
15688         gameInfo.event = StrSave( appData.pgnEventHeader );
15689         gameInfo.site = StrSave(HostName());
15690         gameInfo.date = PGNDate();
15691         gameInfo.round = StrSave("-");
15692         gameInfo.white = StrSave(first.tidy);
15693         gameInfo.black = StrSave(UserName());
15694         gameInfo.timeControl = TimeControlTagValue();
15695         break;
15696
15697       case MachinePlaysBlack:
15698         gameInfo.event = StrSave( appData.pgnEventHeader );
15699         gameInfo.site = StrSave(HostName());
15700         gameInfo.date = PGNDate();
15701         gameInfo.round = StrSave("-");
15702         gameInfo.white = StrSave(UserName());
15703         gameInfo.black = StrSave(first.tidy);
15704         gameInfo.timeControl = TimeControlTagValue();
15705         break;
15706
15707       case TwoMachinesPlay:
15708         gameInfo.event = StrSave( appData.pgnEventHeader );
15709         gameInfo.site = StrSave(HostName());
15710         gameInfo.date = PGNDate();
15711         if (roundNr > 0) {
15712             char buf[MSG_SIZ];
15713             snprintf(buf, MSG_SIZ, "%d", roundNr);
15714             gameInfo.round = StrSave(buf);
15715         } else {
15716             gameInfo.round = StrSave("-");
15717         }
15718         if (first.twoMachinesColor[0] == 'w') {
15719             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15720             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15721         } else {
15722             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15723             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15724         }
15725         gameInfo.timeControl = TimeControlTagValue();
15726         break;
15727
15728       case EditGame:
15729         gameInfo.event = StrSave("Edited game");
15730         gameInfo.site = StrSave(HostName());
15731         gameInfo.date = PGNDate();
15732         gameInfo.round = StrSave("-");
15733         gameInfo.white = StrSave("-");
15734         gameInfo.black = StrSave("-");
15735         gameInfo.result = r;
15736         gameInfo.resultDetails = p;
15737         break;
15738
15739       case EditPosition:
15740         gameInfo.event = StrSave("Edited position");
15741         gameInfo.site = StrSave(HostName());
15742         gameInfo.date = PGNDate();
15743         gameInfo.round = StrSave("-");
15744         gameInfo.white = StrSave("-");
15745         gameInfo.black = StrSave("-");
15746         break;
15747
15748       case IcsPlayingWhite:
15749       case IcsPlayingBlack:
15750       case IcsObserving:
15751       case IcsExamining:
15752         break;
15753
15754       case PlayFromGameFile:
15755         gameInfo.event = StrSave("Game from non-PGN file");
15756         gameInfo.site = StrSave(HostName());
15757         gameInfo.date = PGNDate();
15758         gameInfo.round = StrSave("-");
15759         gameInfo.white = StrSave("?");
15760         gameInfo.black = StrSave("?");
15761         break;
15762
15763       default:
15764         break;
15765     }
15766 }
15767
15768 void
15769 ReplaceComment (int index, char *text)
15770 {
15771     int len;
15772     char *p;
15773     float score;
15774
15775     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
15776        pvInfoList[index-1].depth == len &&
15777        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
15778        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
15779     while (*text == '\n') text++;
15780     len = strlen(text);
15781     while (len > 0 && text[len - 1] == '\n') len--;
15782
15783     if (commentList[index] != NULL)
15784       free(commentList[index]);
15785
15786     if (len == 0) {
15787         commentList[index] = NULL;
15788         return;
15789     }
15790   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
15791       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
15792       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
15793     commentList[index] = (char *) malloc(len + 2);
15794     strncpy(commentList[index], text, len);
15795     commentList[index][len] = '\n';
15796     commentList[index][len + 1] = NULLCHAR;
15797   } else {
15798     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
15799     char *p;
15800     commentList[index] = (char *) malloc(len + 7);
15801     safeStrCpy(commentList[index], "{\n", 3);
15802     safeStrCpy(commentList[index]+2, text, len+1);
15803     commentList[index][len+2] = NULLCHAR;
15804     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
15805     strcat(commentList[index], "\n}\n");
15806   }
15807 }
15808
15809 void
15810 CrushCRs (char *text)
15811 {
15812   char *p = text;
15813   char *q = text;
15814   char ch;
15815
15816   do {
15817     ch = *p++;
15818     if (ch == '\r') continue;
15819     *q++ = ch;
15820   } while (ch != '\0');
15821 }
15822
15823 void
15824 AppendComment (int index, char *text, Boolean addBraces)
15825 /* addBraces  tells if we should add {} */
15826 {
15827     int oldlen, len;
15828     char *old;
15829
15830 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
15831     if(addBraces == 3) addBraces = 0; else // force appending literally
15832     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
15833
15834     CrushCRs(text);
15835     while (*text == '\n') text++;
15836     len = strlen(text);
15837     while (len > 0 && text[len - 1] == '\n') len--;
15838     text[len] = NULLCHAR;
15839
15840     if (len == 0) return;
15841
15842     if (commentList[index] != NULL) {
15843       Boolean addClosingBrace = addBraces;
15844         old = commentList[index];
15845         oldlen = strlen(old);
15846         while(commentList[index][oldlen-1] ==  '\n')
15847           commentList[index][--oldlen] = NULLCHAR;
15848         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
15849         safeStrCpy(commentList[index], old, oldlen + len + 6);
15850         free(old);
15851         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
15852         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
15853           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
15854           while (*text == '\n') { text++; len--; }
15855           commentList[index][--oldlen] = NULLCHAR;
15856       }
15857         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
15858         else          strcat(commentList[index], "\n");
15859         strcat(commentList[index], text);
15860         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
15861         else          strcat(commentList[index], "\n");
15862     } else {
15863         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
15864         if(addBraces)
15865           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
15866         else commentList[index][0] = NULLCHAR;
15867         strcat(commentList[index], text);
15868         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
15869         if(addBraces == TRUE) strcat(commentList[index], "}\n");
15870     }
15871 }
15872
15873 static char *
15874 FindStr (char * text, char * sub_text)
15875 {
15876     char * result = strstr( text, sub_text );
15877
15878     if( result != NULL ) {
15879         result += strlen( sub_text );
15880     }
15881
15882     return result;
15883 }
15884
15885 /* [AS] Try to extract PV info from PGN comment */
15886 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
15887 char *
15888 GetInfoFromComment (int index, char * text)
15889 {
15890     char * sep = text, *p;
15891
15892     if( text != NULL && index > 0 ) {
15893         int score = 0;
15894         int depth = 0;
15895         int time = -1, sec = 0, deci;
15896         char * s_eval = FindStr( text, "[%eval " );
15897         char * s_emt = FindStr( text, "[%emt " );
15898 #if 0
15899         if( s_eval != NULL || s_emt != NULL ) {
15900 #else
15901         if(0) { // [HGM] this code is not finished, and could actually be detrimental
15902 #endif
15903             /* New style */
15904             char delim;
15905
15906             if( s_eval != NULL ) {
15907                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
15908                     return text;
15909                 }
15910
15911                 if( delim != ']' ) {
15912                     return text;
15913                 }
15914             }
15915
15916             if( s_emt != NULL ) {
15917             }
15918                 return text;
15919         }
15920         else {
15921             /* We expect something like: [+|-]nnn.nn/dd */
15922             int score_lo = 0;
15923
15924             if(*text != '{') return text; // [HGM] braces: must be normal comment
15925
15926             sep = strchr( text, '/' );
15927             if( sep == NULL || sep < (text+4) ) {
15928                 return text;
15929             }
15930
15931             p = text;
15932             if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
15933             if(p[1] == '(') { // comment starts with PV
15934                p = strchr(p, ')'); // locate end of PV
15935                if(p == NULL || sep < p+5) return text;
15936                // at this point we have something like "{(.*) +0.23/6 ..."
15937                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
15938                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
15939                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
15940             }
15941             time = -1; sec = -1; deci = -1;
15942             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
15943                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
15944                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
15945                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
15946                 return text;
15947             }
15948
15949             if( score_lo < 0 || score_lo >= 100 ) {
15950                 return text;
15951             }
15952
15953             if(sec >= 0) time = 600*time + 10*sec; else
15954             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
15955
15956             score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
15957
15958             /* [HGM] PV time: now locate end of PV info */
15959             while( *++sep >= '0' && *sep <= '9'); // strip depth
15960             if(time >= 0)
15961             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
15962             if(sec >= 0)
15963             while( *++sep >= '0' && *sep <= '9'); // strip seconds
15964             if(deci >= 0)
15965             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
15966             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
15967         }
15968
15969         if( depth <= 0 ) {
15970             return text;
15971         }
15972
15973         if( time < 0 ) {
15974             time = -1;
15975         }
15976
15977         pvInfoList[index-1].depth = depth;
15978         pvInfoList[index-1].score = score;
15979         pvInfoList[index-1].time  = 10*time; // centi-sec
15980         if(*sep == '}') *sep = 0; else *--sep = '{';
15981         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
15982     }
15983     return sep;
15984 }
15985
15986 void
15987 SendToProgram (char *message, ChessProgramState *cps)
15988 {
15989     int count, outCount, error;
15990     char buf[MSG_SIZ];
15991
15992     if (cps->pr == NoProc) return;
15993     Attention(cps);
15994
15995     if (appData.debugMode) {
15996         TimeMark now;
15997         GetTimeMark(&now);
15998         fprintf(debugFP, "%ld >%-6s: %s",
15999                 SubtractTimeMarks(&now, &programStartTime),
16000                 cps->which, message);
16001         if(serverFP)
16002             fprintf(serverFP, "%ld >%-6s: %s",
16003                 SubtractTimeMarks(&now, &programStartTime),
16004                 cps->which, message), fflush(serverFP);
16005     }
16006
16007     count = strlen(message);
16008     outCount = OutputToProcess(cps->pr, message, count, &error);
16009     if (outCount < count && !exiting
16010                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
16011       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
16012       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
16013         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16014             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16015                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16016                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16017                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16018             } else {
16019                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16020                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16021                 gameInfo.result = res;
16022             }
16023             gameInfo.resultDetails = StrSave(buf);
16024         }
16025         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16026         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16027     }
16028 }
16029
16030 void
16031 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
16032 {
16033     char *end_str;
16034     char buf[MSG_SIZ];
16035     ChessProgramState *cps = (ChessProgramState *)closure;
16036
16037     if (isr != cps->isr) return; /* Killed intentionally */
16038     if (count <= 0) {
16039         if (count == 0) {
16040             RemoveInputSource(cps->isr);
16041             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
16042                     _(cps->which), cps->program);
16043             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
16044             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16045                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16046                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16047                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16048                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16049                 } else {
16050                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16051                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16052                     gameInfo.result = res;
16053                 }
16054                 gameInfo.resultDetails = StrSave(buf);
16055             }
16056             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16057             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
16058         } else {
16059             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
16060                     _(cps->which), cps->program);
16061             RemoveInputSource(cps->isr);
16062
16063             /* [AS] Program is misbehaving badly... kill it */
16064             if( count == -2 ) {
16065                 DestroyChildProcess( cps->pr, 9 );
16066                 cps->pr = NoProc;
16067             }
16068
16069             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16070         }
16071         return;
16072     }
16073
16074     if ((end_str = strchr(message, '\r')) != NULL)
16075       *end_str = NULLCHAR;
16076     if ((end_str = strchr(message, '\n')) != NULL)
16077       *end_str = NULLCHAR;
16078
16079     if (appData.debugMode) {
16080         TimeMark now; int print = 1;
16081         char *quote = ""; char c; int i;
16082
16083         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
16084                 char start = message[0];
16085                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
16086                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
16087                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
16088                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
16089                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
16090                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
16091                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
16092                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
16093                    sscanf(message, "hint: %c", &c)!=1 &&
16094                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
16095                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
16096                     print = (appData.engineComments >= 2);
16097                 }
16098                 message[0] = start; // restore original message
16099         }
16100         if(print) {
16101                 GetTimeMark(&now);
16102                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
16103                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16104                         quote,
16105                         message);
16106                 if(serverFP)
16107                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
16108                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16109                         quote,
16110                         message), fflush(serverFP);
16111         }
16112     }
16113
16114     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
16115     if (appData.icsEngineAnalyze) {
16116         if (strstr(message, "whisper") != NULL ||
16117              strstr(message, "kibitz") != NULL ||
16118             strstr(message, "tellics") != NULL) return;
16119     }
16120
16121     HandleMachineMove(message, cps);
16122 }
16123
16124
16125 void
16126 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
16127 {
16128     char buf[MSG_SIZ];
16129     int seconds;
16130
16131     if( timeControl_2 > 0 ) {
16132         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
16133             tc = timeControl_2;
16134         }
16135     }
16136     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
16137     inc /= cps->timeOdds;
16138     st  /= cps->timeOdds;
16139
16140     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
16141
16142     if (st > 0) {
16143       /* Set exact time per move, normally using st command */
16144       if (cps->stKludge) {
16145         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
16146         seconds = st % 60;
16147         if (seconds == 0) {
16148           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
16149         } else {
16150           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
16151         }
16152       } else {
16153         snprintf(buf, MSG_SIZ, "st %d\n", st);
16154       }
16155     } else {
16156       /* Set conventional or incremental time control, using level command */
16157       if (seconds == 0) {
16158         /* Note old gnuchess bug -- minutes:seconds used to not work.
16159            Fixed in later versions, but still avoid :seconds
16160            when seconds is 0. */
16161         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
16162       } else {
16163         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
16164                  seconds, inc/1000.);
16165       }
16166     }
16167     SendToProgram(buf, cps);
16168
16169     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
16170     /* Orthogonally, limit search to given depth */
16171     if (sd > 0) {
16172       if (cps->sdKludge) {
16173         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
16174       } else {
16175         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
16176       }
16177       SendToProgram(buf, cps);
16178     }
16179
16180     if(cps->nps >= 0) { /* [HGM] nps */
16181         if(cps->supportsNPS == FALSE)
16182           cps->nps = -1; // don't use if engine explicitly says not supported!
16183         else {
16184           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
16185           SendToProgram(buf, cps);
16186         }
16187     }
16188 }
16189
16190 ChessProgramState *
16191 WhitePlayer ()
16192 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
16193 {
16194     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
16195        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
16196         return &second;
16197     return &first;
16198 }
16199
16200 void
16201 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
16202 {
16203     char message[MSG_SIZ];
16204     long time, otime;
16205
16206     /* Note: this routine must be called when the clocks are stopped
16207        or when they have *just* been set or switched; otherwise
16208        it will be off by the time since the current tick started.
16209     */
16210     if (machineWhite) {
16211         time = whiteTimeRemaining / 10;
16212         otime = blackTimeRemaining / 10;
16213     } else {
16214         time = blackTimeRemaining / 10;
16215         otime = whiteTimeRemaining / 10;
16216     }
16217     /* [HGM] translate opponent's time by time-odds factor */
16218     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
16219
16220     if (time <= 0) time = 1;
16221     if (otime <= 0) otime = 1;
16222
16223     snprintf(message, MSG_SIZ, "time %ld\n", time);
16224     SendToProgram(message, cps);
16225
16226     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
16227     SendToProgram(message, cps);
16228 }
16229
16230 char *
16231 EngineDefinedVariant (ChessProgramState *cps, int n)
16232 {   // return name of n-th unknown variant that engine supports
16233     static char buf[MSG_SIZ];
16234     char *p, *s = cps->variants;
16235     if(!s) return NULL;
16236     do { // parse string from variants feature
16237       VariantClass v;
16238         p = strchr(s, ',');
16239         if(p) *p = NULLCHAR;
16240       v = StringToVariant(s);
16241       if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
16242         if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
16243             if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
16244         }
16245         if(p) *p++ = ',';
16246         if(n < 0) return buf;
16247     } while(s = p);
16248     return NULL;
16249 }
16250
16251 int
16252 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16253 {
16254   char buf[MSG_SIZ];
16255   int len = strlen(name);
16256   int val;
16257
16258   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16259     (*p) += len + 1;
16260     sscanf(*p, "%d", &val);
16261     *loc = (val != 0);
16262     while (**p && **p != ' ')
16263       (*p)++;
16264     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16265     SendToProgram(buf, cps);
16266     return TRUE;
16267   }
16268   return FALSE;
16269 }
16270
16271 int
16272 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16273 {
16274   char buf[MSG_SIZ];
16275   int len = strlen(name);
16276   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16277     (*p) += len + 1;
16278     sscanf(*p, "%d", loc);
16279     while (**p && **p != ' ') (*p)++;
16280     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16281     SendToProgram(buf, cps);
16282     return TRUE;
16283   }
16284   return FALSE;
16285 }
16286
16287 int
16288 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
16289 {
16290   char buf[MSG_SIZ];
16291   int len = strlen(name);
16292   if (strncmp((*p), name, len) == 0
16293       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
16294     (*p) += len + 2;
16295     ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
16296     sscanf(*p, "%[^\"]", *loc);
16297     while (**p && **p != '\"') (*p)++;
16298     if (**p == '\"') (*p)++;
16299     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16300     SendToProgram(buf, cps);
16301     return TRUE;
16302   }
16303   return FALSE;
16304 }
16305
16306 int
16307 ParseOption (Option *opt, ChessProgramState *cps)
16308 // [HGM] options: process the string that defines an engine option, and determine
16309 // name, type, default value, and allowed value range
16310 {
16311         char *p, *q, buf[MSG_SIZ];
16312         int n, min = (-1)<<31, max = 1<<31, def;
16313
16314         if(p = strstr(opt->name, " -spin ")) {
16315             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16316             if(max < min) max = min; // enforce consistency
16317             if(def < min) def = min;
16318             if(def > max) def = max;
16319             opt->value = def;
16320             opt->min = min;
16321             opt->max = max;
16322             opt->type = Spin;
16323         } else if((p = strstr(opt->name, " -slider "))) {
16324             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
16325             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16326             if(max < min) max = min; // enforce consistency
16327             if(def < min) def = min;
16328             if(def > max) def = max;
16329             opt->value = def;
16330             opt->min = min;
16331             opt->max = max;
16332             opt->type = Spin; // Slider;
16333         } else if((p = strstr(opt->name, " -string "))) {
16334             opt->textValue = p+9;
16335             opt->type = TextBox;
16336         } else if((p = strstr(opt->name, " -file "))) {
16337             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16338             opt->textValue = p+7;
16339             opt->type = FileName; // FileName;
16340         } else if((p = strstr(opt->name, " -path "))) {
16341             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16342             opt->textValue = p+7;
16343             opt->type = PathName; // PathName;
16344         } else if(p = strstr(opt->name, " -check ")) {
16345             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
16346             opt->value = (def != 0);
16347             opt->type = CheckBox;
16348         } else if(p = strstr(opt->name, " -combo ")) {
16349             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
16350             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
16351             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
16352             opt->value = n = 0;
16353             while(q = StrStr(q, " /// ")) {
16354                 n++; *q = 0;    // count choices, and null-terminate each of them
16355                 q += 5;
16356                 if(*q == '*') { // remember default, which is marked with * prefix
16357                     q++;
16358                     opt->value = n;
16359                 }
16360                 cps->comboList[cps->comboCnt++] = q;
16361             }
16362             cps->comboList[cps->comboCnt++] = NULL;
16363             opt->max = n + 1;
16364             opt->type = ComboBox;
16365         } else if(p = strstr(opt->name, " -button")) {
16366             opt->type = Button;
16367         } else if(p = strstr(opt->name, " -save")) {
16368             opt->type = SaveButton;
16369         } else return FALSE;
16370         *p = 0; // terminate option name
16371         // now look if the command-line options define a setting for this engine option.
16372         if(cps->optionSettings && cps->optionSettings[0])
16373             p = strstr(cps->optionSettings, opt->name); else p = NULL;
16374         if(p && (p == cps->optionSettings || p[-1] == ',')) {
16375           snprintf(buf, MSG_SIZ, "option %s", p);
16376                 if(p = strstr(buf, ",")) *p = 0;
16377                 if(q = strchr(buf, '=')) switch(opt->type) {
16378                     case ComboBox:
16379                         for(n=0; n<opt->max; n++)
16380                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
16381                         break;
16382                     case TextBox:
16383                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
16384                         break;
16385                     case Spin:
16386                     case CheckBox:
16387                         opt->value = atoi(q+1);
16388                     default:
16389                         break;
16390                 }
16391                 strcat(buf, "\n");
16392                 SendToProgram(buf, cps);
16393         }
16394         return TRUE;
16395 }
16396
16397 void
16398 FeatureDone (ChessProgramState *cps, int val)
16399 {
16400   DelayedEventCallback cb = GetDelayedEvent();
16401   if ((cb == InitBackEnd3 && cps == &first) ||
16402       (cb == SettingsMenuIfReady && cps == &second) ||
16403       (cb == LoadEngine) ||
16404       (cb == TwoMachinesEventIfReady)) {
16405     CancelDelayedEvent();
16406     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
16407   }
16408   cps->initDone = val;
16409   if(val) cps->reload = FALSE;
16410 }
16411
16412 /* Parse feature command from engine */
16413 void
16414 ParseFeatures (char *args, ChessProgramState *cps)
16415 {
16416   char *p = args;
16417   char *q = NULL;
16418   int val;
16419   char buf[MSG_SIZ];
16420
16421   for (;;) {
16422     while (*p == ' ') p++;
16423     if (*p == NULLCHAR) return;
16424
16425     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
16426     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
16427     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
16428     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
16429     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
16430     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
16431     if (BoolFeature(&p, "reuse", &val, cps)) {
16432       /* Engine can disable reuse, but can't enable it if user said no */
16433       if (!val) cps->reuse = FALSE;
16434       continue;
16435     }
16436     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
16437     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
16438       if (gameMode == TwoMachinesPlay) {
16439         DisplayTwoMachinesTitle();
16440       } else {
16441         DisplayTitle("");
16442       }
16443       continue;
16444     }
16445     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
16446     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
16447     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
16448     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
16449     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
16450     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
16451     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
16452     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
16453     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
16454     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
16455     if (IntFeature(&p, "done", &val, cps)) {
16456       FeatureDone(cps, val);
16457       continue;
16458     }
16459     /* Added by Tord: */
16460     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
16461     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
16462     /* End of additions by Tord */
16463
16464     /* [HGM] added features: */
16465     if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
16466     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
16467     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
16468     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
16469     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
16470     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
16471     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
16472     if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
16473         if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
16474         FREE(cps->option[cps->nrOptions].name);
16475         cps->option[cps->nrOptions].name = q; q = NULL;
16476         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
16477           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
16478             SendToProgram(buf, cps);
16479             continue;
16480         }
16481         if(cps->nrOptions >= MAX_OPTIONS) {
16482             cps->nrOptions--;
16483             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
16484             DisplayError(buf, 0);
16485         }
16486         continue;
16487     }
16488     /* End of additions by HGM */
16489
16490     /* unknown feature: complain and skip */
16491     q = p;
16492     while (*q && *q != '=') q++;
16493     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
16494     SendToProgram(buf, cps);
16495     p = q;
16496     if (*p == '=') {
16497       p++;
16498       if (*p == '\"') {
16499         p++;
16500         while (*p && *p != '\"') p++;
16501         if (*p == '\"') p++;
16502       } else {
16503         while (*p && *p != ' ') p++;
16504       }
16505     }
16506   }
16507
16508 }
16509
16510 void
16511 PeriodicUpdatesEvent (int newState)
16512 {
16513     if (newState == appData.periodicUpdates)
16514       return;
16515
16516     appData.periodicUpdates=newState;
16517
16518     /* Display type changes, so update it now */
16519 //    DisplayAnalysis();
16520
16521     /* Get the ball rolling again... */
16522     if (newState) {
16523         AnalysisPeriodicEvent(1);
16524         StartAnalysisClock();
16525     }
16526 }
16527
16528 void
16529 PonderNextMoveEvent (int newState)
16530 {
16531     if (newState == appData.ponderNextMove) return;
16532     if (gameMode == EditPosition) EditPositionDone(TRUE);
16533     if (newState) {
16534         SendToProgram("hard\n", &first);
16535         if (gameMode == TwoMachinesPlay) {
16536             SendToProgram("hard\n", &second);
16537         }
16538     } else {
16539         SendToProgram("easy\n", &first);
16540         thinkOutput[0] = NULLCHAR;
16541         if (gameMode == TwoMachinesPlay) {
16542             SendToProgram("easy\n", &second);
16543         }
16544     }
16545     appData.ponderNextMove = newState;
16546 }
16547
16548 void
16549 NewSettingEvent (int option, int *feature, char *command, int value)
16550 {
16551     char buf[MSG_SIZ];
16552
16553     if (gameMode == EditPosition) EditPositionDone(TRUE);
16554     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
16555     if(feature == NULL || *feature) SendToProgram(buf, &first);
16556     if (gameMode == TwoMachinesPlay) {
16557         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
16558     }
16559 }
16560
16561 void
16562 ShowThinkingEvent ()
16563 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
16564 {
16565     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
16566     int newState = appData.showThinking
16567         // [HGM] thinking: other features now need thinking output as well
16568         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
16569
16570     if (oldState == newState) return;
16571     oldState = newState;
16572     if (gameMode == EditPosition) EditPositionDone(TRUE);
16573     if (oldState) {
16574         SendToProgram("post\n", &first);
16575         if (gameMode == TwoMachinesPlay) {
16576             SendToProgram("post\n", &second);
16577         }
16578     } else {
16579         SendToProgram("nopost\n", &first);
16580         thinkOutput[0] = NULLCHAR;
16581         if (gameMode == TwoMachinesPlay) {
16582             SendToProgram("nopost\n", &second);
16583         }
16584     }
16585 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
16586 }
16587
16588 void
16589 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
16590 {
16591   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
16592   if (pr == NoProc) return;
16593   AskQuestion(title, question, replyPrefix, pr);
16594 }
16595
16596 void
16597 TypeInEvent (char firstChar)
16598 {
16599     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
16600         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
16601         gameMode == AnalyzeMode || gameMode == EditGame ||
16602         gameMode == EditPosition || gameMode == IcsExamining ||
16603         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
16604         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
16605                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
16606                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
16607         gameMode == Training) PopUpMoveDialog(firstChar);
16608 }
16609
16610 void
16611 TypeInDoneEvent (char *move)
16612 {
16613         Board board;
16614         int n, fromX, fromY, toX, toY;
16615         char promoChar;
16616         ChessMove moveType;
16617
16618         // [HGM] FENedit
16619         if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
16620                 EditPositionPasteFEN(move);
16621                 return;
16622         }
16623         // [HGM] movenum: allow move number to be typed in any mode
16624         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
16625           ToNrEvent(2*n-1);
16626           return;
16627         }
16628         // undocumented kludge: allow command-line option to be typed in!
16629         // (potentially fatal, and does not implement the effect of the option.)
16630         // should only be used for options that are values on which future decisions will be made,
16631         // and definitely not on options that would be used during initialization.
16632         if(strstr(move, "!!! -") == move) {
16633             ParseArgsFromString(move+4);
16634             return;
16635         }
16636
16637       if (gameMode != EditGame && currentMove != forwardMostMove &&
16638         gameMode != Training) {
16639         DisplayMoveError(_("Displayed move is not current"));
16640       } else {
16641         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16642           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
16643         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
16644         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16645           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
16646           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
16647         } else {
16648           DisplayMoveError(_("Could not parse move"));
16649         }
16650       }
16651 }
16652
16653 void
16654 DisplayMove (int moveNumber)
16655 {
16656     char message[MSG_SIZ];
16657     char res[MSG_SIZ];
16658     char cpThinkOutput[MSG_SIZ];
16659
16660     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
16661
16662     if (moveNumber == forwardMostMove - 1 ||
16663         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16664
16665         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
16666
16667         if (strchr(cpThinkOutput, '\n')) {
16668             *strchr(cpThinkOutput, '\n') = NULLCHAR;
16669         }
16670     } else {
16671         *cpThinkOutput = NULLCHAR;
16672     }
16673
16674     /* [AS] Hide thinking from human user */
16675     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
16676         *cpThinkOutput = NULLCHAR;
16677         if( thinkOutput[0] != NULLCHAR ) {
16678             int i;
16679
16680             for( i=0; i<=hiddenThinkOutputState; i++ ) {
16681                 cpThinkOutput[i] = '.';
16682             }
16683             cpThinkOutput[i] = NULLCHAR;
16684             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
16685         }
16686     }
16687
16688     if (moveNumber == forwardMostMove - 1 &&
16689         gameInfo.resultDetails != NULL) {
16690         if (gameInfo.resultDetails[0] == NULLCHAR) {
16691           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
16692         } else {
16693           snprintf(res, MSG_SIZ, " {%s} %s",
16694                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
16695         }
16696     } else {
16697         res[0] = NULLCHAR;
16698     }
16699
16700     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16701         DisplayMessage(res, cpThinkOutput);
16702     } else {
16703       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
16704                 WhiteOnMove(moveNumber) ? " " : ".. ",
16705                 parseList[moveNumber], res);
16706         DisplayMessage(message, cpThinkOutput);
16707     }
16708 }
16709
16710 void
16711 DisplayComment (int moveNumber, char *text)
16712 {
16713     char title[MSG_SIZ];
16714
16715     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16716       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
16717     } else {
16718       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
16719               WhiteOnMove(moveNumber) ? " " : ".. ",
16720               parseList[moveNumber]);
16721     }
16722     if (text != NULL && (appData.autoDisplayComment || commentUp))
16723         CommentPopUp(title, text);
16724 }
16725
16726 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
16727  * might be busy thinking or pondering.  It can be omitted if your
16728  * gnuchess is configured to stop thinking immediately on any user
16729  * input.  However, that gnuchess feature depends on the FIONREAD
16730  * ioctl, which does not work properly on some flavors of Unix.
16731  */
16732 void
16733 Attention (ChessProgramState *cps)
16734 {
16735 #if ATTENTION
16736     if (!cps->useSigint) return;
16737     if (appData.noChessProgram || (cps->pr == NoProc)) return;
16738     switch (gameMode) {
16739       case MachinePlaysWhite:
16740       case MachinePlaysBlack:
16741       case TwoMachinesPlay:
16742       case IcsPlayingWhite:
16743       case IcsPlayingBlack:
16744       case AnalyzeMode:
16745       case AnalyzeFile:
16746         /* Skip if we know it isn't thinking */
16747         if (!cps->maybeThinking) return;
16748         if (appData.debugMode)
16749           fprintf(debugFP, "Interrupting %s\n", cps->which);
16750         InterruptChildProcess(cps->pr);
16751         cps->maybeThinking = FALSE;
16752         break;
16753       default:
16754         break;
16755     }
16756 #endif /*ATTENTION*/
16757 }
16758
16759 int
16760 CheckFlags ()
16761 {
16762     if (whiteTimeRemaining <= 0) {
16763         if (!whiteFlag) {
16764             whiteFlag = TRUE;
16765             if (appData.icsActive) {
16766                 if (appData.autoCallFlag &&
16767                     gameMode == IcsPlayingBlack && !blackFlag) {
16768                   SendToICS(ics_prefix);
16769                   SendToICS("flag\n");
16770                 }
16771             } else {
16772                 if (blackFlag) {
16773                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16774                 } else {
16775                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
16776                     if (appData.autoCallFlag) {
16777                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
16778                         return TRUE;
16779                     }
16780                 }
16781             }
16782         }
16783     }
16784     if (blackTimeRemaining <= 0) {
16785         if (!blackFlag) {
16786             blackFlag = TRUE;
16787             if (appData.icsActive) {
16788                 if (appData.autoCallFlag &&
16789                     gameMode == IcsPlayingWhite && !whiteFlag) {
16790                   SendToICS(ics_prefix);
16791                   SendToICS("flag\n");
16792                 }
16793             } else {
16794                 if (whiteFlag) {
16795                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16796                 } else {
16797                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
16798                     if (appData.autoCallFlag) {
16799                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
16800                         return TRUE;
16801                     }
16802                 }
16803             }
16804         }
16805     }
16806     return FALSE;
16807 }
16808
16809 void
16810 CheckTimeControl ()
16811 {
16812     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
16813         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
16814
16815     /*
16816      * add time to clocks when time control is achieved ([HGM] now also used for increment)
16817      */
16818     if ( !WhiteOnMove(forwardMostMove) ) {
16819         /* White made time control */
16820         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
16821         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
16822         /* [HGM] time odds: correct new time quota for time odds! */
16823                                             / WhitePlayer()->timeOdds;
16824         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
16825     } else {
16826         lastBlack -= blackTimeRemaining;
16827         /* Black made time control */
16828         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
16829                                             / WhitePlayer()->other->timeOdds;
16830         lastWhite = whiteTimeRemaining;
16831     }
16832 }
16833
16834 void
16835 DisplayBothClocks ()
16836 {
16837     int wom = gameMode == EditPosition ?
16838       !blackPlaysFirst : WhiteOnMove(currentMove);
16839     DisplayWhiteClock(whiteTimeRemaining, wom);
16840     DisplayBlackClock(blackTimeRemaining, !wom);
16841 }
16842
16843
16844 /* Timekeeping seems to be a portability nightmare.  I think everyone
16845    has ftime(), but I'm really not sure, so I'm including some ifdefs
16846    to use other calls if you don't.  Clocks will be less accurate if
16847    you have neither ftime nor gettimeofday.
16848 */
16849
16850 /* VS 2008 requires the #include outside of the function */
16851 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
16852 #include <sys/timeb.h>
16853 #endif
16854
16855 /* Get the current time as a TimeMark */
16856 void
16857 GetTimeMark (TimeMark *tm)
16858 {
16859 #if HAVE_GETTIMEOFDAY
16860
16861     struct timeval timeVal;
16862     struct timezone timeZone;
16863
16864     gettimeofday(&timeVal, &timeZone);
16865     tm->sec = (long) timeVal.tv_sec;
16866     tm->ms = (int) (timeVal.tv_usec / 1000L);
16867
16868 #else /*!HAVE_GETTIMEOFDAY*/
16869 #if HAVE_FTIME
16870
16871 // include <sys/timeb.h> / moved to just above start of function
16872     struct timeb timeB;
16873
16874     ftime(&timeB);
16875     tm->sec = (long) timeB.time;
16876     tm->ms = (int) timeB.millitm;
16877
16878 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
16879     tm->sec = (long) time(NULL);
16880     tm->ms = 0;
16881 #endif
16882 #endif
16883 }
16884
16885 /* Return the difference in milliseconds between two
16886    time marks.  We assume the difference will fit in a long!
16887 */
16888 long
16889 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
16890 {
16891     return 1000L*(tm2->sec - tm1->sec) +
16892            (long) (tm2->ms - tm1->ms);
16893 }
16894
16895
16896 /*
16897  * Code to manage the game clocks.
16898  *
16899  * In tournament play, black starts the clock and then white makes a move.
16900  * We give the human user a slight advantage if he is playing white---the
16901  * clocks don't run until he makes his first move, so it takes zero time.
16902  * Also, we don't account for network lag, so we could get out of sync
16903  * with GNU Chess's clock -- but then, referees are always right.
16904  */
16905
16906 static TimeMark tickStartTM;
16907 static long intendedTickLength;
16908
16909 long
16910 NextTickLength (long timeRemaining)
16911 {
16912     long nominalTickLength, nextTickLength;
16913
16914     if (timeRemaining > 0L && timeRemaining <= 10000L)
16915       nominalTickLength = 100L;
16916     else
16917       nominalTickLength = 1000L;
16918     nextTickLength = timeRemaining % nominalTickLength;
16919     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
16920
16921     return nextTickLength;
16922 }
16923
16924 /* Adjust clock one minute up or down */
16925 void
16926 AdjustClock (Boolean which, int dir)
16927 {
16928     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
16929     if(which) blackTimeRemaining += 60000*dir;
16930     else      whiteTimeRemaining += 60000*dir;
16931     DisplayBothClocks();
16932     adjustedClock = TRUE;
16933 }
16934
16935 /* Stop clocks and reset to a fresh time control */
16936 void
16937 ResetClocks ()
16938 {
16939     (void) StopClockTimer();
16940     if (appData.icsActive) {
16941         whiteTimeRemaining = blackTimeRemaining = 0;
16942     } else if (searchTime) {
16943         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16944         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16945     } else { /* [HGM] correct new time quote for time odds */
16946         whiteTC = blackTC = fullTimeControlString;
16947         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
16948         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
16949     }
16950     if (whiteFlag || blackFlag) {
16951         DisplayTitle("");
16952         whiteFlag = blackFlag = FALSE;
16953     }
16954     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
16955     DisplayBothClocks();
16956     adjustedClock = FALSE;
16957 }
16958
16959 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
16960
16961 /* Decrement running clock by amount of time that has passed */
16962 void
16963 DecrementClocks ()
16964 {
16965     long timeRemaining;
16966     long lastTickLength, fudge;
16967     TimeMark now;
16968
16969     if (!appData.clockMode) return;
16970     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
16971
16972     GetTimeMark(&now);
16973
16974     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16975
16976     /* Fudge if we woke up a little too soon */
16977     fudge = intendedTickLength - lastTickLength;
16978     if (fudge < 0 || fudge > FUDGE) fudge = 0;
16979
16980     if (WhiteOnMove(forwardMostMove)) {
16981         if(whiteNPS >= 0) lastTickLength = 0;
16982         timeRemaining = whiteTimeRemaining -= lastTickLength;
16983         if(timeRemaining < 0 && !appData.icsActive) {
16984             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
16985             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
16986                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
16987                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
16988             }
16989         }
16990         DisplayWhiteClock(whiteTimeRemaining - fudge,
16991                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16992     } else {
16993         if(blackNPS >= 0) lastTickLength = 0;
16994         timeRemaining = blackTimeRemaining -= lastTickLength;
16995         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
16996             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
16997             if(suddenDeath) {
16998                 blackStartMove = forwardMostMove;
16999                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
17000             }
17001         }
17002         DisplayBlackClock(blackTimeRemaining - fudge,
17003                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17004     }
17005     if (CheckFlags()) return;
17006
17007     if(twoBoards) { // count down secondary board's clocks as well
17008         activePartnerTime -= lastTickLength;
17009         partnerUp = 1;
17010         if(activePartner == 'W')
17011             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
17012         else
17013             DisplayBlackClock(activePartnerTime, TRUE);
17014         partnerUp = 0;
17015     }
17016
17017     tickStartTM = now;
17018     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
17019     StartClockTimer(intendedTickLength);
17020
17021     /* if the time remaining has fallen below the alarm threshold, sound the
17022      * alarm. if the alarm has sounded and (due to a takeback or time control
17023      * with increment) the time remaining has increased to a level above the
17024      * threshold, reset the alarm so it can sound again.
17025      */
17026
17027     if (appData.icsActive && appData.icsAlarm) {
17028
17029         /* make sure we are dealing with the user's clock */
17030         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
17031                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
17032            )) return;
17033
17034         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
17035             alarmSounded = FALSE;
17036         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
17037             PlayAlarmSound();
17038             alarmSounded = TRUE;
17039         }
17040     }
17041 }
17042
17043
17044 /* A player has just moved, so stop the previously running
17045    clock and (if in clock mode) start the other one.
17046    We redisplay both clocks in case we're in ICS mode, because
17047    ICS gives us an update to both clocks after every move.
17048    Note that this routine is called *after* forwardMostMove
17049    is updated, so the last fractional tick must be subtracted
17050    from the color that is *not* on move now.
17051 */
17052 void
17053 SwitchClocks (int newMoveNr)
17054 {
17055     long lastTickLength;
17056     TimeMark now;
17057     int flagged = FALSE;
17058
17059     GetTimeMark(&now);
17060
17061     if (StopClockTimer() && appData.clockMode) {
17062         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17063         if (!WhiteOnMove(forwardMostMove)) {
17064             if(blackNPS >= 0) lastTickLength = 0;
17065             blackTimeRemaining -= lastTickLength;
17066            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17067 //         if(pvInfoList[forwardMostMove].time == -1)
17068                  pvInfoList[forwardMostMove].time =               // use GUI time
17069                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
17070         } else {
17071            if(whiteNPS >= 0) lastTickLength = 0;
17072            whiteTimeRemaining -= lastTickLength;
17073            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17074 //         if(pvInfoList[forwardMostMove].time == -1)
17075                  pvInfoList[forwardMostMove].time =
17076                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
17077         }
17078         flagged = CheckFlags();
17079     }
17080     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
17081     CheckTimeControl();
17082
17083     if (flagged || !appData.clockMode) return;
17084
17085     switch (gameMode) {
17086       case MachinePlaysBlack:
17087       case MachinePlaysWhite:
17088       case BeginningOfGame:
17089         if (pausing) return;
17090         break;
17091
17092       case EditGame:
17093       case PlayFromGameFile:
17094       case IcsExamining:
17095         return;
17096
17097       default:
17098         break;
17099     }
17100
17101     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
17102         if(WhiteOnMove(forwardMostMove))
17103              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17104         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17105     }
17106
17107     tickStartTM = now;
17108     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17109       whiteTimeRemaining : blackTimeRemaining);
17110     StartClockTimer(intendedTickLength);
17111 }
17112
17113
17114 /* Stop both clocks */
17115 void
17116 StopClocks ()
17117 {
17118     long lastTickLength;
17119     TimeMark now;
17120
17121     if (!StopClockTimer()) return;
17122     if (!appData.clockMode) return;
17123
17124     GetTimeMark(&now);
17125
17126     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17127     if (WhiteOnMove(forwardMostMove)) {
17128         if(whiteNPS >= 0) lastTickLength = 0;
17129         whiteTimeRemaining -= lastTickLength;
17130         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
17131     } else {
17132         if(blackNPS >= 0) lastTickLength = 0;
17133         blackTimeRemaining -= lastTickLength;
17134         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
17135     }
17136     CheckFlags();
17137 }
17138
17139 /* Start clock of player on move.  Time may have been reset, so
17140    if clock is already running, stop and restart it. */
17141 void
17142 StartClocks ()
17143 {
17144     (void) StopClockTimer(); /* in case it was running already */
17145     DisplayBothClocks();
17146     if (CheckFlags()) return;
17147
17148     if (!appData.clockMode) return;
17149     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
17150
17151     GetTimeMark(&tickStartTM);
17152     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17153       whiteTimeRemaining : blackTimeRemaining);
17154
17155    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
17156     whiteNPS = blackNPS = -1;
17157     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
17158        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
17159         whiteNPS = first.nps;
17160     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
17161        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
17162         blackNPS = first.nps;
17163     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
17164         whiteNPS = second.nps;
17165     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
17166         blackNPS = second.nps;
17167     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
17168
17169     StartClockTimer(intendedTickLength);
17170 }
17171
17172 char *
17173 TimeString (long ms)
17174 {
17175     long second, minute, hour, day;
17176     char *sign = "";
17177     static char buf[32];
17178
17179     if (ms > 0 && ms <= 9900) {
17180       /* convert milliseconds to tenths, rounding up */
17181       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
17182
17183       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
17184       return buf;
17185     }
17186
17187     /* convert milliseconds to seconds, rounding up */
17188     /* use floating point to avoid strangeness of integer division
17189        with negative dividends on many machines */
17190     second = (long) floor(((double) (ms + 999L)) / 1000.0);
17191
17192     if (second < 0) {
17193         sign = "-";
17194         second = -second;
17195     }
17196
17197     day = second / (60 * 60 * 24);
17198     second = second % (60 * 60 * 24);
17199     hour = second / (60 * 60);
17200     second = second % (60 * 60);
17201     minute = second / 60;
17202     second = second % 60;
17203
17204     if (day > 0)
17205       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
17206               sign, day, hour, minute, second);
17207     else if (hour > 0)
17208       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
17209     else
17210       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
17211
17212     return buf;
17213 }
17214
17215
17216 /*
17217  * This is necessary because some C libraries aren't ANSI C compliant yet.
17218  */
17219 char *
17220 StrStr (char *string, char *match)
17221 {
17222     int i, length;
17223
17224     length = strlen(match);
17225
17226     for (i = strlen(string) - length; i >= 0; i--, string++)
17227       if (!strncmp(match, string, length))
17228         return string;
17229
17230     return NULL;
17231 }
17232
17233 char *
17234 StrCaseStr (char *string, char *match)
17235 {
17236     int i, j, length;
17237
17238     length = strlen(match);
17239
17240     for (i = strlen(string) - length; i >= 0; i--, string++) {
17241         for (j = 0; j < length; j++) {
17242             if (ToLower(match[j]) != ToLower(string[j]))
17243               break;
17244         }
17245         if (j == length) return string;
17246     }
17247
17248     return NULL;
17249 }
17250
17251 #ifndef _amigados
17252 int
17253 StrCaseCmp (char *s1, char *s2)
17254 {
17255     char c1, c2;
17256
17257     for (;;) {
17258         c1 = ToLower(*s1++);
17259         c2 = ToLower(*s2++);
17260         if (c1 > c2) return 1;
17261         if (c1 < c2) return -1;
17262         if (c1 == NULLCHAR) return 0;
17263     }
17264 }
17265
17266
17267 int
17268 ToLower (int c)
17269 {
17270     return isupper(c) ? tolower(c) : c;
17271 }
17272
17273
17274 int
17275 ToUpper (int c)
17276 {
17277     return islower(c) ? toupper(c) : c;
17278 }
17279 #endif /* !_amigados    */
17280
17281 char *
17282 StrSave (char *s)
17283 {
17284   char *ret;
17285
17286   if ((ret = (char *) malloc(strlen(s) + 1)))
17287     {
17288       safeStrCpy(ret, s, strlen(s)+1);
17289     }
17290   return ret;
17291 }
17292
17293 char *
17294 StrSavePtr (char *s, char **savePtr)
17295 {
17296     if (*savePtr) {
17297         free(*savePtr);
17298     }
17299     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
17300       safeStrCpy(*savePtr, s, strlen(s)+1);
17301     }
17302     return(*savePtr);
17303 }
17304
17305 char *
17306 PGNDate ()
17307 {
17308     time_t clock;
17309     struct tm *tm;
17310     char buf[MSG_SIZ];
17311
17312     clock = time((time_t *)NULL);
17313     tm = localtime(&clock);
17314     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
17315             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
17316     return StrSave(buf);
17317 }
17318
17319
17320 char *
17321 PositionToFEN (int move, char *overrideCastling, int moveCounts)
17322 {
17323     int i, j, fromX, fromY, toX, toY;
17324     int whiteToPlay;
17325     char buf[MSG_SIZ];
17326     char *p, *q;
17327     int emptycount;
17328     ChessSquare piece;
17329
17330     whiteToPlay = (gameMode == EditPosition) ?
17331       !blackPlaysFirst : (move % 2 == 0);
17332     p = buf;
17333
17334     /* Piece placement data */
17335     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17336         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
17337         emptycount = 0;
17338         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
17339             if (boards[move][i][j] == EmptySquare) {
17340                 emptycount++;
17341             } else { ChessSquare piece = boards[move][i][j];
17342                 if (emptycount > 0) {
17343                     if(emptycount<10) /* [HGM] can be >= 10 */
17344                         *p++ = '0' + emptycount;
17345                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17346                     emptycount = 0;
17347                 }
17348                 if(PieceToChar(piece) == '+') {
17349                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
17350                     *p++ = '+';
17351                     piece = (ChessSquare)(DEMOTED piece);
17352                 }
17353                 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
17354                 if(p[-1] == '~') {
17355                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
17356                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
17357                     *p++ = '~';
17358                 }
17359             }
17360         }
17361         if (emptycount > 0) {
17362             if(emptycount<10) /* [HGM] can be >= 10 */
17363                 *p++ = '0' + emptycount;
17364             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17365             emptycount = 0;
17366         }
17367         *p++ = '/';
17368     }
17369     *(p - 1) = ' ';
17370
17371     /* [HGM] print Crazyhouse or Shogi holdings */
17372     if( gameInfo.holdingsWidth ) {
17373         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
17374         q = p;
17375         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
17376             piece = boards[move][i][BOARD_WIDTH-1];
17377             if( piece != EmptySquare )
17378               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
17379                   *p++ = PieceToChar(piece);
17380         }
17381         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
17382             piece = boards[move][BOARD_HEIGHT-i-1][0];
17383             if( piece != EmptySquare )
17384               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
17385                   *p++ = PieceToChar(piece);
17386         }
17387
17388         if( q == p ) *p++ = '-';
17389         *p++ = ']';
17390         *p++ = ' ';
17391     }
17392
17393     /* Active color */
17394     *p++ = whiteToPlay ? 'w' : 'b';
17395     *p++ = ' ';
17396
17397   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
17398     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
17399   } else {
17400   if(nrCastlingRights) {
17401      q = p;
17402      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
17403        /* [HGM] write directly from rights */
17404            if(boards[move][CASTLING][2] != NoRights &&
17405               boards[move][CASTLING][0] != NoRights   )
17406                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
17407            if(boards[move][CASTLING][2] != NoRights &&
17408               boards[move][CASTLING][1] != NoRights   )
17409                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
17410            if(boards[move][CASTLING][5] != NoRights &&
17411               boards[move][CASTLING][3] != NoRights   )
17412                 *p++ = boards[move][CASTLING][3] + AAA;
17413            if(boards[move][CASTLING][5] != NoRights &&
17414               boards[move][CASTLING][4] != NoRights   )
17415                 *p++ = boards[move][CASTLING][4] + AAA;
17416      } else {
17417
17418         /* [HGM] write true castling rights */
17419         if( nrCastlingRights == 6 ) {
17420             int q, k=0;
17421             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
17422                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
17423             q = (boards[move][CASTLING][1] == BOARD_LEFT &&
17424                  boards[move][CASTLING][2] != NoRights  );
17425             if(gameInfo.variant == VariantSChess) { // for S-Chess, indicate all vrgin backrank pieces
17426                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_RGHT]; // count white held pieces
17427                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17428                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
17429                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
17430             }
17431             if(q) *p++ = 'Q';
17432             k = 0;
17433             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
17434                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
17435             q = (boards[move][CASTLING][4] == BOARD_LEFT &&
17436                  boards[move][CASTLING][5] != NoRights  );
17437             if(gameInfo.variant == VariantSChess) {
17438                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_LEFT-1]; // count black held pieces
17439                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17440                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
17441                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17442             }
17443             if(q) *p++ = 'q';
17444         }
17445      }
17446      if (q == p) *p++ = '-'; /* No castling rights */
17447      *p++ = ' ';
17448   }
17449
17450   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17451      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17452      gameInfo.variant != VariantMakruk   && gameInfo.variant != VariantASEAN ) {
17453     /* En passant target square */
17454     if (move > backwardMostMove) {
17455         fromX = moveList[move - 1][0] - AAA;
17456         fromY = moveList[move - 1][1] - ONE;
17457         toX = moveList[move - 1][2] - AAA;
17458         toY = moveList[move - 1][3] - ONE;
17459         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
17460             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
17461             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
17462             fromX == toX) {
17463             /* 2-square pawn move just happened */
17464             *p++ = toX + AAA;
17465             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17466         } else {
17467             *p++ = '-';
17468         }
17469     } else if(move == backwardMostMove) {
17470         // [HGM] perhaps we should always do it like this, and forget the above?
17471         if((signed char)boards[move][EP_STATUS] >= 0) {
17472             *p++ = boards[move][EP_STATUS] + AAA;
17473             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17474         } else {
17475             *p++ = '-';
17476         }
17477     } else {
17478         *p++ = '-';
17479     }
17480     *p++ = ' ';
17481   }
17482   }
17483
17484     if(moveCounts)
17485     {   int i = 0, j=move;
17486
17487         /* [HGM] find reversible plies */
17488         if (appData.debugMode) { int k;
17489             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
17490             for(k=backwardMostMove; k<=forwardMostMove; k++)
17491                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
17492
17493         }
17494
17495         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
17496         if( j == backwardMostMove ) i += initialRulePlies;
17497         sprintf(p, "%d ", i);
17498         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
17499
17500         /* Fullmove number */
17501         sprintf(p, "%d", (move / 2) + 1);
17502     } else *--p = NULLCHAR;
17503
17504     return StrSave(buf);
17505 }
17506
17507 Boolean
17508 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
17509 {
17510     int i, j, k, w=0;
17511     char *p, c;
17512     int emptycount, virgin[BOARD_FILES];
17513     ChessSquare piece;
17514
17515     p = fen;
17516
17517     /* Piece placement data */
17518     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17519         j = 0;
17520         for (;;) {
17521             if (*p == '/' || *p == ' ' || *p == '[' ) {
17522                 if(j > w) w = j;
17523                 emptycount = gameInfo.boardWidth - j;
17524                 while (emptycount--)
17525                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17526                 if (*p == '/') p++;
17527                 else if(autoSize) { // we stumbled unexpectedly into end of board
17528                     for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
17529                         for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
17530                     }
17531                     appData.NrRanks = gameInfo.boardHeight - i; i=0;
17532                 }
17533                 break;
17534 #if(BOARD_FILES >= 10)
17535             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
17536                 p++; emptycount=10;
17537                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17538                 while (emptycount--)
17539                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17540 #endif
17541             } else if (*p == '*') {
17542                 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
17543             } else if (isdigit(*p)) {
17544                 emptycount = *p++ - '0';
17545                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
17546                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17547                 while (emptycount--)
17548                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17549             } else if (*p == '+' || isalpha(*p)) {
17550                 if (j >= gameInfo.boardWidth) return FALSE;
17551                 if(*p=='+') {
17552                     piece = CharToPiece(*++p);
17553                     if(piece == EmptySquare) return FALSE; /* unknown piece */
17554                     piece = (ChessSquare) (CHUPROMOTED piece ); p++;
17555                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
17556                 } else piece = CharToPiece(*p++);
17557
17558                 if(piece==EmptySquare) return FALSE; /* unknown piece */
17559                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
17560                     piece = (ChessSquare) (PROMOTED piece);
17561                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
17562                     p++;
17563                 }
17564                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
17565             } else {
17566                 return FALSE;
17567             }
17568         }
17569     }
17570     while (*p == '/' || *p == ' ') p++;
17571
17572     if(autoSize) appData.NrFiles = w, InitPosition(TRUE);
17573
17574     /* [HGM] by default clear Crazyhouse holdings, if present */
17575     if(gameInfo.holdingsWidth) {
17576        for(i=0; i<BOARD_HEIGHT; i++) {
17577            board[i][0]             = EmptySquare; /* black holdings */
17578            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
17579            board[i][1]             = (ChessSquare) 0; /* black counts */
17580            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
17581        }
17582     }
17583
17584     /* [HGM] look for Crazyhouse holdings here */
17585     while(*p==' ') p++;
17586     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
17587         if(*p == '[') p++;
17588         if(*p == '-' ) p++; /* empty holdings */ else {
17589             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
17590             /* if we would allow FEN reading to set board size, we would   */
17591             /* have to add holdings and shift the board read so far here   */
17592             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
17593                 p++;
17594                 if((int) piece >= (int) BlackPawn ) {
17595                     i = (int)piece - (int)BlackPawn;
17596                     i = PieceToNumber((ChessSquare)i);
17597                     if( i >= gameInfo.holdingsSize ) return FALSE;
17598                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
17599                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
17600                 } else {
17601                     i = (int)piece - (int)WhitePawn;
17602                     i = PieceToNumber((ChessSquare)i);
17603                     if( i >= gameInfo.holdingsSize ) return FALSE;
17604                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
17605                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
17606                 }
17607             }
17608         }
17609         if(*p == ']') p++;
17610     }
17611
17612     while(*p == ' ') p++;
17613
17614     /* Active color */
17615     c = *p++;
17616     if(appData.colorNickNames) {
17617       if( c == appData.colorNickNames[0] ) c = 'w'; else
17618       if( c == appData.colorNickNames[1] ) c = 'b';
17619     }
17620     switch (c) {
17621       case 'w':
17622         *blackPlaysFirst = FALSE;
17623         break;
17624       case 'b':
17625         *blackPlaysFirst = TRUE;
17626         break;
17627       default:
17628         return FALSE;
17629     }
17630
17631     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
17632     /* return the extra info in global variiables             */
17633
17634     /* set defaults in case FEN is incomplete */
17635     board[EP_STATUS] = EP_UNKNOWN;
17636     for(i=0; i<nrCastlingRights; i++ ) {
17637         board[CASTLING][i] =
17638             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
17639     }   /* assume possible unless obviously impossible */
17640     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
17641     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
17642     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
17643                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
17644     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
17645     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
17646     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
17647                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
17648     FENrulePlies = 0;
17649
17650     while(*p==' ') p++;
17651     if(nrCastlingRights) {
17652       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
17653       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
17654           /* castling indicator present, so default becomes no castlings */
17655           for(i=0; i<nrCastlingRights; i++ ) {
17656                  board[CASTLING][i] = NoRights;
17657           }
17658       }
17659       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
17660              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess) &&
17661              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
17662              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
17663         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
17664
17665         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
17666             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
17667             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
17668         }
17669         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
17670             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
17671         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
17672                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
17673         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
17674                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
17675         switch(c) {
17676           case'K':
17677               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
17678               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
17679               board[CASTLING][2] = whiteKingFile;
17680               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
17681               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17682               break;
17683           case'Q':
17684               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
17685               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
17686               board[CASTLING][2] = whiteKingFile;
17687               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
17688               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17689               break;
17690           case'k':
17691               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
17692               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
17693               board[CASTLING][5] = blackKingFile;
17694               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
17695               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17696               break;
17697           case'q':
17698               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
17699               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
17700               board[CASTLING][5] = blackKingFile;
17701               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
17702               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17703           case '-':
17704               break;
17705           default: /* FRC castlings */
17706               if(c >= 'a') { /* black rights */
17707                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
17708                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17709                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
17710                   if(i == BOARD_RGHT) break;
17711                   board[CASTLING][5] = i;
17712                   c -= AAA;
17713                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
17714                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
17715                   if(c > i)
17716                       board[CASTLING][3] = c;
17717                   else
17718                       board[CASTLING][4] = c;
17719               } else { /* white rights */
17720                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
17721                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17722                     if(board[0][i] == WhiteKing) break;
17723                   if(i == BOARD_RGHT) break;
17724                   board[CASTLING][2] = i;
17725                   c -= AAA - 'a' + 'A';
17726                   if(board[0][c] >= WhiteKing) break;
17727                   if(c > i)
17728                       board[CASTLING][0] = c;
17729                   else
17730                       board[CASTLING][1] = c;
17731               }
17732         }
17733       }
17734       for(i=0; i<nrCastlingRights; i++)
17735         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
17736       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = virgin[i];
17737     if (appData.debugMode) {
17738         fprintf(debugFP, "FEN castling rights:");
17739         for(i=0; i<nrCastlingRights; i++)
17740         fprintf(debugFP, " %d", board[CASTLING][i]);
17741         fprintf(debugFP, "\n");
17742     }
17743
17744       while(*p==' ') p++;
17745     }
17746
17747     /* read e.p. field in games that know e.p. capture */
17748     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17749        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17750        gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
17751       if(*p=='-') {
17752         p++; board[EP_STATUS] = EP_NONE;
17753       } else {
17754          char c = *p++ - AAA;
17755
17756          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
17757          if(*p >= '0' && *p <='9') p++;
17758          board[EP_STATUS] = c;
17759       }
17760     }
17761
17762
17763     if(sscanf(p, "%d", &i) == 1) {
17764         FENrulePlies = i; /* 50-move ply counter */
17765         /* (The move number is still ignored)    */
17766     }
17767
17768     return TRUE;
17769 }
17770
17771 void
17772 EditPositionPasteFEN (char *fen)
17773 {
17774   if (fen != NULL) {
17775     Board initial_position;
17776
17777     if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
17778       DisplayError(_("Bad FEN position in clipboard"), 0);
17779       return ;
17780     } else {
17781       int savedBlackPlaysFirst = blackPlaysFirst;
17782       EditPositionEvent();
17783       blackPlaysFirst = savedBlackPlaysFirst;
17784       CopyBoard(boards[0], initial_position);
17785       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
17786       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
17787       DisplayBothClocks();
17788       DrawPosition(FALSE, boards[currentMove]);
17789     }
17790   }
17791 }
17792
17793 static char cseq[12] = "\\   ";
17794
17795 Boolean
17796 set_cont_sequence (char *new_seq)
17797 {
17798     int len;
17799     Boolean ret;
17800
17801     // handle bad attempts to set the sequence
17802         if (!new_seq)
17803                 return 0; // acceptable error - no debug
17804
17805     len = strlen(new_seq);
17806     ret = (len > 0) && (len < sizeof(cseq));
17807     if (ret)
17808       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
17809     else if (appData.debugMode)
17810       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
17811     return ret;
17812 }
17813
17814 /*
17815     reformat a source message so words don't cross the width boundary.  internal
17816     newlines are not removed.  returns the wrapped size (no null character unless
17817     included in source message).  If dest is NULL, only calculate the size required
17818     for the dest buffer.  lp argument indicats line position upon entry, and it's
17819     passed back upon exit.
17820 */
17821 int
17822 wrap (char *dest, char *src, int count, int width, int *lp)
17823 {
17824     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
17825
17826     cseq_len = strlen(cseq);
17827     old_line = line = *lp;
17828     ansi = len = clen = 0;
17829
17830     for (i=0; i < count; i++)
17831     {
17832         if (src[i] == '\033')
17833             ansi = 1;
17834
17835         // if we hit the width, back up
17836         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
17837         {
17838             // store i & len in case the word is too long
17839             old_i = i, old_len = len;
17840
17841             // find the end of the last word
17842             while (i && src[i] != ' ' && src[i] != '\n')
17843             {
17844                 i--;
17845                 len--;
17846             }
17847
17848             // word too long?  restore i & len before splitting it
17849             if ((old_i-i+clen) >= width)
17850             {
17851                 i = old_i;
17852                 len = old_len;
17853             }
17854
17855             // extra space?
17856             if (i && src[i-1] == ' ')
17857                 len--;
17858
17859             if (src[i] != ' ' && src[i] != '\n')
17860             {
17861                 i--;
17862                 if (len)
17863                     len--;
17864             }
17865
17866             // now append the newline and continuation sequence
17867             if (dest)
17868                 dest[len] = '\n';
17869             len++;
17870             if (dest)
17871                 strncpy(dest+len, cseq, cseq_len);
17872             len += cseq_len;
17873             line = cseq_len;
17874             clen = cseq_len;
17875             continue;
17876         }
17877
17878         if (dest)
17879             dest[len] = src[i];
17880         len++;
17881         if (!ansi)
17882             line++;
17883         if (src[i] == '\n')
17884             line = 0;
17885         if (src[i] == 'm')
17886             ansi = 0;
17887     }
17888     if (dest && appData.debugMode)
17889     {
17890         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
17891             count, width, line, len, *lp);
17892         show_bytes(debugFP, src, count);
17893         fprintf(debugFP, "\ndest: ");
17894         show_bytes(debugFP, dest, len);
17895         fprintf(debugFP, "\n");
17896     }
17897     *lp = dest ? line : old_line;
17898
17899     return len;
17900 }
17901
17902 // [HGM] vari: routines for shelving variations
17903 Boolean modeRestore = FALSE;
17904
17905 void
17906 PushInner (int firstMove, int lastMove)
17907 {
17908         int i, j, nrMoves = lastMove - firstMove;
17909
17910         // push current tail of game on stack
17911         savedResult[storedGames] = gameInfo.result;
17912         savedDetails[storedGames] = gameInfo.resultDetails;
17913         gameInfo.resultDetails = NULL;
17914         savedFirst[storedGames] = firstMove;
17915         savedLast [storedGames] = lastMove;
17916         savedFramePtr[storedGames] = framePtr;
17917         framePtr -= nrMoves; // reserve space for the boards
17918         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
17919             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
17920             for(j=0; j<MOVE_LEN; j++)
17921                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
17922             for(j=0; j<2*MOVE_LEN; j++)
17923                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
17924             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
17925             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
17926             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
17927             pvInfoList[firstMove+i-1].depth = 0;
17928             commentList[framePtr+i] = commentList[firstMove+i];
17929             commentList[firstMove+i] = NULL;
17930         }
17931
17932         storedGames++;
17933         forwardMostMove = firstMove; // truncate game so we can start variation
17934 }
17935
17936 void
17937 PushTail (int firstMove, int lastMove)
17938 {
17939         if(appData.icsActive) { // only in local mode
17940                 forwardMostMove = currentMove; // mimic old ICS behavior
17941                 return;
17942         }
17943         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
17944
17945         PushInner(firstMove, lastMove);
17946         if(storedGames == 1) GreyRevert(FALSE);
17947         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
17948 }
17949
17950 void
17951 PopInner (Boolean annotate)
17952 {
17953         int i, j, nrMoves;
17954         char buf[8000], moveBuf[20];
17955
17956         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
17957         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
17958         nrMoves = savedLast[storedGames] - currentMove;
17959         if(annotate) {
17960                 int cnt = 10;
17961                 if(!WhiteOnMove(currentMove))
17962                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
17963                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
17964                 for(i=currentMove; i<forwardMostMove; i++) {
17965                         if(WhiteOnMove(i))
17966                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
17967                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
17968                         strcat(buf, moveBuf);
17969                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
17970                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
17971                 }
17972                 strcat(buf, ")");
17973         }
17974         for(i=1; i<=nrMoves; i++) { // copy last variation back
17975             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
17976             for(j=0; j<MOVE_LEN; j++)
17977                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
17978             for(j=0; j<2*MOVE_LEN; j++)
17979                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
17980             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
17981             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
17982             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
17983             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
17984             commentList[currentMove+i] = commentList[framePtr+i];
17985             commentList[framePtr+i] = NULL;
17986         }
17987         if(annotate) AppendComment(currentMove+1, buf, FALSE);
17988         framePtr = savedFramePtr[storedGames];
17989         gameInfo.result = savedResult[storedGames];
17990         if(gameInfo.resultDetails != NULL) {
17991             free(gameInfo.resultDetails);
17992       }
17993         gameInfo.resultDetails = savedDetails[storedGames];
17994         forwardMostMove = currentMove + nrMoves;
17995 }
17996
17997 Boolean
17998 PopTail (Boolean annotate)
17999 {
18000         if(appData.icsActive) return FALSE; // only in local mode
18001         if(!storedGames) return FALSE; // sanity
18002         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
18003
18004         PopInner(annotate);
18005         if(currentMove < forwardMostMove) ForwardEvent(); else
18006         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
18007
18008         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
18009         return TRUE;
18010 }
18011
18012 void
18013 CleanupTail ()
18014 {       // remove all shelved variations
18015         int i;
18016         for(i=0; i<storedGames; i++) {
18017             if(savedDetails[i])
18018                 free(savedDetails[i]);
18019             savedDetails[i] = NULL;
18020         }
18021         for(i=framePtr; i<MAX_MOVES; i++) {
18022                 if(commentList[i]) free(commentList[i]);
18023                 commentList[i] = NULL;
18024         }
18025         framePtr = MAX_MOVES-1;
18026         storedGames = 0;
18027 }
18028
18029 void
18030 LoadVariation (int index, char *text)
18031 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
18032         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
18033         int level = 0, move;
18034
18035         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
18036         // first find outermost bracketing variation
18037         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
18038             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
18039                 if(*p == '{') wait = '}'; else
18040                 if(*p == '[') wait = ']'; else
18041                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
18042                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
18043             }
18044             if(*p == wait) wait = NULLCHAR; // closing ]} found
18045             p++;
18046         }
18047         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
18048         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
18049         end[1] = NULLCHAR; // clip off comment beyond variation
18050         ToNrEvent(currentMove-1);
18051         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
18052         // kludge: use ParsePV() to append variation to game
18053         move = currentMove;
18054         ParsePV(start, TRUE, TRUE);
18055         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
18056         ClearPremoveHighlights();
18057         CommentPopDown();
18058         ToNrEvent(currentMove+1);
18059 }
18060
18061 void
18062 LoadTheme ()
18063 {
18064     char *p, *q, buf[MSG_SIZ];
18065     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
18066         snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
18067         ParseArgsFromString(buf);
18068         ActivateTheme(TRUE); // also redo colors
18069         return;
18070     }
18071     p = nickName;
18072     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
18073     {
18074         int len;
18075         q = appData.themeNames;
18076         snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
18077       if(appData.useBitmaps) {
18078         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
18079                 appData.liteBackTextureFile, appData.darkBackTextureFile,
18080                 appData.liteBackTextureMode,
18081                 appData.darkBackTextureMode );
18082       } else {
18083         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
18084                 Col2Text(2),   // lightSquareColor
18085                 Col2Text(3) ); // darkSquareColor
18086       }
18087       if(appData.useBorder) {
18088         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
18089                 appData.border);
18090       } else {
18091         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
18092       }
18093       if(appData.useFont) {
18094         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
18095                 appData.renderPiecesWithFont,
18096                 appData.fontToPieceTable,
18097                 Col2Text(9),    // appData.fontBackColorWhite
18098                 Col2Text(10) ); // appData.fontForeColorBlack
18099       } else {
18100         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
18101                 appData.pieceDirectory);
18102         if(!appData.pieceDirectory[0])
18103           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
18104                 Col2Text(0),   // whitePieceColor
18105                 Col2Text(1) ); // blackPieceColor
18106       }
18107       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
18108                 Col2Text(4),   // highlightSquareColor
18109                 Col2Text(5) ); // premoveHighlightColor
18110         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
18111         if(insert != q) insert[-1] = NULLCHAR;
18112         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
18113         if(q)   free(q);
18114     }
18115     ActivateTheme(FALSE);
18116 }