Allow engine to define its own variant names
[xboard.git] / backend.c
1 /*
2  * backend.c -- Common back end for X and Windows NT versions of
3  *
4  * Copyright 1991 by Digital Equipment Corporation, Maynard,
5  * Massachusetts.
6  *
7  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8  * 2007, 2008, 2009, 2010, 2011, 2012, 2013 Free Software Foundation, Inc.
9  *
10  * Enhancements Copyright 2005 Alessandro Scotti
11  *
12  * The following terms apply to Digital Equipment Corporation's copyright
13  * interest in XBoard:
14  * ------------------------------------------------------------------------
15  * All Rights Reserved
16  *
17  * Permission to use, copy, modify, and distribute this software and its
18  * documentation for any purpose and without fee is hereby granted,
19  * provided that the above copyright notice appear in all copies and that
20  * both that copyright notice and this permission notice appear in
21  * supporting documentation, and that the name of Digital not be
22  * used in advertising or publicity pertaining to distribution of the
23  * software without specific, written prior permission.
24  *
25  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
31  * SOFTWARE.
32  * ------------------------------------------------------------------------
33  *
34  * The following terms apply to the enhanced version of XBoard
35  * distributed by the Free Software Foundation:
36  * ------------------------------------------------------------------------
37  *
38  * GNU XBoard is free software: you can redistribute it and/or modify
39  * it under the terms of the GNU General Public License as published by
40  * the Free Software Foundation, either version 3 of the License, or (at
41  * your option) any later version.
42  *
43  * GNU XBoard is distributed in the hope that it will be useful, but
44  * WITHOUT ANY WARRANTY; without even the implied warranty of
45  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46  * General Public License for more details.
47  *
48  * You should have received a copy of the GNU General Public License
49  * along with this program. If not, see http://www.gnu.org/licenses/.  *
50  *
51  *------------------------------------------------------------------------
52  ** See the file ChangeLog for a revision history.  */
53
54 /* [AS] Also useful here for debugging */
55 #ifdef WIN32
56 #include <windows.h>
57
58 int flock(int f, int code);
59 #define LOCK_EX 2
60 #define SLASH '\\'
61
62 #else
63
64 #include <sys/file.h>
65 #define SLASH '/'
66
67 #endif
68
69 #include "config.h"
70
71 #include <assert.h>
72 #include <stdio.h>
73 #include <ctype.h>
74 #include <errno.h>
75 #include <sys/types.h>
76 #include <sys/stat.h>
77 #include <math.h>
78 #include <ctype.h>
79
80 #if STDC_HEADERS
81 # include <stdlib.h>
82 # include <string.h>
83 # include <stdarg.h>
84 #else /* not STDC_HEADERS */
85 # if HAVE_STRING_H
86 #  include <string.h>
87 # else /* not HAVE_STRING_H */
88 #  include <strings.h>
89 # endif /* not HAVE_STRING_H */
90 #endif /* not STDC_HEADERS */
91
92 #if HAVE_SYS_FCNTL_H
93 # include <sys/fcntl.h>
94 #else /* not HAVE_SYS_FCNTL_H */
95 # if HAVE_FCNTL_H
96 #  include <fcntl.h>
97 # endif /* HAVE_FCNTL_H */
98 #endif /* not HAVE_SYS_FCNTL_H */
99
100 #if TIME_WITH_SYS_TIME
101 # include <sys/time.h>
102 # include <time.h>
103 #else
104 # if HAVE_SYS_TIME_H
105 #  include <sys/time.h>
106 # else
107 #  include <time.h>
108 # endif
109 #endif
110
111 #if defined(_amigados) && !defined(__GNUC__)
112 struct timezone {
113     int tz_minuteswest;
114     int tz_dsttime;
115 };
116 extern int gettimeofday(struct timeval *, struct timezone *);
117 #endif
118
119 #if HAVE_UNISTD_H
120 # include <unistd.h>
121 #endif
122
123 #include "common.h"
124 #include "frontend.h"
125 #include "backend.h"
126 #include "parser.h"
127 #include "moves.h"
128 #if ZIPPY
129 # include "zippy.h"
130 #endif
131 #include "backendz.h"
132 #include "evalgraph.h"
133 #include "gettext.h"
134
135 #ifdef ENABLE_NLS
136 # define _(s) gettext (s)
137 # define N_(s) gettext_noop (s)
138 # define T_(s) gettext(s)
139 #else
140 # ifdef WIN32
141 #   define _(s) T_(s)
142 #   define N_(s) s
143 # else
144 #   define _(s) (s)
145 #   define N_(s) s
146 #   define T_(s) s
147 # endif
148 #endif
149
150
151 int establish P((void));
152 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
153                          char *buf, int count, int error));
154 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
155                       char *buf, int count, int error));
156 void SendToICS P((char *s));
157 void SendToICSDelayed P((char *s, long msdelay));
158 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
159 void HandleMachineMove P((char *message, ChessProgramState *cps));
160 int AutoPlayOneMove P((void));
161 int LoadGameOneMove P((ChessMove readAhead));
162 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
163 int LoadPositionFromFile P((char *filename, int n, char *title));
164 int SavePositionToFile P((char *filename));
165 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
166 void ShowMove P((int fromX, int fromY, int toX, int toY));
167 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
168                    /*char*/int promoChar));
169 void BackwardInner P((int target));
170 void ForwardInner P((int target));
171 int Adjudicate P((ChessProgramState *cps));
172 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
173 void EditPositionDone P((Boolean fakeRights));
174 void PrintOpponents P((FILE *fp));
175 void PrintPosition P((FILE *fp, int move));
176 void StartChessProgram P((ChessProgramState *cps));
177 void SendToProgram P((char *message, ChessProgramState *cps));
178 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
179 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
180                            char *buf, int count, int error));
181 void SendTimeControl P((ChessProgramState *cps,
182                         int mps, long tc, int inc, int sd, int st));
183 char *TimeControlTagValue P((void));
184 void Attention P((ChessProgramState *cps));
185 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
186 int ResurrectChessProgram P((void));
187 void DisplayComment P((int moveNumber, char *text));
188 void DisplayMove P((int moveNumber));
189
190 void ParseGameHistory P((char *game));
191 void ParseBoard12 P((char *string));
192 void KeepAlive P((void));
193 void StartClocks P((void));
194 void SwitchClocks P((int nr));
195 void StopClocks P((void));
196 void ResetClocks P((void));
197 char *PGNDate P((void));
198 void SetGameInfo P((void));
199 int RegisterMove P((void));
200 void MakeRegisteredMove P((void));
201 void TruncateGame P((void));
202 int looking_at P((char *, int *, char *));
203 void CopyPlayerNameIntoFileName P((char **, char *));
204 char *SavePart P((char *));
205 int SaveGameOldStyle P((FILE *));
206 int SaveGamePGN P((FILE *));
207 int CheckFlags P((void));
208 long NextTickLength P((long));
209 void CheckTimeControl P((void));
210 void show_bytes P((FILE *, char *, int));
211 int string_to_rating P((char *str));
212 void ParseFeatures P((char* args, ChessProgramState *cps));
213 void InitBackEnd3 P((void));
214 void FeatureDone P((ChessProgramState* cps, int val));
215 void InitChessProgram P((ChessProgramState *cps, int setup));
216 void OutputKibitz(int window, char *text);
217 int PerpetualChase(int first, int last);
218 int EngineOutputIsUp();
219 void InitDrawingSizes(int x, int y);
220 void NextMatchGame P((void));
221 int NextTourneyGame P((int nr, int *swap));
222 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
223 FILE *WriteTourneyFile P((char *results, FILE *f));
224 void DisplayTwoMachinesTitle P(());
225 static void ExcludeClick P((int index));
226 void ToggleSecond P((void));
227 void PauseEngine P((ChessProgramState *cps));
228 static int NonStandardBoardSize P((void));
229
230 #ifdef WIN32
231        extern void ConsoleCreate();
232 #endif
233
234 ChessProgramState *WhitePlayer();
235 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
236 int VerifyDisplayMode P(());
237
238 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
239 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
240 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
241 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
242 void ics_update_width P((int new_width));
243 extern char installDir[MSG_SIZ];
244 VariantClass startVariant; /* [HGM] nicks: initial variant */
245 Boolean abortMatch;
246
247 extern int tinyLayout, smallLayout;
248 ChessProgramStats programStats;
249 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
250 int endPV = -1;
251 static int exiting = 0; /* [HGM] moved to top */
252 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
253 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
254 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
255 int partnerHighlight[2];
256 Boolean partnerBoardValid = 0;
257 char partnerStatus[MSG_SIZ];
258 Boolean partnerUp;
259 Boolean originalFlip;
260 Boolean twoBoards = 0;
261 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
262 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
263 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
264 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
265 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
266 int opponentKibitzes;
267 int lastSavedGame; /* [HGM] save: ID of game */
268 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
269 extern int chatCount;
270 int chattingPartner;
271 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
272 char legal[BOARD_RANKS][BOARD_FILES];  /* [HGM] legal target squares */
273 char lastMsg[MSG_SIZ];
274 ChessSquare pieceSweep = EmptySquare;
275 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
276 int promoDefaultAltered;
277 int keepInfo = 0; /* [HGM] to protect PGN tags in auto-step game analysis */
278
279 /* States for ics_getting_history */
280 #define H_FALSE 0
281 #define H_REQUESTED 1
282 #define H_GOT_REQ_HEADER 2
283 #define H_GOT_UNREQ_HEADER 3
284 #define H_GETTING_MOVES 4
285 #define H_GOT_UNWANTED_HEADER 5
286
287 /* whosays values for GameEnds */
288 #define GE_ICS 0
289 #define GE_ENGINE 1
290 #define GE_PLAYER 2
291 #define GE_FILE 3
292 #define GE_XBOARD 4
293 #define GE_ENGINE1 5
294 #define GE_ENGINE2 6
295
296 /* Maximum number of games in a cmail message */
297 #define CMAIL_MAX_GAMES 20
298
299 /* Different types of move when calling RegisterMove */
300 #define CMAIL_MOVE   0
301 #define CMAIL_RESIGN 1
302 #define CMAIL_DRAW   2
303 #define CMAIL_ACCEPT 3
304
305 /* Different types of result to remember for each game */
306 #define CMAIL_NOT_RESULT 0
307 #define CMAIL_OLD_RESULT 1
308 #define CMAIL_NEW_RESULT 2
309
310 /* Telnet protocol constants */
311 #define TN_WILL 0373
312 #define TN_WONT 0374
313 #define TN_DO   0375
314 #define TN_DONT 0376
315 #define TN_IAC  0377
316 #define TN_ECHO 0001
317 #define TN_SGA  0003
318 #define TN_PORT 23
319
320 char*
321 safeStrCpy (char *dst, const char *src, size_t count)
322 { // [HGM] made safe
323   int i;
324   assert( dst != NULL );
325   assert( src != NULL );
326   assert( count > 0 );
327
328   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
329   if(  i == count && dst[count-1] != NULLCHAR)
330     {
331       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
332       if(appData.debugMode)
333         fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
334     }
335
336   return dst;
337 }
338
339 /* Some compiler can't cast u64 to double
340  * This function do the job for us:
341
342  * We use the highest bit for cast, this only
343  * works if the highest bit is not
344  * in use (This should not happen)
345  *
346  * We used this for all compiler
347  */
348 double
349 u64ToDouble (u64 value)
350 {
351   double r;
352   u64 tmp = value & u64Const(0x7fffffffffffffff);
353   r = (double)(s64)tmp;
354   if (value & u64Const(0x8000000000000000))
355        r +=  9.2233720368547758080e18; /* 2^63 */
356  return r;
357 }
358
359 /* Fake up flags for now, as we aren't keeping track of castling
360    availability yet. [HGM] Change of logic: the flag now only
361    indicates the type of castlings allowed by the rule of the game.
362    The actual rights themselves are maintained in the array
363    castlingRights, as part of the game history, and are not probed
364    by this function.
365  */
366 int
367 PosFlags (index)
368 {
369   int flags = F_ALL_CASTLE_OK;
370   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
371   switch (gameInfo.variant) {
372   case VariantSuicide:
373     flags &= ~F_ALL_CASTLE_OK;
374   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
375     flags |= F_IGNORE_CHECK;
376   case VariantLosers:
377     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
378     break;
379   case VariantAtomic:
380     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
381     break;
382   case VariantKriegspiel:
383     flags |= F_KRIEGSPIEL_CAPTURE;
384     break;
385   case VariantCapaRandom:
386   case VariantFischeRandom:
387     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
388   case VariantNoCastle:
389   case VariantShatranj:
390   case VariantCourier:
391   case VariantMakruk:
392   case VariantASEAN:
393   case VariantGrand:
394     flags &= ~F_ALL_CASTLE_OK;
395     break;
396   default:
397     break;
398   }
399   return flags;
400 }
401
402 FILE *gameFileFP, *debugFP, *serverFP;
403 char *currentDebugFile; // [HGM] debug split: to remember name
404
405 /*
406     [AS] Note: sometimes, the sscanf() function is used to parse the input
407     into a fixed-size buffer. Because of this, we must be prepared to
408     receive strings as long as the size of the input buffer, which is currently
409     set to 4K for Windows and 8K for the rest.
410     So, we must either allocate sufficiently large buffers here, or
411     reduce the size of the input buffer in the input reading part.
412 */
413
414 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
415 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
416 char thinkOutput1[MSG_SIZ*10];
417
418 ChessProgramState first, second, pairing;
419
420 /* premove variables */
421 int premoveToX = 0;
422 int premoveToY = 0;
423 int premoveFromX = 0;
424 int premoveFromY = 0;
425 int premovePromoChar = 0;
426 int gotPremove = 0;
427 Boolean alarmSounded;
428 /* end premove variables */
429
430 char *ics_prefix = "$";
431 enum ICS_TYPE ics_type = ICS_GENERIC;
432
433 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
434 int pauseExamForwardMostMove = 0;
435 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
436 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
437 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
438 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
439 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
440 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
441 int whiteFlag = FALSE, blackFlag = FALSE;
442 int userOfferedDraw = FALSE;
443 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
444 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
445 int cmailMoveType[CMAIL_MAX_GAMES];
446 long ics_clock_paused = 0;
447 ProcRef icsPR = NoProc, cmailPR = NoProc;
448 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
449 GameMode gameMode = BeginningOfGame;
450 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
451 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
452 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
453 int hiddenThinkOutputState = 0; /* [AS] */
454 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
455 int adjudicateLossPlies = 6;
456 char white_holding[64], black_holding[64];
457 TimeMark lastNodeCountTime;
458 long lastNodeCount=0;
459 int shiftKey, controlKey; // [HGM] set by mouse handler
460
461 int have_sent_ICS_logon = 0;
462 int movesPerSession;
463 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
464 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack, activePartnerTime;
465 Boolean adjustedClock;
466 long timeControl_2; /* [AS] Allow separate time controls */
467 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC, activePartner; /* [HGM] secondary TC: merge of MPS, TC and inc */
468 long timeRemaining[2][MAX_MOVES];
469 int matchGame = 0, nextGame = 0, roundNr = 0;
470 Boolean waitingForGame = FALSE, startingEngine = FALSE;
471 TimeMark programStartTime, pauseStart;
472 char ics_handle[MSG_SIZ];
473 int have_set_title = 0;
474
475 /* animateTraining preserves the state of appData.animate
476  * when Training mode is activated. This allows the
477  * response to be animated when appData.animate == TRUE and
478  * appData.animateDragging == TRUE.
479  */
480 Boolean animateTraining;
481
482 GameInfo gameInfo;
483
484 AppData appData;
485
486 Board boards[MAX_MOVES];
487 /* [HGM] Following 7 needed for accurate legality tests: */
488 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
489 signed char  initialRights[BOARD_FILES];
490 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
491 int   initialRulePlies, FENrulePlies;
492 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
493 int loadFlag = 0;
494 Boolean shuffleOpenings;
495 int mute; // mute all sounds
496
497 // [HGM] vari: next 12 to save and restore variations
498 #define MAX_VARIATIONS 10
499 int framePtr = MAX_MOVES-1; // points to free stack entry
500 int storedGames = 0;
501 int savedFirst[MAX_VARIATIONS];
502 int savedLast[MAX_VARIATIONS];
503 int savedFramePtr[MAX_VARIATIONS];
504 char *savedDetails[MAX_VARIATIONS];
505 ChessMove savedResult[MAX_VARIATIONS];
506
507 void PushTail P((int firstMove, int lastMove));
508 Boolean PopTail P((Boolean annotate));
509 void PushInner P((int firstMove, int lastMove));
510 void PopInner P((Boolean annotate));
511 void CleanupTail P((void));
512
513 ChessSquare  FIDEArray[2][BOARD_FILES] = {
514     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
515         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
516     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
517         BlackKing, BlackBishop, BlackKnight, BlackRook }
518 };
519
520 ChessSquare twoKingsArray[2][BOARD_FILES] = {
521     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
522         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
523     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
524         BlackKing, BlackKing, BlackKnight, BlackRook }
525 };
526
527 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
528     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
529         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
530     { BlackRook, BlackMan, BlackBishop, BlackQueen,
531         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
532 };
533
534 ChessSquare SpartanArray[2][BOARD_FILES] = {
535     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
536         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
537     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
538         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
539 };
540
541 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
542     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
543         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
544     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
545         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
546 };
547
548 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
549     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
550         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
551     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
552         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
553 };
554
555 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
556     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
557         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
558     { BlackRook, BlackKnight, BlackMan, BlackFerz,
559         BlackKing, BlackMan, BlackKnight, BlackRook }
560 };
561
562 ChessSquare aseanArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
563     { WhiteRook, WhiteKnight, WhiteMan, WhiteFerz,
564         WhiteKing, WhiteMan, WhiteKnight, WhiteRook },
565     { BlackRook, BlackKnight, BlackMan, BlackFerz,
566         BlackKing, BlackMan, BlackKnight, BlackRook }
567 };
568
569
570 #if (BOARD_FILES>=10)
571 ChessSquare ShogiArray[2][BOARD_FILES] = {
572     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
573         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
574     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
575         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
576 };
577
578 ChessSquare XiangqiArray[2][BOARD_FILES] = {
579     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
580         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
581     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
582         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
583 };
584
585 ChessSquare CapablancaArray[2][BOARD_FILES] = {
586     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
587         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
588     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
589         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
590 };
591
592 ChessSquare GreatArray[2][BOARD_FILES] = {
593     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
594         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
595     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
596         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
597 };
598
599 ChessSquare JanusArray[2][BOARD_FILES] = {
600     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
601         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
602     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
603         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
604 };
605
606 ChessSquare GrandArray[2][BOARD_FILES] = {
607     { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
608         WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
609     { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
610         BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
611 };
612
613 #ifdef GOTHIC
614 ChessSquare GothicArray[2][BOARD_FILES] = {
615     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
616         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
617     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
618         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
619 };
620 #else // !GOTHIC
621 #define GothicArray CapablancaArray
622 #endif // !GOTHIC
623
624 #ifdef FALCON
625 ChessSquare FalconArray[2][BOARD_FILES] = {
626     { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
627         WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
628     { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
629         BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
630 };
631 #else // !FALCON
632 #define FalconArray CapablancaArray
633 #endif // !FALCON
634
635 #else // !(BOARD_FILES>=10)
636 #define XiangqiPosition FIDEArray
637 #define CapablancaArray FIDEArray
638 #define GothicArray FIDEArray
639 #define GreatArray FIDEArray
640 #endif // !(BOARD_FILES>=10)
641
642 #if (BOARD_FILES>=12)
643 ChessSquare CourierArray[2][BOARD_FILES] = {
644     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
645         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
646     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
647         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
648 };
649 #else // !(BOARD_FILES>=12)
650 #define CourierArray CapablancaArray
651 #endif // !(BOARD_FILES>=12)
652
653
654 Board initialPosition;
655
656
657 /* Convert str to a rating. Checks for special cases of "----",
658
659    "++++", etc. Also strips ()'s */
660 int
661 string_to_rating (char *str)
662 {
663   while(*str && !isdigit(*str)) ++str;
664   if (!*str)
665     return 0;   /* One of the special "no rating" cases */
666   else
667     return atoi(str);
668 }
669
670 void
671 ClearProgramStats ()
672 {
673     /* Init programStats */
674     programStats.movelist[0] = 0;
675     programStats.depth = 0;
676     programStats.nr_moves = 0;
677     programStats.moves_left = 0;
678     programStats.nodes = 0;
679     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
680     programStats.score = 0;
681     programStats.got_only_move = 0;
682     programStats.got_fail = 0;
683     programStats.line_is_book = 0;
684 }
685
686 void
687 CommonEngineInit ()
688 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
689     if (appData.firstPlaysBlack) {
690         first.twoMachinesColor = "black\n";
691         second.twoMachinesColor = "white\n";
692     } else {
693         first.twoMachinesColor = "white\n";
694         second.twoMachinesColor = "black\n";
695     }
696
697     first.other = &second;
698     second.other = &first;
699
700     { float norm = 1;
701         if(appData.timeOddsMode) {
702             norm = appData.timeOdds[0];
703             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
704         }
705         first.timeOdds  = appData.timeOdds[0]/norm;
706         second.timeOdds = appData.timeOdds[1]/norm;
707     }
708
709     if(programVersion) free(programVersion);
710     if (appData.noChessProgram) {
711         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
712         sprintf(programVersion, "%s", PACKAGE_STRING);
713     } else {
714       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
715       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
716       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
717     }
718 }
719
720 void
721 UnloadEngine (ChessProgramState *cps)
722 {
723         /* Kill off first chess program */
724         if (cps->isr != NULL)
725           RemoveInputSource(cps->isr);
726         cps->isr = NULL;
727
728         if (cps->pr != NoProc) {
729             ExitAnalyzeMode();
730             DoSleep( appData.delayBeforeQuit );
731             SendToProgram("quit\n", cps);
732             DoSleep( appData.delayAfterQuit );
733             DestroyChildProcess(cps->pr, cps->useSigterm);
734         }
735         cps->pr = NoProc;
736         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
737 }
738
739 void
740 ClearOptions (ChessProgramState *cps)
741 {
742     int i;
743     cps->nrOptions = cps->comboCnt = 0;
744     for(i=0; i<MAX_OPTIONS; i++) {
745         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
746         cps->option[i].textValue = 0;
747     }
748 }
749
750 char *engineNames[] = {
751   /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
752      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
753 N_("first"),
754   /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
755      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
756 N_("second")
757 };
758
759 void
760 InitEngine (ChessProgramState *cps, int n)
761 {   // [HGM] all engine initialiation put in a function that does one engine
762
763     ClearOptions(cps);
764
765     cps->which = engineNames[n];
766     cps->maybeThinking = FALSE;
767     cps->pr = NoProc;
768     cps->isr = NULL;
769     cps->sendTime = 2;
770     cps->sendDrawOffers = 1;
771
772     cps->program = appData.chessProgram[n];
773     cps->host = appData.host[n];
774     cps->dir = appData.directory[n];
775     cps->initString = appData.engInitString[n];
776     cps->computerString = appData.computerString[n];
777     cps->useSigint  = TRUE;
778     cps->useSigterm = TRUE;
779     cps->reuse = appData.reuse[n];
780     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
781     cps->useSetboard = FALSE;
782     cps->useSAN = FALSE;
783     cps->usePing = FALSE;
784     cps->lastPing = 0;
785     cps->lastPong = 0;
786     cps->usePlayother = FALSE;
787     cps->useColors = TRUE;
788     cps->useUsermove = FALSE;
789     cps->sendICS = FALSE;
790     cps->sendName = appData.icsActive;
791     cps->sdKludge = FALSE;
792     cps->stKludge = FALSE;
793     if(cps->tidy == NULL) cps->tidy = (char*) malloc(MSG_SIZ);
794     TidyProgramName(cps->program, cps->host, cps->tidy);
795     cps->matchWins = 0;
796     ASSIGN(cps->variants, appData.variant);
797     cps->analysisSupport = 2; /* detect */
798     cps->analyzing = FALSE;
799     cps->initDone = FALSE;
800     cps->reload = FALSE;
801
802     /* New features added by Tord: */
803     cps->useFEN960 = FALSE;
804     cps->useOOCastle = TRUE;
805     /* End of new features added by Tord. */
806     cps->fenOverride  = appData.fenOverride[n];
807
808     /* [HGM] time odds: set factor for each machine */
809     cps->timeOdds  = appData.timeOdds[n];
810
811     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
812     cps->accumulateTC = appData.accumulateTC[n];
813     cps->maxNrOfSessions = 1;
814
815     /* [HGM] debug */
816     cps->debug = FALSE;
817
818     cps->supportsNPS = UNKNOWN;
819     cps->memSize = FALSE;
820     cps->maxCores = FALSE;
821     ASSIGN(cps->egtFormats, "");
822
823     /* [HGM] options */
824     cps->optionSettings  = appData.engOptions[n];
825
826     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
827     cps->isUCI = appData.isUCI[n]; /* [AS] */
828     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
829     cps->highlight = 0;
830
831     if (appData.protocolVersion[n] > PROTOVER
832         || appData.protocolVersion[n] < 1)
833       {
834         char buf[MSG_SIZ];
835         int len;
836
837         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
838                        appData.protocolVersion[n]);
839         if( (len >= MSG_SIZ) && appData.debugMode )
840           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
841
842         DisplayFatalError(buf, 0, 2);
843       }
844     else
845       {
846         cps->protocolVersion = appData.protocolVersion[n];
847       }
848
849     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
850     ParseFeatures(appData.featureDefaults, cps);
851 }
852
853 ChessProgramState *savCps;
854
855 GameMode oldMode;
856
857 void
858 LoadEngine ()
859 {
860     int i;
861     if(WaitForEngine(savCps, LoadEngine)) return;
862     CommonEngineInit(); // recalculate time odds
863     if(gameInfo.variant != StringToVariant(appData.variant)) {
864         // we changed variant when loading the engine; this forces us to reset
865         Reset(TRUE, savCps != &first);
866         oldMode = BeginningOfGame; // to prevent restoring old mode
867     }
868     InitChessProgram(savCps, FALSE);
869     if(gameMode == EditGame) SendToProgram("force\n", savCps); // in EditGame mode engine must be in force mode
870     DisplayMessage("", "");
871     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
872     for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
873     ThawUI();
874     SetGNUMode();
875     if(oldMode == AnalyzeMode) AnalyzeModeEvent();
876 }
877
878 void
879 ReplaceEngine (ChessProgramState *cps, int n)
880 {
881     oldMode = gameMode; // remember mode, so it can be restored after loading sequence is complete
882     keepInfo = 1;
883     if(oldMode != BeginningOfGame) EditGameEvent();
884     keepInfo = 0;
885     UnloadEngine(cps);
886     appData.noChessProgram = FALSE;
887     appData.clockMode = TRUE;
888     InitEngine(cps, n);
889     UpdateLogos(TRUE);
890     if(n) return; // only startup first engine immediately; second can wait
891     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
892     LoadEngine();
893 }
894
895 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
896 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
897
898 static char resetOptions[] =
899         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
900         "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
901         "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 "
902         "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
903
904 void
905 FloatToFront(char **list, char *engineLine)
906 {
907     char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
908     int i=0;
909     if(appData.recentEngines <= 0) return;
910     TidyProgramName(engineLine, "localhost", tidy+1);
911     tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
912     strncpy(buf+1, *list, MSG_SIZ-50);
913     if(p = strstr(buf, tidy)) { // tidy name appears in list
914         q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
915         while(*p++ = *++q); // squeeze out
916     }
917     strcat(tidy, buf+1); // put list behind tidy name
918     p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
919     if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
920     ASSIGN(*list, tidy+1);
921 }
922
923 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
924
925 void
926 Load (ChessProgramState *cps, int i)
927 {
928     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ];
929     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
930         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
931         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
932         ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
933         FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
934         appData.firstProtocolVersion = PROTOVER;
935         ParseArgsFromString(buf);
936         SwapEngines(i);
937         ReplaceEngine(cps, i);
938         FloatToFront(&appData.recentEngineList, engineLine);
939         return;
940     }
941     p = engineName;
942     while(q = strchr(p, SLASH)) p = q+1;
943     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
944     if(engineDir[0] != NULLCHAR) {
945         ASSIGN(appData.directory[i], engineDir); p = engineName;
946     } else if(p != engineName) { // derive directory from engine path, when not given
947         p[-1] = 0;
948         ASSIGN(appData.directory[i], engineName);
949         p[-1] = SLASH;
950         if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
951     } else { ASSIGN(appData.directory[i], "."); }
952     if(params[0]) {
953         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
954         snprintf(command, MSG_SIZ, "%s %s", p, params);
955         p = command;
956     }
957     ASSIGN(appData.chessProgram[i], p);
958     appData.isUCI[i] = isUCI;
959     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
960     appData.hasOwnBookUCI[i] = hasBook;
961     if(!nickName[0]) useNick = FALSE;
962     if(useNick) ASSIGN(appData.pgnName[i], nickName);
963     if(addToList) {
964         int len;
965         char quote;
966         q = firstChessProgramNames;
967         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
968         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
969         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
970                         quote, p, quote, appData.directory[i],
971                         useNick ? " -fn \"" : "",
972                         useNick ? nickName : "",
973                         useNick ? "\"" : "",
974                         v1 ? " -firstProtocolVersion 1" : "",
975                         hasBook ? "" : " -fNoOwnBookUCI",
976                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
977                         storeVariant ? " -variant " : "",
978                         storeVariant ? VariantName(gameInfo.variant) : "");
979         if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
980         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
981         if(insert != q) insert[-1] = NULLCHAR;
982         snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
983         if(q)   free(q);
984         FloatToFront(&appData.recentEngineList, buf);
985     }
986     ReplaceEngine(cps, i);
987 }
988
989 void
990 InitTimeControls ()
991 {
992     int matched, min, sec;
993     /*
994      * Parse timeControl resource
995      */
996     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
997                           appData.movesPerSession)) {
998         char buf[MSG_SIZ];
999         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
1000         DisplayFatalError(buf, 0, 2);
1001     }
1002
1003     /*
1004      * Parse searchTime resource
1005      */
1006     if (*appData.searchTime != NULLCHAR) {
1007         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
1008         if (matched == 1) {
1009             searchTime = min * 60;
1010         } else if (matched == 2) {
1011             searchTime = min * 60 + sec;
1012         } else {
1013             char buf[MSG_SIZ];
1014             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
1015             DisplayFatalError(buf, 0, 2);
1016         }
1017     }
1018 }
1019
1020 void
1021 InitBackEnd1 ()
1022 {
1023
1024     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1025     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1026
1027     GetTimeMark(&programStartTime);
1028     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1029     appData.seedBase = random() + (random()<<15);
1030     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1031
1032     ClearProgramStats();
1033     programStats.ok_to_send = 1;
1034     programStats.seen_stat = 0;
1035
1036     /*
1037      * Initialize game list
1038      */
1039     ListNew(&gameList);
1040
1041
1042     /*
1043      * Internet chess server status
1044      */
1045     if (appData.icsActive) {
1046         appData.matchMode = FALSE;
1047         appData.matchGames = 0;
1048 #if ZIPPY
1049         appData.noChessProgram = !appData.zippyPlay;
1050 #else
1051         appData.zippyPlay = FALSE;
1052         appData.zippyTalk = FALSE;
1053         appData.noChessProgram = TRUE;
1054 #endif
1055         if (*appData.icsHelper != NULLCHAR) {
1056             appData.useTelnet = TRUE;
1057             appData.telnetProgram = appData.icsHelper;
1058         }
1059     } else {
1060         appData.zippyTalk = appData.zippyPlay = FALSE;
1061     }
1062
1063     /* [AS] Initialize pv info list [HGM] and game state */
1064     {
1065         int i, j;
1066
1067         for( i=0; i<=framePtr; i++ ) {
1068             pvInfoList[i].depth = -1;
1069             boards[i][EP_STATUS] = EP_NONE;
1070             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1071         }
1072     }
1073
1074     InitTimeControls();
1075
1076     /* [AS] Adjudication threshold */
1077     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1078
1079     InitEngine(&first, 0);
1080     InitEngine(&second, 1);
1081     CommonEngineInit();
1082
1083     pairing.which = "pairing"; // pairing engine
1084     pairing.pr = NoProc;
1085     pairing.isr = NULL;
1086     pairing.program = appData.pairingEngine;
1087     pairing.host = "localhost";
1088     pairing.dir = ".";
1089
1090     if (appData.icsActive) {
1091         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1092     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1093         appData.clockMode = FALSE;
1094         first.sendTime = second.sendTime = 0;
1095     }
1096
1097 #if ZIPPY
1098     /* Override some settings from environment variables, for backward
1099        compatibility.  Unfortunately it's not feasible to have the env
1100        vars just set defaults, at least in xboard.  Ugh.
1101     */
1102     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1103       ZippyInit();
1104     }
1105 #endif
1106
1107     if (!appData.icsActive) {
1108       char buf[MSG_SIZ];
1109       int len;
1110
1111       /* Check for variants that are supported only in ICS mode,
1112          or not at all.  Some that are accepted here nevertheless
1113          have bugs; see comments below.
1114       */
1115       VariantClass variant = StringToVariant(appData.variant);
1116       switch (variant) {
1117       case VariantBughouse:     /* need four players and two boards */
1118       case VariantKriegspiel:   /* need to hide pieces and move details */
1119         /* case VariantFischeRandom: (Fabien: moved below) */
1120         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1121         if( (len >= MSG_SIZ) && appData.debugMode )
1122           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1123
1124         DisplayFatalError(buf, 0, 2);
1125         return;
1126
1127       case VariantUnknown:
1128       case VariantLoadable:
1129       case Variant29:
1130       case Variant30:
1131       case Variant31:
1132       case Variant32:
1133       case Variant33:
1134       case Variant34:
1135       case Variant35:
1136       case Variant36:
1137       default:
1138         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1139         if( (len >= MSG_SIZ) && appData.debugMode )
1140           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1141
1142         DisplayFatalError(buf, 0, 2);
1143         return;
1144
1145       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1146       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1147       case VariantGothic:     /* [HGM] should work */
1148       case VariantCapablanca: /* [HGM] should work */
1149       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1150       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1151       case VariantKnightmate: /* [HGM] should work */
1152       case VariantCylinder:   /* [HGM] untested */
1153       case VariantFalcon:     /* [HGM] untested */
1154       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1155                                  offboard interposition not understood */
1156       case VariantNormal:     /* definitely works! */
1157       case VariantWildCastle: /* pieces not automatically shuffled */
1158       case VariantNoCastle:   /* pieces not automatically shuffled */
1159       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1160       case VariantLosers:     /* should work except for win condition,
1161                                  and doesn't know captures are mandatory */
1162       case VariantSuicide:    /* should work except for win condition,
1163                                  and doesn't know captures are mandatory */
1164       case VariantGiveaway:   /* should work except for win condition,
1165                                  and doesn't know captures are mandatory */
1166       case VariantTwoKings:   /* should work */
1167       case VariantAtomic:     /* should work except for win condition */
1168       case Variant3Check:     /* should work except for win condition */
1169       case VariantShatranj:   /* should work except for all win conditions */
1170       case VariantMakruk:     /* should work except for draw countdown */
1171       case VariantASEAN :     /* should work except for draw countdown */
1172       case VariantBerolina:   /* might work if TestLegality is off */
1173       case VariantCapaRandom: /* should work */
1174       case VariantJanus:      /* should work */
1175       case VariantSuper:      /* experimental */
1176       case VariantGreat:      /* experimental, requires legality testing to be off */
1177       case VariantSChess:     /* S-Chess, should work */
1178       case VariantGrand:      /* should work */
1179       case VariantSpartan:    /* should work */
1180         break;
1181       }
1182     }
1183
1184 }
1185
1186 int
1187 NextIntegerFromString (char ** str, long * value)
1188 {
1189     int result = -1;
1190     char * s = *str;
1191
1192     while( *s == ' ' || *s == '\t' ) {
1193         s++;
1194     }
1195
1196     *value = 0;
1197
1198     if( *s >= '0' && *s <= '9' ) {
1199         while( *s >= '0' && *s <= '9' ) {
1200             *value = *value * 10 + (*s - '0');
1201             s++;
1202         }
1203
1204         result = 0;
1205     }
1206
1207     *str = s;
1208
1209     return result;
1210 }
1211
1212 int
1213 NextTimeControlFromString (char ** str, long * value)
1214 {
1215     long temp;
1216     int result = NextIntegerFromString( str, &temp );
1217
1218     if( result == 0 ) {
1219         *value = temp * 60; /* Minutes */
1220         if( **str == ':' ) {
1221             (*str)++;
1222             result = NextIntegerFromString( str, &temp );
1223             *value += temp; /* Seconds */
1224         }
1225     }
1226
1227     return result;
1228 }
1229
1230 int
1231 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1232 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1233     int result = -1, type = 0; long temp, temp2;
1234
1235     if(**str != ':') return -1; // old params remain in force!
1236     (*str)++;
1237     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1238     if( NextIntegerFromString( str, &temp ) ) return -1;
1239     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1240
1241     if(**str != '/') {
1242         /* time only: incremental or sudden-death time control */
1243         if(**str == '+') { /* increment follows; read it */
1244             (*str)++;
1245             if(**str == '!') type = *(*str)++; // Bronstein TC
1246             if(result = NextIntegerFromString( str, &temp2)) return -1;
1247             *inc = temp2 * 1000;
1248             if(**str == '.') { // read fraction of increment
1249                 char *start = ++(*str);
1250                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1251                 temp2 *= 1000;
1252                 while(start++ < *str) temp2 /= 10;
1253                 *inc += temp2;
1254             }
1255         } else *inc = 0;
1256         *moves = 0; *tc = temp * 1000; *incType = type;
1257         return 0;
1258     }
1259
1260     (*str)++; /* classical time control */
1261     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1262
1263     if(result == 0) {
1264         *moves = temp;
1265         *tc    = temp2 * 1000;
1266         *inc   = 0;
1267         *incType = type;
1268     }
1269     return result;
1270 }
1271
1272 int
1273 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1274 {   /* [HGM] get time to add from the multi-session time-control string */
1275     int incType, moves=1; /* kludge to force reading of first session */
1276     long time, increment;
1277     char *s = tcString;
1278
1279     if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1280     do {
1281         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1282         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1283         if(movenr == -1) return time;    /* last move before new session     */
1284         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1285         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1286         if(!moves) return increment;     /* current session is incremental   */
1287         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1288     } while(movenr >= -1);               /* try again for next session       */
1289
1290     return 0; // no new time quota on this move
1291 }
1292
1293 int
1294 ParseTimeControl (char *tc, float ti, int mps)
1295 {
1296   long tc1;
1297   long tc2;
1298   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1299   int min, sec=0;
1300
1301   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1302   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1303       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1304   if(ti > 0) {
1305
1306     if(mps)
1307       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1308     else
1309       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1310   } else {
1311     if(mps)
1312       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1313     else
1314       snprintf(buf, MSG_SIZ, ":%s", mytc);
1315   }
1316   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1317
1318   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1319     return FALSE;
1320   }
1321
1322   if( *tc == '/' ) {
1323     /* Parse second time control */
1324     tc++;
1325
1326     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1327       return FALSE;
1328     }
1329
1330     if( tc2 == 0 ) {
1331       return FALSE;
1332     }
1333
1334     timeControl_2 = tc2 * 1000;
1335   }
1336   else {
1337     timeControl_2 = 0;
1338   }
1339
1340   if( tc1 == 0 ) {
1341     return FALSE;
1342   }
1343
1344   timeControl = tc1 * 1000;
1345
1346   if (ti >= 0) {
1347     timeIncrement = ti * 1000;  /* convert to ms */
1348     movesPerSession = 0;
1349   } else {
1350     timeIncrement = 0;
1351     movesPerSession = mps;
1352   }
1353   return TRUE;
1354 }
1355
1356 void
1357 InitBackEnd2 ()
1358 {
1359     if (appData.debugMode) {
1360 #    ifdef __GIT_VERSION
1361       fprintf(debugFP, "Version: %s (%s)\n", programVersion, __GIT_VERSION);
1362 #    else
1363       fprintf(debugFP, "Version: %s\n", programVersion);
1364 #    endif
1365     }
1366     ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1367
1368     set_cont_sequence(appData.wrapContSeq);
1369     if (appData.matchGames > 0) {
1370         appData.matchMode = TRUE;
1371     } else if (appData.matchMode) {
1372         appData.matchGames = 1;
1373     }
1374     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1375         appData.matchGames = appData.sameColorGames;
1376     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1377         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1378         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1379     }
1380     Reset(TRUE, FALSE);
1381     if (appData.noChessProgram || first.protocolVersion == 1) {
1382       InitBackEnd3();
1383     } else {
1384       /* kludge: allow timeout for initial "feature" commands */
1385       FreezeUI();
1386       DisplayMessage("", _("Starting chess program"));
1387       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1388     }
1389 }
1390
1391 int
1392 CalculateIndex (int index, int gameNr)
1393 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1394     int res;
1395     if(index > 0) return index; // fixed nmber
1396     if(index == 0) return 1;
1397     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1398     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1399     return res;
1400 }
1401
1402 int
1403 LoadGameOrPosition (int gameNr)
1404 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1405     if (*appData.loadGameFile != NULLCHAR) {
1406         if (!LoadGameFromFile(appData.loadGameFile,
1407                 CalculateIndex(appData.loadGameIndex, gameNr),
1408                               appData.loadGameFile, FALSE)) {
1409             DisplayFatalError(_("Bad game file"), 0, 1);
1410             return 0;
1411         }
1412     } else if (*appData.loadPositionFile != NULLCHAR) {
1413         if (!LoadPositionFromFile(appData.loadPositionFile,
1414                 CalculateIndex(appData.loadPositionIndex, gameNr),
1415                                   appData.loadPositionFile)) {
1416             DisplayFatalError(_("Bad position file"), 0, 1);
1417             return 0;
1418         }
1419     }
1420     return 1;
1421 }
1422
1423 void
1424 ReserveGame (int gameNr, char resChar)
1425 {
1426     FILE *tf = fopen(appData.tourneyFile, "r+");
1427     char *p, *q, c, buf[MSG_SIZ];
1428     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1429     safeStrCpy(buf, lastMsg, MSG_SIZ);
1430     DisplayMessage(_("Pick new game"), "");
1431     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1432     ParseArgsFromFile(tf);
1433     p = q = appData.results;
1434     if(appData.debugMode) {
1435       char *r = appData.participants;
1436       fprintf(debugFP, "results = '%s'\n", p);
1437       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1438       fprintf(debugFP, "\n");
1439     }
1440     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1441     nextGame = q - p;
1442     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1443     safeStrCpy(q, p, strlen(p) + 2);
1444     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1445     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1446     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1447         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1448         q[nextGame] = '*';
1449     }
1450     fseek(tf, -(strlen(p)+4), SEEK_END);
1451     c = fgetc(tf);
1452     if(c != '"') // depending on DOS or Unix line endings we can be one off
1453          fseek(tf, -(strlen(p)+2), SEEK_END);
1454     else fseek(tf, -(strlen(p)+3), SEEK_END);
1455     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1456     DisplayMessage(buf, "");
1457     free(p); appData.results = q;
1458     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1459        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1460       int round = appData.defaultMatchGames * appData.tourneyType;
1461       if(gameNr < 0 || appData.tourneyType < 1 ||  // gauntlet engine can always stay loaded as first engine
1462          appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1463         UnloadEngine(&first);  // next game belongs to other pairing;
1464         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1465     }
1466     if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1467 }
1468
1469 void
1470 MatchEvent (int mode)
1471 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1472         int dummy;
1473         if(matchMode) { // already in match mode: switch it off
1474             abortMatch = TRUE;
1475             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1476             return;
1477         }
1478 //      if(gameMode != BeginningOfGame) {
1479 //          DisplayError(_("You can only start a match from the initial position."), 0);
1480 //          return;
1481 //      }
1482         abortMatch = FALSE;
1483         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1484         /* Set up machine vs. machine match */
1485         nextGame = 0;
1486         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1487         if(appData.tourneyFile[0]) {
1488             ReserveGame(-1, 0);
1489             if(nextGame > appData.matchGames) {
1490                 char buf[MSG_SIZ];
1491                 if(strchr(appData.results, '*') == NULL) {
1492                     FILE *f;
1493                     appData.tourneyCycles++;
1494                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1495                         fclose(f);
1496                         NextTourneyGame(-1, &dummy);
1497                         ReserveGame(-1, 0);
1498                         if(nextGame <= appData.matchGames) {
1499                             DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1500                             matchMode = mode;
1501                             ScheduleDelayedEvent(NextMatchGame, 10000);
1502                             return;
1503                         }
1504                     }
1505                 }
1506                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1507                 DisplayError(buf, 0);
1508                 appData.tourneyFile[0] = 0;
1509                 return;
1510             }
1511         } else
1512         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1513             DisplayFatalError(_("Can't have a match with no chess programs"),
1514                               0, 2);
1515             return;
1516         }
1517         matchMode = mode;
1518         matchGame = roundNr = 1;
1519         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1520         NextMatchGame();
1521 }
1522
1523 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1524
1525 void
1526 InitBackEnd3 P((void))
1527 {
1528     GameMode initialMode;
1529     char buf[MSG_SIZ];
1530     int err, len;
1531
1532     InitChessProgram(&first, startedFromSetupPosition);
1533
1534     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1535         free(programVersion);
1536         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1537         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1538         FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1539     }
1540
1541     if (appData.icsActive) {
1542 #ifdef WIN32
1543         /* [DM] Make a console window if needed [HGM] merged ifs */
1544         ConsoleCreate();
1545 #endif
1546         err = establish();
1547         if (err != 0)
1548           {
1549             if (*appData.icsCommPort != NULLCHAR)
1550               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1551                              appData.icsCommPort);
1552             else
1553               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1554                         appData.icsHost, appData.icsPort);
1555
1556             if( (len >= MSG_SIZ) && appData.debugMode )
1557               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1558
1559             DisplayFatalError(buf, err, 1);
1560             return;
1561         }
1562         SetICSMode();
1563         telnetISR =
1564           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1565         fromUserISR =
1566           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1567         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1568             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1569     } else if (appData.noChessProgram) {
1570         SetNCPMode();
1571     } else {
1572         SetGNUMode();
1573     }
1574
1575     if (*appData.cmailGameName != NULLCHAR) {
1576         SetCmailMode();
1577         OpenLoopback(&cmailPR);
1578         cmailISR =
1579           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1580     }
1581
1582     ThawUI();
1583     DisplayMessage("", "");
1584     if (StrCaseCmp(appData.initialMode, "") == 0) {
1585       initialMode = BeginningOfGame;
1586       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1587         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1588         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1589         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1590         ModeHighlight();
1591       }
1592     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1593       initialMode = TwoMachinesPlay;
1594     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1595       initialMode = AnalyzeFile;
1596     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1597       initialMode = AnalyzeMode;
1598     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1599       initialMode = MachinePlaysWhite;
1600     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1601       initialMode = MachinePlaysBlack;
1602     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1603       initialMode = EditGame;
1604     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1605       initialMode = EditPosition;
1606     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1607       initialMode = Training;
1608     } else {
1609       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1610       if( (len >= MSG_SIZ) && appData.debugMode )
1611         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1612
1613       DisplayFatalError(buf, 0, 2);
1614       return;
1615     }
1616
1617     if (appData.matchMode) {
1618         if(appData.tourneyFile[0]) { // start tourney from command line
1619             FILE *f;
1620             if(f = fopen(appData.tourneyFile, "r")) {
1621                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1622                 fclose(f);
1623                 appData.clockMode = TRUE;
1624                 SetGNUMode();
1625             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1626         }
1627         MatchEvent(TRUE);
1628     } else if (*appData.cmailGameName != NULLCHAR) {
1629         /* Set up cmail mode */
1630         ReloadCmailMsgEvent(TRUE);
1631     } else {
1632         /* Set up other modes */
1633         if (initialMode == AnalyzeFile) {
1634           if (*appData.loadGameFile == NULLCHAR) {
1635             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1636             return;
1637           }
1638         }
1639         if (*appData.loadGameFile != NULLCHAR) {
1640             (void) LoadGameFromFile(appData.loadGameFile,
1641                                     appData.loadGameIndex,
1642                                     appData.loadGameFile, TRUE);
1643         } else if (*appData.loadPositionFile != NULLCHAR) {
1644             (void) LoadPositionFromFile(appData.loadPositionFile,
1645                                         appData.loadPositionIndex,
1646                                         appData.loadPositionFile);
1647             /* [HGM] try to make self-starting even after FEN load */
1648             /* to allow automatic setup of fairy variants with wtm */
1649             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1650                 gameMode = BeginningOfGame;
1651                 setboardSpoiledMachineBlack = 1;
1652             }
1653             /* [HGM] loadPos: make that every new game uses the setup */
1654             /* from file as long as we do not switch variant          */
1655             if(!blackPlaysFirst) {
1656                 startedFromPositionFile = TRUE;
1657                 CopyBoard(filePosition, boards[0]);
1658             }
1659         }
1660         if (initialMode == AnalyzeMode) {
1661           if (appData.noChessProgram) {
1662             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1663             return;
1664           }
1665           if (appData.icsActive) {
1666             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1667             return;
1668           }
1669           AnalyzeModeEvent();
1670         } else if (initialMode == AnalyzeFile) {
1671           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1672           ShowThinkingEvent();
1673           AnalyzeFileEvent();
1674           AnalysisPeriodicEvent(1);
1675         } else if (initialMode == MachinePlaysWhite) {
1676           if (appData.noChessProgram) {
1677             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1678                               0, 2);
1679             return;
1680           }
1681           if (appData.icsActive) {
1682             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1683                               0, 2);
1684             return;
1685           }
1686           MachineWhiteEvent();
1687         } else if (initialMode == MachinePlaysBlack) {
1688           if (appData.noChessProgram) {
1689             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1690                               0, 2);
1691             return;
1692           }
1693           if (appData.icsActive) {
1694             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1695                               0, 2);
1696             return;
1697           }
1698           MachineBlackEvent();
1699         } else if (initialMode == TwoMachinesPlay) {
1700           if (appData.noChessProgram) {
1701             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1702                               0, 2);
1703             return;
1704           }
1705           if (appData.icsActive) {
1706             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1707                               0, 2);
1708             return;
1709           }
1710           TwoMachinesEvent();
1711         } else if (initialMode == EditGame) {
1712           EditGameEvent();
1713         } else if (initialMode == EditPosition) {
1714           EditPositionEvent();
1715         } else if (initialMode == Training) {
1716           if (*appData.loadGameFile == NULLCHAR) {
1717             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1718             return;
1719           }
1720           TrainingEvent();
1721         }
1722     }
1723 }
1724
1725 void
1726 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1727 {
1728     DisplayBook(current+1);
1729
1730     MoveHistorySet( movelist, first, last, current, pvInfoList );
1731
1732     EvalGraphSet( first, last, current, pvInfoList );
1733
1734     MakeEngineOutputTitle();
1735 }
1736
1737 /*
1738  * Establish will establish a contact to a remote host.port.
1739  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1740  *  used to talk to the host.
1741  * Returns 0 if okay, error code if not.
1742  */
1743 int
1744 establish ()
1745 {
1746     char buf[MSG_SIZ];
1747
1748     if (*appData.icsCommPort != NULLCHAR) {
1749         /* Talk to the host through a serial comm port */
1750         return OpenCommPort(appData.icsCommPort, &icsPR);
1751
1752     } else if (*appData.gateway != NULLCHAR) {
1753         if (*appData.remoteShell == NULLCHAR) {
1754             /* Use the rcmd protocol to run telnet program on a gateway host */
1755             snprintf(buf, sizeof(buf), "%s %s %s",
1756                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1757             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1758
1759         } else {
1760             /* Use the rsh program to run telnet program on a gateway host */
1761             if (*appData.remoteUser == NULLCHAR) {
1762                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1763                         appData.gateway, appData.telnetProgram,
1764                         appData.icsHost, appData.icsPort);
1765             } else {
1766                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1767                         appData.remoteShell, appData.gateway,
1768                         appData.remoteUser, appData.telnetProgram,
1769                         appData.icsHost, appData.icsPort);
1770             }
1771             return StartChildProcess(buf, "", &icsPR);
1772
1773         }
1774     } else if (appData.useTelnet) {
1775         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1776
1777     } else {
1778         /* TCP socket interface differs somewhat between
1779            Unix and NT; handle details in the front end.
1780            */
1781         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1782     }
1783 }
1784
1785 void
1786 EscapeExpand (char *p, char *q)
1787 {       // [HGM] initstring: routine to shape up string arguments
1788         while(*p++ = *q++) if(p[-1] == '\\')
1789             switch(*q++) {
1790                 case 'n': p[-1] = '\n'; break;
1791                 case 'r': p[-1] = '\r'; break;
1792                 case 't': p[-1] = '\t'; break;
1793                 case '\\': p[-1] = '\\'; break;
1794                 case 0: *p = 0; return;
1795                 default: p[-1] = q[-1]; break;
1796             }
1797 }
1798
1799 void
1800 show_bytes (FILE *fp, char *buf, int count)
1801 {
1802     while (count--) {
1803         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1804             fprintf(fp, "\\%03o", *buf & 0xff);
1805         } else {
1806             putc(*buf, fp);
1807         }
1808         buf++;
1809     }
1810     fflush(fp);
1811 }
1812
1813 /* Returns an errno value */
1814 int
1815 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1816 {
1817     char buf[8192], *p, *q, *buflim;
1818     int left, newcount, outcount;
1819
1820     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1821         *appData.gateway != NULLCHAR) {
1822         if (appData.debugMode) {
1823             fprintf(debugFP, ">ICS: ");
1824             show_bytes(debugFP, message, count);
1825             fprintf(debugFP, "\n");
1826         }
1827         return OutputToProcess(pr, message, count, outError);
1828     }
1829
1830     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1831     p = message;
1832     q = buf;
1833     left = count;
1834     newcount = 0;
1835     while (left) {
1836         if (q >= buflim) {
1837             if (appData.debugMode) {
1838                 fprintf(debugFP, ">ICS: ");
1839                 show_bytes(debugFP, buf, newcount);
1840                 fprintf(debugFP, "\n");
1841             }
1842             outcount = OutputToProcess(pr, buf, newcount, outError);
1843             if (outcount < newcount) return -1; /* to be sure */
1844             q = buf;
1845             newcount = 0;
1846         }
1847         if (*p == '\n') {
1848             *q++ = '\r';
1849             newcount++;
1850         } else if (((unsigned char) *p) == TN_IAC) {
1851             *q++ = (char) TN_IAC;
1852             newcount ++;
1853         }
1854         *q++ = *p++;
1855         newcount++;
1856         left--;
1857     }
1858     if (appData.debugMode) {
1859         fprintf(debugFP, ">ICS: ");
1860         show_bytes(debugFP, buf, newcount);
1861         fprintf(debugFP, "\n");
1862     }
1863     outcount = OutputToProcess(pr, buf, newcount, outError);
1864     if (outcount < newcount) return -1; /* to be sure */
1865     return count;
1866 }
1867
1868 void
1869 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1870 {
1871     int outError, outCount;
1872     static int gotEof = 0;
1873     static FILE *ini;
1874
1875     /* Pass data read from player on to ICS */
1876     if (count > 0) {
1877         gotEof = 0;
1878         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1879         if (outCount < count) {
1880             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1881         }
1882         if(have_sent_ICS_logon == 2) {
1883           if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1884             fprintf(ini, "%s", message);
1885             have_sent_ICS_logon = 3;
1886           } else
1887             have_sent_ICS_logon = 1;
1888         } else if(have_sent_ICS_logon == 3) {
1889             fprintf(ini, "%s", message);
1890             fclose(ini);
1891           have_sent_ICS_logon = 1;
1892         }
1893     } else if (count < 0) {
1894         RemoveInputSource(isr);
1895         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1896     } else if (gotEof++ > 0) {
1897         RemoveInputSource(isr);
1898         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1899     }
1900 }
1901
1902 void
1903 KeepAlive ()
1904 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1905     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1906     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1907     SendToICS("date\n");
1908     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1909 }
1910
1911 /* added routine for printf style output to ics */
1912 void
1913 ics_printf (char *format, ...)
1914 {
1915     char buffer[MSG_SIZ];
1916     va_list args;
1917
1918     va_start(args, format);
1919     vsnprintf(buffer, sizeof(buffer), format, args);
1920     buffer[sizeof(buffer)-1] = '\0';
1921     SendToICS(buffer);
1922     va_end(args);
1923 }
1924
1925 void
1926 SendToICS (char *s)
1927 {
1928     int count, outCount, outError;
1929
1930     if (icsPR == NoProc) return;
1931
1932     count = strlen(s);
1933     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1934     if (outCount < count) {
1935         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1936     }
1937 }
1938
1939 /* This is used for sending logon scripts to the ICS. Sending
1940    without a delay causes problems when using timestamp on ICC
1941    (at least on my machine). */
1942 void
1943 SendToICSDelayed (char *s, long msdelay)
1944 {
1945     int count, outCount, outError;
1946
1947     if (icsPR == NoProc) return;
1948
1949     count = strlen(s);
1950     if (appData.debugMode) {
1951         fprintf(debugFP, ">ICS: ");
1952         show_bytes(debugFP, s, count);
1953         fprintf(debugFP, "\n");
1954     }
1955     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1956                                       msdelay);
1957     if (outCount < count) {
1958         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1959     }
1960 }
1961
1962
1963 /* Remove all highlighting escape sequences in s
1964    Also deletes any suffix starting with '('
1965    */
1966 char *
1967 StripHighlightAndTitle (char *s)
1968 {
1969     static char retbuf[MSG_SIZ];
1970     char *p = retbuf;
1971
1972     while (*s != NULLCHAR) {
1973         while (*s == '\033') {
1974             while (*s != NULLCHAR && !isalpha(*s)) s++;
1975             if (*s != NULLCHAR) s++;
1976         }
1977         while (*s != NULLCHAR && *s != '\033') {
1978             if (*s == '(' || *s == '[') {
1979                 *p = NULLCHAR;
1980                 return retbuf;
1981             }
1982             *p++ = *s++;
1983         }
1984     }
1985     *p = NULLCHAR;
1986     return retbuf;
1987 }
1988
1989 /* Remove all highlighting escape sequences in s */
1990 char *
1991 StripHighlight (char *s)
1992 {
1993     static char retbuf[MSG_SIZ];
1994     char *p = retbuf;
1995
1996     while (*s != NULLCHAR) {
1997         while (*s == '\033') {
1998             while (*s != NULLCHAR && !isalpha(*s)) s++;
1999             if (*s != NULLCHAR) s++;
2000         }
2001         while (*s != NULLCHAR && *s != '\033') {
2002             *p++ = *s++;
2003         }
2004     }
2005     *p = NULLCHAR;
2006     return retbuf;
2007 }
2008
2009 char engineVariant[MSG_SIZ];
2010 char *variantNames[] = VARIANT_NAMES;
2011 char *
2012 VariantName (VariantClass v)
2013 {
2014     if(v == VariantUnknown) return engineVariant;
2015     return variantNames[v];
2016 }
2017
2018
2019 /* Identify a variant from the strings the chess servers use or the
2020    PGN Variant tag names we use. */
2021 VariantClass
2022 StringToVariant (char *e)
2023 {
2024     char *p;
2025     int wnum = -1;
2026     VariantClass v = VariantNormal;
2027     int i, found = FALSE;
2028     char buf[MSG_SIZ];
2029     int len;
2030
2031     if (!e) return v;
2032
2033     /* [HGM] skip over optional board-size prefixes */
2034     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
2035         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
2036         while( *e++ != '_');
2037     }
2038
2039     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2040         v = VariantNormal;
2041         found = TRUE;
2042     } else
2043     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2044       if (StrCaseStr(e, variantNames[i])) {
2045         v = (VariantClass) i;
2046         found = TRUE;
2047         break;
2048       }
2049     }
2050
2051     if (!found) {
2052       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2053           || StrCaseStr(e, "wild/fr")
2054           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2055         v = VariantFischeRandom;
2056       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2057                  (i = 1, p = StrCaseStr(e, "w"))) {
2058         p += i;
2059         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2060         if (isdigit(*p)) {
2061           wnum = atoi(p);
2062         } else {
2063           wnum = -1;
2064         }
2065         switch (wnum) {
2066         case 0: /* FICS only, actually */
2067         case 1:
2068           /* Castling legal even if K starts on d-file */
2069           v = VariantWildCastle;
2070           break;
2071         case 2:
2072         case 3:
2073         case 4:
2074           /* Castling illegal even if K & R happen to start in
2075              normal positions. */
2076           v = VariantNoCastle;
2077           break;
2078         case 5:
2079         case 7:
2080         case 8:
2081         case 10:
2082         case 11:
2083         case 12:
2084         case 13:
2085         case 14:
2086         case 15:
2087         case 18:
2088         case 19:
2089           /* Castling legal iff K & R start in normal positions */
2090           v = VariantNormal;
2091           break;
2092         case 6:
2093         case 20:
2094         case 21:
2095           /* Special wilds for position setup; unclear what to do here */
2096           v = VariantLoadable;
2097           break;
2098         case 9:
2099           /* Bizarre ICC game */
2100           v = VariantTwoKings;
2101           break;
2102         case 16:
2103           v = VariantKriegspiel;
2104           break;
2105         case 17:
2106           v = VariantLosers;
2107           break;
2108         case 22:
2109           v = VariantFischeRandom;
2110           break;
2111         case 23:
2112           v = VariantCrazyhouse;
2113           break;
2114         case 24:
2115           v = VariantBughouse;
2116           break;
2117         case 25:
2118           v = Variant3Check;
2119           break;
2120         case 26:
2121           /* Not quite the same as FICS suicide! */
2122           v = VariantGiveaway;
2123           break;
2124         case 27:
2125           v = VariantAtomic;
2126           break;
2127         case 28:
2128           v = VariantShatranj;
2129           break;
2130
2131         /* Temporary names for future ICC types.  The name *will* change in
2132            the next xboard/WinBoard release after ICC defines it. */
2133         case 29:
2134           v = Variant29;
2135           break;
2136         case 30:
2137           v = Variant30;
2138           break;
2139         case 31:
2140           v = Variant31;
2141           break;
2142         case 32:
2143           v = Variant32;
2144           break;
2145         case 33:
2146           v = Variant33;
2147           break;
2148         case 34:
2149           v = Variant34;
2150           break;
2151         case 35:
2152           v = Variant35;
2153           break;
2154         case 36:
2155           v = Variant36;
2156           break;
2157         case 37:
2158           v = VariantShogi;
2159           break;
2160         case 38:
2161           v = VariantXiangqi;
2162           break;
2163         case 39:
2164           v = VariantCourier;
2165           break;
2166         case 40:
2167           v = VariantGothic;
2168           break;
2169         case 41:
2170           v = VariantCapablanca;
2171           break;
2172         case 42:
2173           v = VariantKnightmate;
2174           break;
2175         case 43:
2176           v = VariantFairy;
2177           break;
2178         case 44:
2179           v = VariantCylinder;
2180           break;
2181         case 45:
2182           v = VariantFalcon;
2183           break;
2184         case 46:
2185           v = VariantCapaRandom;
2186           break;
2187         case 47:
2188           v = VariantBerolina;
2189           break;
2190         case 48:
2191           v = VariantJanus;
2192           break;
2193         case 49:
2194           v = VariantSuper;
2195           break;
2196         case 50:
2197           v = VariantGreat;
2198           break;
2199         case -1:
2200           /* Found "wild" or "w" in the string but no number;
2201              must assume it's normal chess. */
2202           v = VariantNormal;
2203           break;
2204         default:
2205           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2206           if( (len >= MSG_SIZ) && appData.debugMode )
2207             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2208
2209           DisplayError(buf, 0);
2210           v = VariantUnknown;
2211           break;
2212         }
2213       }
2214     }
2215     if (appData.debugMode) {
2216       fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
2217               e, wnum, VariantName(v));
2218     }
2219     return v;
2220 }
2221
2222 static int leftover_start = 0, leftover_len = 0;
2223 char star_match[STAR_MATCH_N][MSG_SIZ];
2224
2225 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2226    advance *index beyond it, and set leftover_start to the new value of
2227    *index; else return FALSE.  If pattern contains the character '*', it
2228    matches any sequence of characters not containing '\r', '\n', or the
2229    character following the '*' (if any), and the matched sequence(s) are
2230    copied into star_match.
2231    */
2232 int
2233 looking_at ( char *buf, int *index, char *pattern)
2234 {
2235     char *bufp = &buf[*index], *patternp = pattern;
2236     int star_count = 0;
2237     char *matchp = star_match[0];
2238
2239     for (;;) {
2240         if (*patternp == NULLCHAR) {
2241             *index = leftover_start = bufp - buf;
2242             *matchp = NULLCHAR;
2243             return TRUE;
2244         }
2245         if (*bufp == NULLCHAR) return FALSE;
2246         if (*patternp == '*') {
2247             if (*bufp == *(patternp + 1)) {
2248                 *matchp = NULLCHAR;
2249                 matchp = star_match[++star_count];
2250                 patternp += 2;
2251                 bufp++;
2252                 continue;
2253             } else if (*bufp == '\n' || *bufp == '\r') {
2254                 patternp++;
2255                 if (*patternp == NULLCHAR)
2256                   continue;
2257                 else
2258                   return FALSE;
2259             } else {
2260                 *matchp++ = *bufp++;
2261                 continue;
2262             }
2263         }
2264         if (*patternp != *bufp) return FALSE;
2265         patternp++;
2266         bufp++;
2267     }
2268 }
2269
2270 void
2271 SendToPlayer (char *data, int length)
2272 {
2273     int error, outCount;
2274     outCount = OutputToProcess(NoProc, data, length, &error);
2275     if (outCount < length) {
2276         DisplayFatalError(_("Error writing to display"), error, 1);
2277     }
2278 }
2279
2280 void
2281 PackHolding (char packed[], char *holding)
2282 {
2283     char *p = holding;
2284     char *q = packed;
2285     int runlength = 0;
2286     int curr = 9999;
2287     do {
2288         if (*p == curr) {
2289             runlength++;
2290         } else {
2291             switch (runlength) {
2292               case 0:
2293                 break;
2294               case 1:
2295                 *q++ = curr;
2296                 break;
2297               case 2:
2298                 *q++ = curr;
2299                 *q++ = curr;
2300                 break;
2301               default:
2302                 sprintf(q, "%d", runlength);
2303                 while (*q) q++;
2304                 *q++ = curr;
2305                 break;
2306             }
2307             runlength = 1;
2308             curr = *p;
2309         }
2310     } while (*p++);
2311     *q = NULLCHAR;
2312 }
2313
2314 /* Telnet protocol requests from the front end */
2315 void
2316 TelnetRequest (unsigned char ddww, unsigned char option)
2317 {
2318     unsigned char msg[3];
2319     int outCount, outError;
2320
2321     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2322
2323     if (appData.debugMode) {
2324         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2325         switch (ddww) {
2326           case TN_DO:
2327             ddwwStr = "DO";
2328             break;
2329           case TN_DONT:
2330             ddwwStr = "DONT";
2331             break;
2332           case TN_WILL:
2333             ddwwStr = "WILL";
2334             break;
2335           case TN_WONT:
2336             ddwwStr = "WONT";
2337             break;
2338           default:
2339             ddwwStr = buf1;
2340             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2341             break;
2342         }
2343         switch (option) {
2344           case TN_ECHO:
2345             optionStr = "ECHO";
2346             break;
2347           default:
2348             optionStr = buf2;
2349             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2350             break;
2351         }
2352         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2353     }
2354     msg[0] = TN_IAC;
2355     msg[1] = ddww;
2356     msg[2] = option;
2357     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2358     if (outCount < 3) {
2359         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2360     }
2361 }
2362
2363 void
2364 DoEcho ()
2365 {
2366     if (!appData.icsActive) return;
2367     TelnetRequest(TN_DO, TN_ECHO);
2368 }
2369
2370 void
2371 DontEcho ()
2372 {
2373     if (!appData.icsActive) return;
2374     TelnetRequest(TN_DONT, TN_ECHO);
2375 }
2376
2377 void
2378 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2379 {
2380     /* put the holdings sent to us by the server on the board holdings area */
2381     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2382     char p;
2383     ChessSquare piece;
2384
2385     if(gameInfo.holdingsWidth < 2)  return;
2386     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2387         return; // prevent overwriting by pre-board holdings
2388
2389     if( (int)lowestPiece >= BlackPawn ) {
2390         holdingsColumn = 0;
2391         countsColumn = 1;
2392         holdingsStartRow = BOARD_HEIGHT-1;
2393         direction = -1;
2394     } else {
2395         holdingsColumn = BOARD_WIDTH-1;
2396         countsColumn = BOARD_WIDTH-2;
2397         holdingsStartRow = 0;
2398         direction = 1;
2399     }
2400
2401     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2402         board[i][holdingsColumn] = EmptySquare;
2403         board[i][countsColumn]   = (ChessSquare) 0;
2404     }
2405     while( (p=*holdings++) != NULLCHAR ) {
2406         piece = CharToPiece( ToUpper(p) );
2407         if(piece == EmptySquare) continue;
2408         /*j = (int) piece - (int) WhitePawn;*/
2409         j = PieceToNumber(piece);
2410         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2411         if(j < 0) continue;               /* should not happen */
2412         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2413         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2414         board[holdingsStartRow+j*direction][countsColumn]++;
2415     }
2416 }
2417
2418
2419 void
2420 VariantSwitch (Board board, VariantClass newVariant)
2421 {
2422    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2423    static Board oldBoard;
2424
2425    startedFromPositionFile = FALSE;
2426    if(gameInfo.variant == newVariant) return;
2427
2428    /* [HGM] This routine is called each time an assignment is made to
2429     * gameInfo.variant during a game, to make sure the board sizes
2430     * are set to match the new variant. If that means adding or deleting
2431     * holdings, we shift the playing board accordingly
2432     * This kludge is needed because in ICS observe mode, we get boards
2433     * of an ongoing game without knowing the variant, and learn about the
2434     * latter only later. This can be because of the move list we requested,
2435     * in which case the game history is refilled from the beginning anyway,
2436     * but also when receiving holdings of a crazyhouse game. In the latter
2437     * case we want to add those holdings to the already received position.
2438     */
2439
2440
2441    if (appData.debugMode) {
2442      fprintf(debugFP, "Switch board from %s to %s\n",
2443              VariantName(gameInfo.variant), VariantName(newVariant));
2444      setbuf(debugFP, NULL);
2445    }
2446    shuffleOpenings = 0;       /* [HGM] shuffle */
2447    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2448    switch(newVariant)
2449      {
2450      case VariantShogi:
2451        newWidth = 9;  newHeight = 9;
2452        gameInfo.holdingsSize = 7;
2453      case VariantBughouse:
2454      case VariantCrazyhouse:
2455        newHoldingsWidth = 2; break;
2456      case VariantGreat:
2457        newWidth = 10;
2458      case VariantSuper:
2459        newHoldingsWidth = 2;
2460        gameInfo.holdingsSize = 8;
2461        break;
2462      case VariantGothic:
2463      case VariantCapablanca:
2464      case VariantCapaRandom:
2465        newWidth = 10;
2466      default:
2467        newHoldingsWidth = gameInfo.holdingsSize = 0;
2468      };
2469
2470    if(newWidth  != gameInfo.boardWidth  ||
2471       newHeight != gameInfo.boardHeight ||
2472       newHoldingsWidth != gameInfo.holdingsWidth ) {
2473
2474      /* shift position to new playing area, if needed */
2475      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2476        for(i=0; i<BOARD_HEIGHT; i++)
2477          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2478            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2479              board[i][j];
2480        for(i=0; i<newHeight; i++) {
2481          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2482          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2483        }
2484      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2485        for(i=0; i<BOARD_HEIGHT; i++)
2486          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2487            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2488              board[i][j];
2489      }
2490      board[HOLDINGS_SET] = 0;
2491      gameInfo.boardWidth  = newWidth;
2492      gameInfo.boardHeight = newHeight;
2493      gameInfo.holdingsWidth = newHoldingsWidth;
2494      gameInfo.variant = newVariant;
2495      InitDrawingSizes(-2, 0);
2496    } else gameInfo.variant = newVariant;
2497    CopyBoard(oldBoard, board);   // remember correctly formatted board
2498      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2499    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2500 }
2501
2502 static int loggedOn = FALSE;
2503
2504 /*-- Game start info cache: --*/
2505 int gs_gamenum;
2506 char gs_kind[MSG_SIZ];
2507 static char player1Name[128] = "";
2508 static char player2Name[128] = "";
2509 static char cont_seq[] = "\n\\   ";
2510 static int player1Rating = -1;
2511 static int player2Rating = -1;
2512 /*----------------------------*/
2513
2514 ColorClass curColor = ColorNormal;
2515 int suppressKibitz = 0;
2516
2517 // [HGM] seekgraph
2518 Boolean soughtPending = FALSE;
2519 Boolean seekGraphUp;
2520 #define MAX_SEEK_ADS 200
2521 #define SQUARE 0x80
2522 char *seekAdList[MAX_SEEK_ADS];
2523 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2524 float tcList[MAX_SEEK_ADS];
2525 char colorList[MAX_SEEK_ADS];
2526 int nrOfSeekAds = 0;
2527 int minRating = 1010, maxRating = 2800;
2528 int hMargin = 10, vMargin = 20, h, w;
2529 extern int squareSize, lineGap;
2530
2531 void
2532 PlotSeekAd (int i)
2533 {
2534         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2535         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2536         if(r < minRating+100 && r >=0 ) r = minRating+100;
2537         if(r > maxRating) r = maxRating;
2538         if(tc < 1.f) tc = 1.f;
2539         if(tc > 95.f) tc = 95.f;
2540         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2541         y = ((double)r - minRating)/(maxRating - minRating)
2542             * (h-vMargin-squareSize/8-1) + vMargin;
2543         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2544         if(strstr(seekAdList[i], " u ")) color = 1;
2545         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2546            !strstr(seekAdList[i], "bullet") &&
2547            !strstr(seekAdList[i], "blitz") &&
2548            !strstr(seekAdList[i], "standard") ) color = 2;
2549         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2550         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2551 }
2552
2553 void
2554 PlotSingleSeekAd (int i)
2555 {
2556         PlotSeekAd(i);
2557 }
2558
2559 void
2560 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2561 {
2562         char buf[MSG_SIZ], *ext = "";
2563         VariantClass v = StringToVariant(type);
2564         if(strstr(type, "wild")) {
2565             ext = type + 4; // append wild number
2566             if(v == VariantFischeRandom) type = "chess960"; else
2567             if(v == VariantLoadable) type = "setup"; else
2568             type = VariantName(v);
2569         }
2570         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2571         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2572             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2573             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2574             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2575             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2576             seekNrList[nrOfSeekAds] = nr;
2577             zList[nrOfSeekAds] = 0;
2578             seekAdList[nrOfSeekAds++] = StrSave(buf);
2579             if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2580         }
2581 }
2582
2583 void
2584 EraseSeekDot (int i)
2585 {
2586     int x = xList[i], y = yList[i], d=squareSize/4, k;
2587     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2588     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2589     // now replot every dot that overlapped
2590     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2591         int xx = xList[k], yy = yList[k];
2592         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2593             DrawSeekDot(xx, yy, colorList[k]);
2594     }
2595 }
2596
2597 void
2598 RemoveSeekAd (int nr)
2599 {
2600         int i;
2601         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2602             EraseSeekDot(i);
2603             if(seekAdList[i]) free(seekAdList[i]);
2604             seekAdList[i] = seekAdList[--nrOfSeekAds];
2605             seekNrList[i] = seekNrList[nrOfSeekAds];
2606             ratingList[i] = ratingList[nrOfSeekAds];
2607             colorList[i]  = colorList[nrOfSeekAds];
2608             tcList[i] = tcList[nrOfSeekAds];
2609             xList[i]  = xList[nrOfSeekAds];
2610             yList[i]  = yList[nrOfSeekAds];
2611             zList[i]  = zList[nrOfSeekAds];
2612             seekAdList[nrOfSeekAds] = NULL;
2613             break;
2614         }
2615 }
2616
2617 Boolean
2618 MatchSoughtLine (char *line)
2619 {
2620     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2621     int nr, base, inc, u=0; char dummy;
2622
2623     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2624        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2625        (u=1) &&
2626        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2627         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2628         // match: compact and save the line
2629         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2630         return TRUE;
2631     }
2632     return FALSE;
2633 }
2634
2635 int
2636 DrawSeekGraph ()
2637 {
2638     int i;
2639     if(!seekGraphUp) return FALSE;
2640     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2641     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2642
2643     DrawSeekBackground(0, 0, w, h);
2644     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2645     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2646     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2647         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2648         yy = h-1-yy;
2649         DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2650         if(i%500 == 0) {
2651             char buf[MSG_SIZ];
2652             snprintf(buf, MSG_SIZ, "%d", i);
2653             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2654         }
2655     }
2656     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2657     for(i=1; i<100; i+=(i<10?1:5)) {
2658         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2659         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2660         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2661             char buf[MSG_SIZ];
2662             snprintf(buf, MSG_SIZ, "%d", i);
2663             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2664         }
2665     }
2666     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2667     return TRUE;
2668 }
2669
2670 int
2671 SeekGraphClick (ClickType click, int x, int y, int moving)
2672 {
2673     static int lastDown = 0, displayed = 0, lastSecond;
2674     if(y < 0) return FALSE;
2675     if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2676         (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2677         if(!seekGraphUp) return FALSE;
2678         seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2679         DrawPosition(TRUE, NULL);
2680         return TRUE;
2681     }
2682     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2683         if(click == Release || moving) return FALSE;
2684         nrOfSeekAds = 0;
2685         soughtPending = TRUE;
2686         SendToICS(ics_prefix);
2687         SendToICS("sought\n"); // should this be "sought all"?
2688     } else { // issue challenge based on clicked ad
2689         int dist = 10000; int i, closest = 0, second = 0;
2690         for(i=0; i<nrOfSeekAds; i++) {
2691             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2692             if(d < dist) { dist = d; closest = i; }
2693             second += (d - zList[i] < 120); // count in-range ads
2694             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2695         }
2696         if(dist < 120) {
2697             char buf[MSG_SIZ];
2698             second = (second > 1);
2699             if(displayed != closest || second != lastSecond) {
2700                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2701                 lastSecond = second; displayed = closest;
2702             }
2703             if(click == Press) {
2704                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2705                 lastDown = closest;
2706                 return TRUE;
2707             } // on press 'hit', only show info
2708             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2709             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2710             SendToICS(ics_prefix);
2711             SendToICS(buf);
2712             return TRUE; // let incoming board of started game pop down the graph
2713         } else if(click == Release) { // release 'miss' is ignored
2714             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2715             if(moving == 2) { // right up-click
2716                 nrOfSeekAds = 0; // refresh graph
2717                 soughtPending = TRUE;
2718                 SendToICS(ics_prefix);
2719                 SendToICS("sought\n"); // should this be "sought all"?
2720             }
2721             return TRUE;
2722         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2723         // press miss or release hit 'pop down' seek graph
2724         seekGraphUp = FALSE;
2725         DrawPosition(TRUE, NULL);
2726     }
2727     return TRUE;
2728 }
2729
2730 void
2731 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2732 {
2733 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2734 #define STARTED_NONE 0
2735 #define STARTED_MOVES 1
2736 #define STARTED_BOARD 2
2737 #define STARTED_OBSERVE 3
2738 #define STARTED_HOLDINGS 4
2739 #define STARTED_CHATTER 5
2740 #define STARTED_COMMENT 6
2741 #define STARTED_MOVES_NOHIDE 7
2742
2743     static int started = STARTED_NONE;
2744     static char parse[20000];
2745     static int parse_pos = 0;
2746     static char buf[BUF_SIZE + 1];
2747     static int firstTime = TRUE, intfSet = FALSE;
2748     static ColorClass prevColor = ColorNormal;
2749     static int savingComment = FALSE;
2750     static int cmatch = 0; // continuation sequence match
2751     char *bp;
2752     char str[MSG_SIZ];
2753     int i, oldi;
2754     int buf_len;
2755     int next_out;
2756     int tkind;
2757     int backup;    /* [DM] For zippy color lines */
2758     char *p;
2759     char talker[MSG_SIZ]; // [HGM] chat
2760     int channel;
2761
2762     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2763
2764     if (appData.debugMode) {
2765       if (!error) {
2766         fprintf(debugFP, "<ICS: ");
2767         show_bytes(debugFP, data, count);
2768         fprintf(debugFP, "\n");
2769       }
2770     }
2771
2772     if (appData.debugMode) { int f = forwardMostMove;
2773         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2774                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2775                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2776     }
2777     if (count > 0) {
2778         /* If last read ended with a partial line that we couldn't parse,
2779            prepend it to the new read and try again. */
2780         if (leftover_len > 0) {
2781             for (i=0; i<leftover_len; i++)
2782               buf[i] = buf[leftover_start + i];
2783         }
2784
2785     /* copy new characters into the buffer */
2786     bp = buf + leftover_len;
2787     buf_len=leftover_len;
2788     for (i=0; i<count; i++)
2789     {
2790         // ignore these
2791         if (data[i] == '\r')
2792             continue;
2793
2794         // join lines split by ICS?
2795         if (!appData.noJoin)
2796         {
2797             /*
2798                 Joining just consists of finding matches against the
2799                 continuation sequence, and discarding that sequence
2800                 if found instead of copying it.  So, until a match
2801                 fails, there's nothing to do since it might be the
2802                 complete sequence, and thus, something we don't want
2803                 copied.
2804             */
2805             if (data[i] == cont_seq[cmatch])
2806             {
2807                 cmatch++;
2808                 if (cmatch == strlen(cont_seq))
2809                 {
2810                     cmatch = 0; // complete match.  just reset the counter
2811
2812                     /*
2813                         it's possible for the ICS to not include the space
2814                         at the end of the last word, making our [correct]
2815                         join operation fuse two separate words.  the server
2816                         does this when the space occurs at the width setting.
2817                     */
2818                     if (!buf_len || buf[buf_len-1] != ' ')
2819                     {
2820                         *bp++ = ' ';
2821                         buf_len++;
2822                     }
2823                 }
2824                 continue;
2825             }
2826             else if (cmatch)
2827             {
2828                 /*
2829                     match failed, so we have to copy what matched before
2830                     falling through and copying this character.  In reality,
2831                     this will only ever be just the newline character, but
2832                     it doesn't hurt to be precise.
2833                 */
2834                 strncpy(bp, cont_seq, cmatch);
2835                 bp += cmatch;
2836                 buf_len += cmatch;
2837                 cmatch = 0;
2838             }
2839         }
2840
2841         // copy this char
2842         *bp++ = data[i];
2843         buf_len++;
2844     }
2845
2846         buf[buf_len] = NULLCHAR;
2847 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2848         next_out = 0;
2849         leftover_start = 0;
2850
2851         i = 0;
2852         while (i < buf_len) {
2853             /* Deal with part of the TELNET option negotiation
2854                protocol.  We refuse to do anything beyond the
2855                defaults, except that we allow the WILL ECHO option,
2856                which ICS uses to turn off password echoing when we are
2857                directly connected to it.  We reject this option
2858                if localLineEditing mode is on (always on in xboard)
2859                and we are talking to port 23, which might be a real
2860                telnet server that will try to keep WILL ECHO on permanently.
2861              */
2862             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2863                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2864                 unsigned char option;
2865                 oldi = i;
2866                 switch ((unsigned char) buf[++i]) {
2867                   case TN_WILL:
2868                     if (appData.debugMode)
2869                       fprintf(debugFP, "\n<WILL ");
2870                     switch (option = (unsigned char) buf[++i]) {
2871                       case TN_ECHO:
2872                         if (appData.debugMode)
2873                           fprintf(debugFP, "ECHO ");
2874                         /* Reply only if this is a change, according
2875                            to the protocol rules. */
2876                         if (remoteEchoOption) break;
2877                         if (appData.localLineEditing &&
2878                             atoi(appData.icsPort) == TN_PORT) {
2879                             TelnetRequest(TN_DONT, TN_ECHO);
2880                         } else {
2881                             EchoOff();
2882                             TelnetRequest(TN_DO, TN_ECHO);
2883                             remoteEchoOption = TRUE;
2884                         }
2885                         break;
2886                       default:
2887                         if (appData.debugMode)
2888                           fprintf(debugFP, "%d ", option);
2889                         /* Whatever this is, we don't want it. */
2890                         TelnetRequest(TN_DONT, option);
2891                         break;
2892                     }
2893                     break;
2894                   case TN_WONT:
2895                     if (appData.debugMode)
2896                       fprintf(debugFP, "\n<WONT ");
2897                     switch (option = (unsigned char) buf[++i]) {
2898                       case TN_ECHO:
2899                         if (appData.debugMode)
2900                           fprintf(debugFP, "ECHO ");
2901                         /* Reply only if this is a change, according
2902                            to the protocol rules. */
2903                         if (!remoteEchoOption) break;
2904                         EchoOn();
2905                         TelnetRequest(TN_DONT, TN_ECHO);
2906                         remoteEchoOption = FALSE;
2907                         break;
2908                       default:
2909                         if (appData.debugMode)
2910                           fprintf(debugFP, "%d ", (unsigned char) option);
2911                         /* Whatever this is, it must already be turned
2912                            off, because we never agree to turn on
2913                            anything non-default, so according to the
2914                            protocol rules, we don't reply. */
2915                         break;
2916                     }
2917                     break;
2918                   case TN_DO:
2919                     if (appData.debugMode)
2920                       fprintf(debugFP, "\n<DO ");
2921                     switch (option = (unsigned char) buf[++i]) {
2922                       default:
2923                         /* Whatever this is, we refuse to do it. */
2924                         if (appData.debugMode)
2925                           fprintf(debugFP, "%d ", option);
2926                         TelnetRequest(TN_WONT, option);
2927                         break;
2928                     }
2929                     break;
2930                   case TN_DONT:
2931                     if (appData.debugMode)
2932                       fprintf(debugFP, "\n<DONT ");
2933                     switch (option = (unsigned char) buf[++i]) {
2934                       default:
2935                         if (appData.debugMode)
2936                           fprintf(debugFP, "%d ", option);
2937                         /* Whatever this is, we are already not doing
2938                            it, because we never agree to do anything
2939                            non-default, so according to the protocol
2940                            rules, we don't reply. */
2941                         break;
2942                     }
2943                     break;
2944                   case TN_IAC:
2945                     if (appData.debugMode)
2946                       fprintf(debugFP, "\n<IAC ");
2947                     /* Doubled IAC; pass it through */
2948                     i--;
2949                     break;
2950                   default:
2951                     if (appData.debugMode)
2952                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2953                     /* Drop all other telnet commands on the floor */
2954                     break;
2955                 }
2956                 if (oldi > next_out)
2957                   SendToPlayer(&buf[next_out], oldi - next_out);
2958                 if (++i > next_out)
2959                   next_out = i;
2960                 continue;
2961             }
2962
2963             /* OK, this at least will *usually* work */
2964             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2965                 loggedOn = TRUE;
2966             }
2967
2968             if (loggedOn && !intfSet) {
2969                 if (ics_type == ICS_ICC) {
2970                   snprintf(str, MSG_SIZ,
2971                           "/set-quietly interface %s\n/set-quietly style 12\n",
2972                           programVersion);
2973                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2974                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2975                 } else if (ics_type == ICS_CHESSNET) {
2976                   snprintf(str, MSG_SIZ, "/style 12\n");
2977                 } else {
2978                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2979                   strcat(str, programVersion);
2980                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2981                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2982                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2983 #ifdef WIN32
2984                   strcat(str, "$iset nohighlight 1\n");
2985 #endif
2986                   strcat(str, "$iset lock 1\n$style 12\n");
2987                 }
2988                 SendToICS(str);
2989                 NotifyFrontendLogin();
2990                 intfSet = TRUE;
2991             }
2992
2993             if (started == STARTED_COMMENT) {
2994                 /* Accumulate characters in comment */
2995                 parse[parse_pos++] = buf[i];
2996                 if (buf[i] == '\n') {
2997                     parse[parse_pos] = NULLCHAR;
2998                     if(chattingPartner>=0) {
2999                         char mess[MSG_SIZ];
3000                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
3001                         OutputChatMessage(chattingPartner, mess);
3002                         chattingPartner = -1;
3003                         next_out = i+1; // [HGM] suppress printing in ICS window
3004                     } else
3005                     if(!suppressKibitz) // [HGM] kibitz
3006                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
3007                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
3008                         int nrDigit = 0, nrAlph = 0, j;
3009                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
3010                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
3011                         parse[parse_pos] = NULLCHAR;
3012                         // try to be smart: if it does not look like search info, it should go to
3013                         // ICS interaction window after all, not to engine-output window.
3014                         for(j=0; j<parse_pos; j++) { // count letters and digits
3015                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
3016                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
3017                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
3018                         }
3019                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
3020                             int depth=0; float score;
3021                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
3022                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
3023                                 pvInfoList[forwardMostMove-1].depth = depth;
3024                                 pvInfoList[forwardMostMove-1].score = 100*score;
3025                             }
3026                             OutputKibitz(suppressKibitz, parse);
3027                         } else {
3028                             char tmp[MSG_SIZ];
3029                             if(gameMode == IcsObserving) // restore original ICS messages
3030                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3031                             else
3032                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3033                             SendToPlayer(tmp, strlen(tmp));
3034                         }
3035                         next_out = i+1; // [HGM] suppress printing in ICS window
3036                     }
3037                     started = STARTED_NONE;
3038                 } else {
3039                     /* Don't match patterns against characters in comment */
3040                     i++;
3041                     continue;
3042                 }
3043             }
3044             if (started == STARTED_CHATTER) {
3045                 if (buf[i] != '\n') {
3046                     /* Don't match patterns against characters in chatter */
3047                     i++;
3048                     continue;
3049                 }
3050                 started = STARTED_NONE;
3051                 if(suppressKibitz) next_out = i+1;
3052             }
3053
3054             /* Kludge to deal with rcmd protocol */
3055             if (firstTime && looking_at(buf, &i, "\001*")) {
3056                 DisplayFatalError(&buf[1], 0, 1);
3057                 continue;
3058             } else {
3059                 firstTime = FALSE;
3060             }
3061
3062             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3063                 ics_type = ICS_ICC;
3064                 ics_prefix = "/";
3065                 if (appData.debugMode)
3066                   fprintf(debugFP, "ics_type %d\n", ics_type);
3067                 continue;
3068             }
3069             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3070                 ics_type = ICS_FICS;
3071                 ics_prefix = "$";
3072                 if (appData.debugMode)
3073                   fprintf(debugFP, "ics_type %d\n", ics_type);
3074                 continue;
3075             }
3076             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3077                 ics_type = ICS_CHESSNET;
3078                 ics_prefix = "/";
3079                 if (appData.debugMode)
3080                   fprintf(debugFP, "ics_type %d\n", ics_type);
3081                 continue;
3082             }
3083
3084             if (!loggedOn &&
3085                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3086                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3087                  looking_at(buf, &i, "will be \"*\""))) {
3088               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3089               continue;
3090             }
3091
3092             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3093               char buf[MSG_SIZ];
3094               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3095               DisplayIcsInteractionTitle(buf);
3096               have_set_title = TRUE;
3097             }
3098
3099             /* skip finger notes */
3100             if (started == STARTED_NONE &&
3101                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3102                  (buf[i] == '1' && buf[i+1] == '0')) &&
3103                 buf[i+2] == ':' && buf[i+3] == ' ') {
3104               started = STARTED_CHATTER;
3105               i += 3;
3106               continue;
3107             }
3108
3109             oldi = i;
3110             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3111             if(appData.seekGraph) {
3112                 if(soughtPending && MatchSoughtLine(buf+i)) {
3113                     i = strstr(buf+i, "rated") - buf;
3114                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3115                     next_out = leftover_start = i;
3116                     started = STARTED_CHATTER;
3117                     suppressKibitz = TRUE;
3118                     continue;
3119                 }
3120                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3121                         && looking_at(buf, &i, "* ads displayed")) {
3122                     soughtPending = FALSE;
3123                     seekGraphUp = TRUE;
3124                     DrawSeekGraph();
3125                     continue;
3126                 }
3127                 if(appData.autoRefresh) {
3128                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3129                         int s = (ics_type == ICS_ICC); // ICC format differs
3130                         if(seekGraphUp)
3131                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3132                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3133                         looking_at(buf, &i, "*% "); // eat prompt
3134                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3135                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3136                         next_out = i; // suppress
3137                         continue;
3138                     }
3139                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3140                         char *p = star_match[0];
3141                         while(*p) {
3142                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3143                             while(*p && *p++ != ' '); // next
3144                         }
3145                         looking_at(buf, &i, "*% "); // eat prompt
3146                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3147                         next_out = i;
3148                         continue;
3149                     }
3150                 }
3151             }
3152
3153             /* skip formula vars */
3154             if (started == STARTED_NONE &&
3155                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3156               started = STARTED_CHATTER;
3157               i += 3;
3158               continue;
3159             }
3160
3161             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3162             if (appData.autoKibitz && started == STARTED_NONE &&
3163                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3164                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3165                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3166                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3167                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3168                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3169                         suppressKibitz = TRUE;
3170                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3171                         next_out = i;
3172                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3173                                 && (gameMode == IcsPlayingWhite)) ||
3174                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3175                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3176                             started = STARTED_CHATTER; // own kibitz we simply discard
3177                         else {
3178                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3179                             parse_pos = 0; parse[0] = NULLCHAR;
3180                             savingComment = TRUE;
3181                             suppressKibitz = gameMode != IcsObserving ? 2 :
3182                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3183                         }
3184                         continue;
3185                 } else
3186                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3187                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3188                          && atoi(star_match[0])) {
3189                     // suppress the acknowledgements of our own autoKibitz
3190                     char *p;
3191                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3192                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3193                     SendToPlayer(star_match[0], strlen(star_match[0]));
3194                     if(looking_at(buf, &i, "*% ")) // eat prompt
3195                         suppressKibitz = FALSE;
3196                     next_out = i;
3197                     continue;
3198                 }
3199             } // [HGM] kibitz: end of patch
3200
3201             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3202
3203             // [HGM] chat: intercept tells by users for which we have an open chat window
3204             channel = -1;
3205             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3206                                            looking_at(buf, &i, "* whispers:") ||
3207                                            looking_at(buf, &i, "* kibitzes:") ||
3208                                            looking_at(buf, &i, "* shouts:") ||
3209                                            looking_at(buf, &i, "* c-shouts:") ||
3210                                            looking_at(buf, &i, "--> * ") ||
3211                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3212                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3213                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3214                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3215                 int p;
3216                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3217                 chattingPartner = -1;
3218
3219                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3220                 for(p=0; p<MAX_CHAT; p++) {
3221                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3222                     talker[0] = '['; strcat(talker, "] ");
3223                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3224                     chattingPartner = p; break;
3225                     }
3226                 } else
3227                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3228                 for(p=0; p<MAX_CHAT; p++) {
3229                     if(!strcmp("kibitzes", chatPartner[p])) {
3230                         talker[0] = '['; strcat(talker, "] ");
3231                         chattingPartner = p; break;
3232                     }
3233                 } else
3234                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3235                 for(p=0; p<MAX_CHAT; p++) {
3236                     if(!strcmp("whispers", chatPartner[p])) {
3237                         talker[0] = '['; strcat(talker, "] ");
3238                         chattingPartner = p; break;
3239                     }
3240                 } else
3241                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3242                   if(buf[i-8] == '-' && buf[i-3] == 't')
3243                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3244                     if(!strcmp("c-shouts", chatPartner[p])) {
3245                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3246                         chattingPartner = p; break;
3247                     }
3248                   }
3249                   if(chattingPartner < 0)
3250                   for(p=0; p<MAX_CHAT; p++) {
3251                     if(!strcmp("shouts", chatPartner[p])) {
3252                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3253                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3254                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3255                         chattingPartner = p; break;
3256                     }
3257                   }
3258                 }
3259                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3260                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3261                     talker[0] = 0; Colorize(ColorTell, FALSE);
3262                     chattingPartner = p; break;
3263                 }
3264                 if(chattingPartner<0) i = oldi; else {
3265                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3266                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3267                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3268                     started = STARTED_COMMENT;
3269                     parse_pos = 0; parse[0] = NULLCHAR;
3270                     savingComment = 3 + chattingPartner; // counts as TRUE
3271                     suppressKibitz = TRUE;
3272                     continue;
3273                 }
3274             } // [HGM] chat: end of patch
3275
3276           backup = i;
3277             if (appData.zippyTalk || appData.zippyPlay) {
3278                 /* [DM] Backup address for color zippy lines */
3279 #if ZIPPY
3280                if (loggedOn == TRUE)
3281                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3282                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3283 #endif
3284             } // [DM] 'else { ' deleted
3285                 if (
3286                     /* Regular tells and says */
3287                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3288                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3289                     looking_at(buf, &i, "* says: ") ||
3290                     /* Don't color "message" or "messages" output */
3291                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3292                     looking_at(buf, &i, "*. * at *:*: ") ||
3293                     looking_at(buf, &i, "--* (*:*): ") ||
3294                     /* Message notifications (same color as tells) */
3295                     looking_at(buf, &i, "* has left a message ") ||
3296                     looking_at(buf, &i, "* just sent you a message:\n") ||
3297                     /* Whispers and kibitzes */
3298                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3299                     looking_at(buf, &i, "* kibitzes: ") ||
3300                     /* Channel tells */
3301                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3302
3303                   if (tkind == 1 && strchr(star_match[0], ':')) {
3304                       /* Avoid "tells you:" spoofs in channels */
3305                      tkind = 3;
3306                   }
3307                   if (star_match[0][0] == NULLCHAR ||
3308                       strchr(star_match[0], ' ') ||
3309                       (tkind == 3 && strchr(star_match[1], ' '))) {
3310                     /* Reject bogus matches */
3311                     i = oldi;
3312                   } else {
3313                     if (appData.colorize) {
3314                       if (oldi > next_out) {
3315                         SendToPlayer(&buf[next_out], oldi - next_out);
3316                         next_out = oldi;
3317                       }
3318                       switch (tkind) {
3319                       case 1:
3320                         Colorize(ColorTell, FALSE);
3321                         curColor = ColorTell;
3322                         break;
3323                       case 2:
3324                         Colorize(ColorKibitz, FALSE);
3325                         curColor = ColorKibitz;
3326                         break;
3327                       case 3:
3328                         p = strrchr(star_match[1], '(');
3329                         if (p == NULL) {
3330                           p = star_match[1];
3331                         } else {
3332                           p++;
3333                         }
3334                         if (atoi(p) == 1) {
3335                           Colorize(ColorChannel1, FALSE);
3336                           curColor = ColorChannel1;
3337                         } else {
3338                           Colorize(ColorChannel, FALSE);
3339                           curColor = ColorChannel;
3340                         }
3341                         break;
3342                       case 5:
3343                         curColor = ColorNormal;
3344                         break;
3345                       }
3346                     }
3347                     if (started == STARTED_NONE && appData.autoComment &&
3348                         (gameMode == IcsObserving ||
3349                          gameMode == IcsPlayingWhite ||
3350                          gameMode == IcsPlayingBlack)) {
3351                       parse_pos = i - oldi;
3352                       memcpy(parse, &buf[oldi], parse_pos);
3353                       parse[parse_pos] = NULLCHAR;
3354                       started = STARTED_COMMENT;
3355                       savingComment = TRUE;
3356                     } else {
3357                       started = STARTED_CHATTER;
3358                       savingComment = FALSE;
3359                     }
3360                     loggedOn = TRUE;
3361                     continue;
3362                   }
3363                 }
3364
3365                 if (looking_at(buf, &i, "* s-shouts: ") ||
3366                     looking_at(buf, &i, "* c-shouts: ")) {
3367                     if (appData.colorize) {
3368                         if (oldi > next_out) {
3369                             SendToPlayer(&buf[next_out], oldi - next_out);
3370                             next_out = oldi;
3371                         }
3372                         Colorize(ColorSShout, FALSE);
3373                         curColor = ColorSShout;
3374                     }
3375                     loggedOn = TRUE;
3376                     started = STARTED_CHATTER;
3377                     continue;
3378                 }
3379
3380                 if (looking_at(buf, &i, "--->")) {
3381                     loggedOn = TRUE;
3382                     continue;
3383                 }
3384
3385                 if (looking_at(buf, &i, "* shouts: ") ||
3386                     looking_at(buf, &i, "--> ")) {
3387                     if (appData.colorize) {
3388                         if (oldi > next_out) {
3389                             SendToPlayer(&buf[next_out], oldi - next_out);
3390                             next_out = oldi;
3391                         }
3392                         Colorize(ColorShout, FALSE);
3393                         curColor = ColorShout;
3394                     }
3395                     loggedOn = TRUE;
3396                     started = STARTED_CHATTER;
3397                     continue;
3398                 }
3399
3400                 if (looking_at( buf, &i, "Challenge:")) {
3401                     if (appData.colorize) {
3402                         if (oldi > next_out) {
3403                             SendToPlayer(&buf[next_out], oldi - next_out);
3404                             next_out = oldi;
3405                         }
3406                         Colorize(ColorChallenge, FALSE);
3407                         curColor = ColorChallenge;
3408                     }
3409                     loggedOn = TRUE;
3410                     continue;
3411                 }
3412
3413                 if (looking_at(buf, &i, "* offers you") ||
3414                     looking_at(buf, &i, "* offers to be") ||
3415                     looking_at(buf, &i, "* would like to") ||
3416                     looking_at(buf, &i, "* requests to") ||
3417                     looking_at(buf, &i, "Your opponent offers") ||
3418                     looking_at(buf, &i, "Your opponent requests")) {
3419
3420                     if (appData.colorize) {
3421                         if (oldi > next_out) {
3422                             SendToPlayer(&buf[next_out], oldi - next_out);
3423                             next_out = oldi;
3424                         }
3425                         Colorize(ColorRequest, FALSE);
3426                         curColor = ColorRequest;
3427                     }
3428                     continue;
3429                 }
3430
3431                 if (looking_at(buf, &i, "* (*) seeking")) {
3432                     if (appData.colorize) {
3433                         if (oldi > next_out) {
3434                             SendToPlayer(&buf[next_out], oldi - next_out);
3435                             next_out = oldi;
3436                         }
3437                         Colorize(ColorSeek, FALSE);
3438                         curColor = ColorSeek;
3439                     }
3440                     continue;
3441             }
3442
3443           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3444
3445             if (looking_at(buf, &i, "\\   ")) {
3446                 if (prevColor != ColorNormal) {
3447                     if (oldi > next_out) {
3448                         SendToPlayer(&buf[next_out], oldi - next_out);
3449                         next_out = oldi;
3450                     }
3451                     Colorize(prevColor, TRUE);
3452                     curColor = prevColor;
3453                 }
3454                 if (savingComment) {
3455                     parse_pos = i - oldi;
3456                     memcpy(parse, &buf[oldi], parse_pos);
3457                     parse[parse_pos] = NULLCHAR;
3458                     started = STARTED_COMMENT;
3459                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3460                         chattingPartner = savingComment - 3; // kludge to remember the box
3461                 } else {
3462                     started = STARTED_CHATTER;
3463                 }
3464                 continue;
3465             }
3466
3467             if (looking_at(buf, &i, "Black Strength :") ||
3468                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3469                 looking_at(buf, &i, "<10>") ||
3470                 looking_at(buf, &i, "#@#")) {
3471                 /* Wrong board style */
3472                 loggedOn = TRUE;
3473                 SendToICS(ics_prefix);
3474                 SendToICS("set style 12\n");
3475                 SendToICS(ics_prefix);
3476                 SendToICS("refresh\n");
3477                 continue;
3478             }
3479
3480             if (looking_at(buf, &i, "login:")) {
3481               if (!have_sent_ICS_logon) {
3482                 if(ICSInitScript())
3483                   have_sent_ICS_logon = 1;
3484                 else // no init script was found
3485                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3486               } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3487                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3488               }
3489                 continue;
3490             }
3491
3492             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3493                 (looking_at(buf, &i, "\n<12> ") ||
3494                  looking_at(buf, &i, "<12> "))) {
3495                 loggedOn = TRUE;
3496                 if (oldi > next_out) {
3497                     SendToPlayer(&buf[next_out], oldi - next_out);
3498                 }
3499                 next_out = i;
3500                 started = STARTED_BOARD;
3501                 parse_pos = 0;
3502                 continue;
3503             }
3504
3505             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3506                 looking_at(buf, &i, "<b1> ")) {
3507                 if (oldi > next_out) {
3508                     SendToPlayer(&buf[next_out], oldi - next_out);
3509                 }
3510                 next_out = i;
3511                 started = STARTED_HOLDINGS;
3512                 parse_pos = 0;
3513                 continue;
3514             }
3515
3516             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3517                 loggedOn = TRUE;
3518                 /* Header for a move list -- first line */
3519
3520                 switch (ics_getting_history) {
3521                   case H_FALSE:
3522                     switch (gameMode) {
3523                       case IcsIdle:
3524                       case BeginningOfGame:
3525                         /* User typed "moves" or "oldmoves" while we
3526                            were idle.  Pretend we asked for these
3527                            moves and soak them up so user can step
3528                            through them and/or save them.
3529                            */
3530                         Reset(FALSE, TRUE);
3531                         gameMode = IcsObserving;
3532                         ModeHighlight();
3533                         ics_gamenum = -1;
3534                         ics_getting_history = H_GOT_UNREQ_HEADER;
3535                         break;
3536                       case EditGame: /*?*/
3537                       case EditPosition: /*?*/
3538                         /* Should above feature work in these modes too? */
3539                         /* For now it doesn't */
3540                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3541                         break;
3542                       default:
3543                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3544                         break;
3545                     }
3546                     break;
3547                   case H_REQUESTED:
3548                     /* Is this the right one? */
3549                     if (gameInfo.white && gameInfo.black &&
3550                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3551                         strcmp(gameInfo.black, star_match[2]) == 0) {
3552                         /* All is well */
3553                         ics_getting_history = H_GOT_REQ_HEADER;
3554                     }
3555                     break;
3556                   case H_GOT_REQ_HEADER:
3557                   case H_GOT_UNREQ_HEADER:
3558                   case H_GOT_UNWANTED_HEADER:
3559                   case H_GETTING_MOVES:
3560                     /* Should not happen */
3561                     DisplayError(_("Error gathering move list: two headers"), 0);
3562                     ics_getting_history = H_FALSE;
3563                     break;
3564                 }
3565
3566                 /* Save player ratings into gameInfo if needed */
3567                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3568                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3569                     (gameInfo.whiteRating == -1 ||
3570                      gameInfo.blackRating == -1)) {
3571
3572                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3573                     gameInfo.blackRating = string_to_rating(star_match[3]);
3574                     if (appData.debugMode)
3575                       fprintf(debugFP, "Ratings from header: W %d, B %d\n",
3576                               gameInfo.whiteRating, gameInfo.blackRating);
3577                 }
3578                 continue;
3579             }
3580
3581             if (looking_at(buf, &i,
3582               "* * match, initial time: * minute*, increment: * second")) {
3583                 /* Header for a move list -- second line */
3584                 /* Initial board will follow if this is a wild game */
3585                 if (gameInfo.event != NULL) free(gameInfo.event);
3586                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3587                 gameInfo.event = StrSave(str);
3588                 /* [HGM] we switched variant. Translate boards if needed. */
3589                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3590                 continue;
3591             }
3592
3593             if (looking_at(buf, &i, "Move  ")) {
3594                 /* Beginning of a move list */
3595                 switch (ics_getting_history) {
3596                   case H_FALSE:
3597                     /* Normally should not happen */
3598                     /* Maybe user hit reset while we were parsing */
3599                     break;
3600                   case H_REQUESTED:
3601                     /* Happens if we are ignoring a move list that is not
3602                      * the one we just requested.  Common if the user
3603                      * tries to observe two games without turning off
3604                      * getMoveList */
3605                     break;
3606                   case H_GETTING_MOVES:
3607                     /* Should not happen */
3608                     DisplayError(_("Error gathering move list: nested"), 0);
3609                     ics_getting_history = H_FALSE;
3610                     break;
3611                   case H_GOT_REQ_HEADER:
3612                     ics_getting_history = H_GETTING_MOVES;
3613                     started = STARTED_MOVES;
3614                     parse_pos = 0;
3615                     if (oldi > next_out) {
3616                         SendToPlayer(&buf[next_out], oldi - next_out);
3617                     }
3618                     break;
3619                   case H_GOT_UNREQ_HEADER:
3620                     ics_getting_history = H_GETTING_MOVES;
3621                     started = STARTED_MOVES_NOHIDE;
3622                     parse_pos = 0;
3623                     break;
3624                   case H_GOT_UNWANTED_HEADER:
3625                     ics_getting_history = H_FALSE;
3626                     break;
3627                 }
3628                 continue;
3629             }
3630
3631             if (looking_at(buf, &i, "% ") ||
3632                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3633                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3634                 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3635                     soughtPending = FALSE;
3636                     seekGraphUp = TRUE;
3637                     DrawSeekGraph();
3638                 }
3639                 if(suppressKibitz) next_out = i;
3640                 savingComment = FALSE;
3641                 suppressKibitz = 0;
3642                 switch (started) {
3643                   case STARTED_MOVES:
3644                   case STARTED_MOVES_NOHIDE:
3645                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3646                     parse[parse_pos + i - oldi] = NULLCHAR;
3647                     ParseGameHistory(parse);
3648 #if ZIPPY
3649                     if (appData.zippyPlay && first.initDone) {
3650                         FeedMovesToProgram(&first, forwardMostMove);
3651                         if (gameMode == IcsPlayingWhite) {
3652                             if (WhiteOnMove(forwardMostMove)) {
3653                                 if (first.sendTime) {
3654                                   if (first.useColors) {
3655                                     SendToProgram("black\n", &first);
3656                                   }
3657                                   SendTimeRemaining(&first, TRUE);
3658                                 }
3659                                 if (first.useColors) {
3660                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3661                                 }
3662                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3663                                 first.maybeThinking = TRUE;
3664                             } else {
3665                                 if (first.usePlayother) {
3666                                   if (first.sendTime) {
3667                                     SendTimeRemaining(&first, TRUE);
3668                                   }
3669                                   SendToProgram("playother\n", &first);
3670                                   firstMove = FALSE;
3671                                 } else {
3672                                   firstMove = TRUE;
3673                                 }
3674                             }
3675                         } else if (gameMode == IcsPlayingBlack) {
3676                             if (!WhiteOnMove(forwardMostMove)) {
3677                                 if (first.sendTime) {
3678                                   if (first.useColors) {
3679                                     SendToProgram("white\n", &first);
3680                                   }
3681                                   SendTimeRemaining(&first, FALSE);
3682                                 }
3683                                 if (first.useColors) {
3684                                   SendToProgram("black\n", &first);
3685                                 }
3686                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3687                                 first.maybeThinking = TRUE;
3688                             } else {
3689                                 if (first.usePlayother) {
3690                                   if (first.sendTime) {
3691                                     SendTimeRemaining(&first, FALSE);
3692                                   }
3693                                   SendToProgram("playother\n", &first);
3694                                   firstMove = FALSE;
3695                                 } else {
3696                                   firstMove = TRUE;
3697                                 }
3698                             }
3699                         }
3700                     }
3701 #endif
3702                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3703                         /* Moves came from oldmoves or moves command
3704                            while we weren't doing anything else.
3705                            */
3706                         currentMove = forwardMostMove;
3707                         ClearHighlights();/*!!could figure this out*/
3708                         flipView = appData.flipView;
3709                         DrawPosition(TRUE, boards[currentMove]);
3710                         DisplayBothClocks();
3711                         snprintf(str, MSG_SIZ, "%s %s %s",
3712                                 gameInfo.white, _("vs."),  gameInfo.black);
3713                         DisplayTitle(str);
3714                         gameMode = IcsIdle;
3715                     } else {
3716                         /* Moves were history of an active game */
3717                         if (gameInfo.resultDetails != NULL) {
3718                             free(gameInfo.resultDetails);
3719                             gameInfo.resultDetails = NULL;
3720                         }
3721                     }
3722                     HistorySet(parseList, backwardMostMove,
3723                                forwardMostMove, currentMove-1);
3724                     DisplayMove(currentMove - 1);
3725                     if (started == STARTED_MOVES) next_out = i;
3726                     started = STARTED_NONE;
3727                     ics_getting_history = H_FALSE;
3728                     break;
3729
3730                   case STARTED_OBSERVE:
3731                     started = STARTED_NONE;
3732                     SendToICS(ics_prefix);
3733                     SendToICS("refresh\n");
3734                     break;
3735
3736                   default:
3737                     break;
3738                 }
3739                 if(bookHit) { // [HGM] book: simulate book reply
3740                     static char bookMove[MSG_SIZ]; // a bit generous?
3741
3742                     programStats.nodes = programStats.depth = programStats.time =
3743                     programStats.score = programStats.got_only_move = 0;
3744                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3745
3746                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3747                     strcat(bookMove, bookHit);
3748                     HandleMachineMove(bookMove, &first);
3749                 }
3750                 continue;
3751             }
3752
3753             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3754                  started == STARTED_HOLDINGS ||
3755                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3756                 /* Accumulate characters in move list or board */
3757                 parse[parse_pos++] = buf[i];
3758             }
3759
3760             /* Start of game messages.  Mostly we detect start of game
3761                when the first board image arrives.  On some versions
3762                of the ICS, though, we need to do a "refresh" after starting
3763                to observe in order to get the current board right away. */
3764             if (looking_at(buf, &i, "Adding game * to observation list")) {
3765                 started = STARTED_OBSERVE;
3766                 continue;
3767             }
3768
3769             /* Handle auto-observe */
3770             if (appData.autoObserve &&
3771                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3772                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3773                 char *player;
3774                 /* Choose the player that was highlighted, if any. */
3775                 if (star_match[0][0] == '\033' ||
3776                     star_match[1][0] != '\033') {
3777                     player = star_match[0];
3778                 } else {
3779                     player = star_match[2];
3780                 }
3781                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3782                         ics_prefix, StripHighlightAndTitle(player));
3783                 SendToICS(str);
3784
3785                 /* Save ratings from notify string */
3786                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3787                 player1Rating = string_to_rating(star_match[1]);
3788                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3789                 player2Rating = string_to_rating(star_match[3]);
3790
3791                 if (appData.debugMode)
3792                   fprintf(debugFP,
3793                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3794                           player1Name, player1Rating,
3795                           player2Name, player2Rating);
3796
3797                 continue;
3798             }
3799
3800             /* Deal with automatic examine mode after a game,
3801                and with IcsObserving -> IcsExamining transition */
3802             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3803                 looking_at(buf, &i, "has made you an examiner of game *")) {
3804
3805                 int gamenum = atoi(star_match[0]);
3806                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3807                     gamenum == ics_gamenum) {
3808                     /* We were already playing or observing this game;
3809                        no need to refetch history */
3810                     gameMode = IcsExamining;
3811                     if (pausing) {
3812                         pauseExamForwardMostMove = forwardMostMove;
3813                     } else if (currentMove < forwardMostMove) {
3814                         ForwardInner(forwardMostMove);
3815                     }
3816                 } else {
3817                     /* I don't think this case really can happen */
3818                     SendToICS(ics_prefix);
3819                     SendToICS("refresh\n");
3820                 }
3821                 continue;
3822             }
3823
3824             /* Error messages */
3825 //          if (ics_user_moved) {
3826             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3827                 if (looking_at(buf, &i, "Illegal move") ||
3828                     looking_at(buf, &i, "Not a legal move") ||
3829                     looking_at(buf, &i, "Your king is in check") ||
3830                     looking_at(buf, &i, "It isn't your turn") ||
3831                     looking_at(buf, &i, "It is not your move")) {
3832                     /* Illegal move */
3833                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3834                         currentMove = forwardMostMove-1;
3835                         DisplayMove(currentMove - 1); /* before DMError */
3836                         DrawPosition(FALSE, boards[currentMove]);
3837                         SwitchClocks(forwardMostMove-1); // [HGM] race
3838                         DisplayBothClocks();
3839                     }
3840                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3841                     ics_user_moved = 0;
3842                     continue;
3843                 }
3844             }
3845
3846             if (looking_at(buf, &i, "still have time") ||
3847                 looking_at(buf, &i, "not out of time") ||
3848                 looking_at(buf, &i, "either player is out of time") ||
3849                 looking_at(buf, &i, "has timeseal; checking")) {
3850                 /* We must have called his flag a little too soon */
3851                 whiteFlag = blackFlag = FALSE;
3852                 continue;
3853             }
3854
3855             if (looking_at(buf, &i, "added * seconds to") ||
3856                 looking_at(buf, &i, "seconds were added to")) {
3857                 /* Update the clocks */
3858                 SendToICS(ics_prefix);
3859                 SendToICS("refresh\n");
3860                 continue;
3861             }
3862
3863             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3864                 ics_clock_paused = TRUE;
3865                 StopClocks();
3866                 continue;
3867             }
3868
3869             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3870                 ics_clock_paused = FALSE;
3871                 StartClocks();
3872                 continue;
3873             }
3874
3875             /* Grab player ratings from the Creating: message.
3876                Note we have to check for the special case when
3877                the ICS inserts things like [white] or [black]. */
3878             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3879                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3880                 /* star_matches:
3881                    0    player 1 name (not necessarily white)
3882                    1    player 1 rating
3883                    2    empty, white, or black (IGNORED)
3884                    3    player 2 name (not necessarily black)
3885                    4    player 2 rating
3886
3887                    The names/ratings are sorted out when the game
3888                    actually starts (below).
3889                 */
3890                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3891                 player1Rating = string_to_rating(star_match[1]);
3892                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3893                 player2Rating = string_to_rating(star_match[4]);
3894
3895                 if (appData.debugMode)
3896                   fprintf(debugFP,
3897                           "Ratings from 'Creating:' %s %d, %s %d\n",
3898                           player1Name, player1Rating,
3899                           player2Name, player2Rating);
3900
3901                 continue;
3902             }
3903
3904             /* Improved generic start/end-of-game messages */
3905             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3906                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3907                 /* If tkind == 0: */
3908                 /* star_match[0] is the game number */
3909                 /*           [1] is the white player's name */
3910                 /*           [2] is the black player's name */
3911                 /* For end-of-game: */
3912                 /*           [3] is the reason for the game end */
3913                 /*           [4] is a PGN end game-token, preceded by " " */
3914                 /* For start-of-game: */
3915                 /*           [3] begins with "Creating" or "Continuing" */
3916                 /*           [4] is " *" or empty (don't care). */
3917                 int gamenum = atoi(star_match[0]);
3918                 char *whitename, *blackname, *why, *endtoken;
3919                 ChessMove endtype = EndOfFile;
3920
3921                 if (tkind == 0) {
3922                   whitename = star_match[1];
3923                   blackname = star_match[2];
3924                   why = star_match[3];
3925                   endtoken = star_match[4];
3926                 } else {
3927                   whitename = star_match[1];
3928                   blackname = star_match[3];
3929                   why = star_match[5];
3930                   endtoken = star_match[6];
3931                 }
3932
3933                 /* Game start messages */
3934                 if (strncmp(why, "Creating ", 9) == 0 ||
3935                     strncmp(why, "Continuing ", 11) == 0) {
3936                     gs_gamenum = gamenum;
3937                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3938                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
3939                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3940 #if ZIPPY
3941                     if (appData.zippyPlay) {
3942                         ZippyGameStart(whitename, blackname);
3943                     }
3944 #endif /*ZIPPY*/
3945                     partnerBoardValid = FALSE; // [HGM] bughouse
3946                     continue;
3947                 }
3948
3949                 /* Game end messages */
3950                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3951                     ics_gamenum != gamenum) {
3952                     continue;
3953                 }
3954                 while (endtoken[0] == ' ') endtoken++;
3955                 switch (endtoken[0]) {
3956                   case '*':
3957                   default:
3958                     endtype = GameUnfinished;
3959                     break;
3960                   case '0':
3961                     endtype = BlackWins;
3962                     break;
3963                   case '1':
3964                     if (endtoken[1] == '/')
3965                       endtype = GameIsDrawn;
3966                     else
3967                       endtype = WhiteWins;
3968                     break;
3969                 }
3970                 GameEnds(endtype, why, GE_ICS);
3971 #if ZIPPY
3972                 if (appData.zippyPlay && first.initDone) {
3973                     ZippyGameEnd(endtype, why);
3974                     if (first.pr == NoProc) {
3975                       /* Start the next process early so that we'll
3976                          be ready for the next challenge */
3977                       StartChessProgram(&first);
3978                     }
3979                     /* Send "new" early, in case this command takes
3980                        a long time to finish, so that we'll be ready
3981                        for the next challenge. */
3982                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3983                     Reset(TRUE, TRUE);
3984                 }
3985 #endif /*ZIPPY*/
3986                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3987                 continue;
3988             }
3989
3990             if (looking_at(buf, &i, "Removing game * from observation") ||
3991                 looking_at(buf, &i, "no longer observing game *") ||
3992                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3993                 if (gameMode == IcsObserving &&
3994                     atoi(star_match[0]) == ics_gamenum)
3995                   {
3996                       /* icsEngineAnalyze */
3997                       if (appData.icsEngineAnalyze) {
3998                             ExitAnalyzeMode();
3999                             ModeHighlight();
4000                       }
4001                       StopClocks();
4002                       gameMode = IcsIdle;
4003                       ics_gamenum = -1;
4004                       ics_user_moved = FALSE;
4005                   }
4006                 continue;
4007             }
4008
4009             if (looking_at(buf, &i, "no longer examining game *")) {
4010                 if (gameMode == IcsExamining &&
4011                     atoi(star_match[0]) == ics_gamenum)
4012                   {
4013                       gameMode = IcsIdle;
4014                       ics_gamenum = -1;
4015                       ics_user_moved = FALSE;
4016                   }
4017                 continue;
4018             }
4019
4020             /* Advance leftover_start past any newlines we find,
4021                so only partial lines can get reparsed */
4022             if (looking_at(buf, &i, "\n")) {
4023                 prevColor = curColor;
4024                 if (curColor != ColorNormal) {
4025                     if (oldi > next_out) {
4026                         SendToPlayer(&buf[next_out], oldi - next_out);
4027                         next_out = oldi;
4028                     }
4029                     Colorize(ColorNormal, FALSE);
4030                     curColor = ColorNormal;
4031                 }
4032                 if (started == STARTED_BOARD) {
4033                     started = STARTED_NONE;
4034                     parse[parse_pos] = NULLCHAR;
4035                     ParseBoard12(parse);
4036                     ics_user_moved = 0;
4037
4038                     /* Send premove here */
4039                     if (appData.premove) {
4040                       char str[MSG_SIZ];
4041                       if (currentMove == 0 &&
4042                           gameMode == IcsPlayingWhite &&
4043                           appData.premoveWhite) {
4044                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
4045                         if (appData.debugMode)
4046                           fprintf(debugFP, "Sending premove:\n");
4047                         SendToICS(str);
4048                       } else if (currentMove == 1 &&
4049                                  gameMode == IcsPlayingBlack &&
4050                                  appData.premoveBlack) {
4051                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
4052                         if (appData.debugMode)
4053                           fprintf(debugFP, "Sending premove:\n");
4054                         SendToICS(str);
4055                       } else if (gotPremove) {
4056                         gotPremove = 0;
4057                         ClearPremoveHighlights();
4058                         if (appData.debugMode)
4059                           fprintf(debugFP, "Sending premove:\n");
4060                           UserMoveEvent(premoveFromX, premoveFromY,
4061                                         premoveToX, premoveToY,
4062                                         premovePromoChar);
4063                       }
4064                     }
4065
4066                     /* Usually suppress following prompt */
4067                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4068                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4069                         if (looking_at(buf, &i, "*% ")) {
4070                             savingComment = FALSE;
4071                             suppressKibitz = 0;
4072                         }
4073                     }
4074                     next_out = i;
4075                 } else if (started == STARTED_HOLDINGS) {
4076                     int gamenum;
4077                     char new_piece[MSG_SIZ];
4078                     started = STARTED_NONE;
4079                     parse[parse_pos] = NULLCHAR;
4080                     if (appData.debugMode)
4081                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4082                                                         parse, currentMove);
4083                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4084                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4085                         if (gameInfo.variant == VariantNormal) {
4086                           /* [HGM] We seem to switch variant during a game!
4087                            * Presumably no holdings were displayed, so we have
4088                            * to move the position two files to the right to
4089                            * create room for them!
4090                            */
4091                           VariantClass newVariant;
4092                           switch(gameInfo.boardWidth) { // base guess on board width
4093                                 case 9:  newVariant = VariantShogi; break;
4094                                 case 10: newVariant = VariantGreat; break;
4095                                 default: newVariant = VariantCrazyhouse; break;
4096                           }
4097                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4098                           /* Get a move list just to see the header, which
4099                              will tell us whether this is really bug or zh */
4100                           if (ics_getting_history == H_FALSE) {
4101                             ics_getting_history = H_REQUESTED;
4102                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4103                             SendToICS(str);
4104                           }
4105                         }
4106                         new_piece[0] = NULLCHAR;
4107                         sscanf(parse, "game %d white [%s black [%s <- %s",
4108                                &gamenum, white_holding, black_holding,
4109                                new_piece);
4110                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4111                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4112                         /* [HGM] copy holdings to board holdings area */
4113                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4114                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4115                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4116 #if ZIPPY
4117                         if (appData.zippyPlay && first.initDone) {
4118                             ZippyHoldings(white_holding, black_holding,
4119                                           new_piece);
4120                         }
4121 #endif /*ZIPPY*/
4122                         if (tinyLayout || smallLayout) {
4123                             char wh[16], bh[16];
4124                             PackHolding(wh, white_holding);
4125                             PackHolding(bh, black_holding);
4126                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4127                                     gameInfo.white, gameInfo.black);
4128                         } else {
4129                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4130                                     gameInfo.white, white_holding, _("vs."),
4131                                     gameInfo.black, black_holding);
4132                         }
4133                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4134                         DrawPosition(FALSE, boards[currentMove]);
4135                         DisplayTitle(str);
4136                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4137                         sscanf(parse, "game %d white [%s black [%s <- %s",
4138                                &gamenum, white_holding, black_holding,
4139                                new_piece);
4140                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4141                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4142                         /* [HGM] copy holdings to partner-board holdings area */
4143                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4144                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4145                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4146                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4147                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4148                       }
4149                     }
4150                     /* Suppress following prompt */
4151                     if (looking_at(buf, &i, "*% ")) {
4152                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4153                         savingComment = FALSE;
4154                         suppressKibitz = 0;
4155                     }
4156                     next_out = i;
4157                 }
4158                 continue;
4159             }
4160
4161             i++;                /* skip unparsed character and loop back */
4162         }
4163
4164         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4165 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4166 //          SendToPlayer(&buf[next_out], i - next_out);
4167             started != STARTED_HOLDINGS && leftover_start > next_out) {
4168             SendToPlayer(&buf[next_out], leftover_start - next_out);
4169             next_out = i;
4170         }
4171
4172         leftover_len = buf_len - leftover_start;
4173         /* if buffer ends with something we couldn't parse,
4174            reparse it after appending the next read */
4175
4176     } else if (count == 0) {
4177         RemoveInputSource(isr);
4178         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4179     } else {
4180         DisplayFatalError(_("Error reading from ICS"), error, 1);
4181     }
4182 }
4183
4184
4185 /* Board style 12 looks like this:
4186
4187    <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
4188
4189  * The "<12> " is stripped before it gets to this routine.  The two
4190  * trailing 0's (flip state and clock ticking) are later addition, and
4191  * some chess servers may not have them, or may have only the first.
4192  * Additional trailing fields may be added in the future.
4193  */
4194
4195 #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"
4196
4197 #define RELATION_OBSERVING_PLAYED    0
4198 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4199 #define RELATION_PLAYING_MYMOVE      1
4200 #define RELATION_PLAYING_NOTMYMOVE  -1
4201 #define RELATION_EXAMINING           2
4202 #define RELATION_ISOLATED_BOARD     -3
4203 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4204
4205 void
4206 ParseBoard12 (char *string)
4207 {
4208 #if ZIPPY
4209     int i, takeback;
4210     char *bookHit = NULL; // [HGM] book
4211 #endif
4212     GameMode newGameMode;
4213     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
4214     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
4215     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4216     char to_play, board_chars[200];
4217     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4218     char black[32], white[32];
4219     Board board;
4220     int prevMove = currentMove;
4221     int ticking = 2;
4222     ChessMove moveType;
4223     int fromX, fromY, toX, toY;
4224     char promoChar;
4225     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4226     Boolean weird = FALSE, reqFlag = FALSE;
4227
4228     fromX = fromY = toX = toY = -1;
4229
4230     newGame = FALSE;
4231
4232     if (appData.debugMode)
4233       fprintf(debugFP, "Parsing board: %s\n", string);
4234
4235     move_str[0] = NULLCHAR;
4236     elapsed_time[0] = NULLCHAR;
4237     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4238         int  i = 0, j;
4239         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4240             if(string[i] == ' ') { ranks++; files = 0; }
4241             else files++;
4242             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4243             i++;
4244         }
4245         for(j = 0; j <i; j++) board_chars[j] = string[j];
4246         board_chars[i] = '\0';
4247         string += i + 1;
4248     }
4249     n = sscanf(string, PATTERN, &to_play, &double_push,
4250                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4251                &gamenum, white, black, &relation, &basetime, &increment,
4252                &white_stren, &black_stren, &white_time, &black_time,
4253                &moveNum, str, elapsed_time, move_str, &ics_flip,
4254                &ticking);
4255
4256     if (n < 21) {
4257         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4258         DisplayError(str, 0);
4259         return;
4260     }
4261
4262     /* Convert the move number to internal form */
4263     moveNum = (moveNum - 1) * 2;
4264     if (to_play == 'B') moveNum++;
4265     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4266       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4267                         0, 1);
4268       return;
4269     }
4270
4271     switch (relation) {
4272       case RELATION_OBSERVING_PLAYED:
4273       case RELATION_OBSERVING_STATIC:
4274         if (gamenum == -1) {
4275             /* Old ICC buglet */
4276             relation = RELATION_OBSERVING_STATIC;
4277         }
4278         newGameMode = IcsObserving;
4279         break;
4280       case RELATION_PLAYING_MYMOVE:
4281       case RELATION_PLAYING_NOTMYMOVE:
4282         newGameMode =
4283           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4284             IcsPlayingWhite : IcsPlayingBlack;
4285         soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4286         break;
4287       case RELATION_EXAMINING:
4288         newGameMode = IcsExamining;
4289         break;
4290       case RELATION_ISOLATED_BOARD:
4291       default:
4292         /* Just display this board.  If user was doing something else,
4293            we will forget about it until the next board comes. */
4294         newGameMode = IcsIdle;
4295         break;
4296       case RELATION_STARTING_POSITION:
4297         newGameMode = gameMode;
4298         break;
4299     }
4300
4301     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4302         gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4303          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4304       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4305       int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4306       static int lastBgGame = -1;
4307       char *toSqr;
4308       for (k = 0; k < ranks; k++) {
4309         for (j = 0; j < files; j++)
4310           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4311         if(gameInfo.holdingsWidth > 1) {
4312              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4313              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4314         }
4315       }
4316       CopyBoard(partnerBoard, board);
4317       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4318         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4319         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4320       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4321       if(toSqr = strchr(str, '-')) {
4322         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4323         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4324       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4325       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4326       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4327       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4328       if(twoBoards) {
4329           DisplayWhiteClock(white_time*fac, to_play == 'W');
4330           DisplayBlackClock(black_time*fac, to_play != 'W');
4331           activePartner = to_play;
4332           if(gamenum != lastBgGame) {
4333               char buf[MSG_SIZ];
4334               snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4335               DisplayTitle(buf);
4336           }
4337           lastBgGame = gamenum;
4338           activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4339                       partnerUp = 0; flipView = !flipView; } // [HGM] dual
4340       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4341                  (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4342       if(!twoBoards) DisplayMessage(partnerStatus, "");
4343         partnerBoardValid = TRUE;
4344       return;
4345     }
4346
4347     if(appData.dualBoard && appData.bgObserve) {
4348         if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4349             SendToICS(ics_prefix), SendToICS("pobserve\n");
4350         else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4351             char buf[MSG_SIZ];
4352             snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4353             SendToICS(buf);
4354         }
4355     }
4356
4357     /* Modify behavior for initial board display on move listing
4358        of wild games.
4359        */
4360     switch (ics_getting_history) {
4361       case H_FALSE:
4362       case H_REQUESTED:
4363         break;
4364       case H_GOT_REQ_HEADER:
4365       case H_GOT_UNREQ_HEADER:
4366         /* This is the initial position of the current game */
4367         gamenum = ics_gamenum;
4368         moveNum = 0;            /* old ICS bug workaround */
4369         if (to_play == 'B') {
4370           startedFromSetupPosition = TRUE;
4371           blackPlaysFirst = TRUE;
4372           moveNum = 1;
4373           if (forwardMostMove == 0) forwardMostMove = 1;
4374           if (backwardMostMove == 0) backwardMostMove = 1;
4375           if (currentMove == 0) currentMove = 1;
4376         }
4377         newGameMode = gameMode;
4378         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4379         break;
4380       case H_GOT_UNWANTED_HEADER:
4381         /* This is an initial board that we don't want */
4382         return;
4383       case H_GETTING_MOVES:
4384         /* Should not happen */
4385         DisplayError(_("Error gathering move list: extra board"), 0);
4386         ics_getting_history = H_FALSE;
4387         return;
4388     }
4389
4390    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4391                                         move_str[1] == '@' && !gameInfo.holdingsWidth ||
4392                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4393      /* [HGM] We seem to have switched variant unexpectedly
4394       * Try to guess new variant from board size
4395       */
4396           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4397           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4398           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4399           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4400           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4401           if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4402           if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4403           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4404           /* Get a move list just to see the header, which
4405              will tell us whether this is really bug or zh */
4406           if (ics_getting_history == H_FALSE) {
4407             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4408             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4409             SendToICS(str);
4410           }
4411     }
4412
4413     /* Take action if this is the first board of a new game, or of a
4414        different game than is currently being displayed.  */
4415     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4416         relation == RELATION_ISOLATED_BOARD) {
4417
4418         /* Forget the old game and get the history (if any) of the new one */
4419         if (gameMode != BeginningOfGame) {
4420           Reset(TRUE, TRUE);
4421         }
4422         newGame = TRUE;
4423         if (appData.autoRaiseBoard) BoardToTop();
4424         prevMove = -3;
4425         if (gamenum == -1) {
4426             newGameMode = IcsIdle;
4427         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4428                    appData.getMoveList && !reqFlag) {
4429             /* Need to get game history */
4430             ics_getting_history = H_REQUESTED;
4431             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4432             SendToICS(str);
4433         }
4434
4435         /* Initially flip the board to have black on the bottom if playing
4436            black or if the ICS flip flag is set, but let the user change
4437            it with the Flip View button. */
4438         flipView = appData.autoFlipView ?
4439           (newGameMode == IcsPlayingBlack) || ics_flip :
4440           appData.flipView;
4441
4442         /* Done with values from previous mode; copy in new ones */
4443         gameMode = newGameMode;
4444         ModeHighlight();
4445         ics_gamenum = gamenum;
4446         if (gamenum == gs_gamenum) {
4447             int klen = strlen(gs_kind);
4448             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4449             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4450             gameInfo.event = StrSave(str);
4451         } else {
4452             gameInfo.event = StrSave("ICS game");
4453         }
4454         gameInfo.site = StrSave(appData.icsHost);
4455         gameInfo.date = PGNDate();
4456         gameInfo.round = StrSave("-");
4457         gameInfo.white = StrSave(white);
4458         gameInfo.black = StrSave(black);
4459         timeControl = basetime * 60 * 1000;
4460         timeControl_2 = 0;
4461         timeIncrement = increment * 1000;
4462         movesPerSession = 0;
4463         gameInfo.timeControl = TimeControlTagValue();
4464         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4465   if (appData.debugMode) {
4466     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4467     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4468     setbuf(debugFP, NULL);
4469   }
4470
4471         gameInfo.outOfBook = NULL;
4472
4473         /* Do we have the ratings? */
4474         if (strcmp(player1Name, white) == 0 &&
4475             strcmp(player2Name, black) == 0) {
4476             if (appData.debugMode)
4477               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4478                       player1Rating, player2Rating);
4479             gameInfo.whiteRating = player1Rating;
4480             gameInfo.blackRating = player2Rating;
4481         } else if (strcmp(player2Name, white) == 0 &&
4482                    strcmp(player1Name, black) == 0) {
4483             if (appData.debugMode)
4484               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4485                       player2Rating, player1Rating);
4486             gameInfo.whiteRating = player2Rating;
4487             gameInfo.blackRating = player1Rating;
4488         }
4489         player1Name[0] = player2Name[0] = NULLCHAR;
4490
4491         /* Silence shouts if requested */
4492         if (appData.quietPlay &&
4493             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4494             SendToICS(ics_prefix);
4495             SendToICS("set shout 0\n");
4496         }
4497     }
4498
4499     /* Deal with midgame name changes */
4500     if (!newGame) {
4501         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4502             if (gameInfo.white) free(gameInfo.white);
4503             gameInfo.white = StrSave(white);
4504         }
4505         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4506             if (gameInfo.black) free(gameInfo.black);
4507             gameInfo.black = StrSave(black);
4508         }
4509     }
4510
4511     /* Throw away game result if anything actually changes in examine mode */
4512     if (gameMode == IcsExamining && !newGame) {
4513         gameInfo.result = GameUnfinished;
4514         if (gameInfo.resultDetails != NULL) {
4515             free(gameInfo.resultDetails);
4516             gameInfo.resultDetails = NULL;
4517         }
4518     }
4519
4520     /* In pausing && IcsExamining mode, we ignore boards coming
4521        in if they are in a different variation than we are. */
4522     if (pauseExamInvalid) return;
4523     if (pausing && gameMode == IcsExamining) {
4524         if (moveNum <= pauseExamForwardMostMove) {
4525             pauseExamInvalid = TRUE;
4526             forwardMostMove = pauseExamForwardMostMove;
4527             return;
4528         }
4529     }
4530
4531   if (appData.debugMode) {
4532     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4533   }
4534     /* Parse the board */
4535     for (k = 0; k < ranks; k++) {
4536       for (j = 0; j < files; j++)
4537         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4538       if(gameInfo.holdingsWidth > 1) {
4539            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4540            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4541       }
4542     }
4543     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4544       board[5][BOARD_RGHT+1] = WhiteAngel;
4545       board[6][BOARD_RGHT+1] = WhiteMarshall;
4546       board[1][0] = BlackMarshall;
4547       board[2][0] = BlackAngel;
4548       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4549     }
4550     CopyBoard(boards[moveNum], board);
4551     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4552     if (moveNum == 0) {
4553         startedFromSetupPosition =
4554           !CompareBoards(board, initialPosition);
4555         if(startedFromSetupPosition)
4556             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4557     }
4558
4559     /* [HGM] Set castling rights. Take the outermost Rooks,
4560        to make it also work for FRC opening positions. Note that board12
4561        is really defective for later FRC positions, as it has no way to
4562        indicate which Rook can castle if they are on the same side of King.
4563        For the initial position we grant rights to the outermost Rooks,
4564        and remember thos rights, and we then copy them on positions
4565        later in an FRC game. This means WB might not recognize castlings with
4566        Rooks that have moved back to their original position as illegal,
4567        but in ICS mode that is not its job anyway.
4568     */
4569     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4570     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4571
4572         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4573             if(board[0][i] == WhiteRook) j = i;
4574         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4575         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4576             if(board[0][i] == WhiteRook) j = i;
4577         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4578         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4579             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4580         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4581         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4582             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4583         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4584
4585         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4586         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4587         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4588             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4589         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4590             if(board[BOARD_HEIGHT-1][k] == bKing)
4591                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4592         if(gameInfo.variant == VariantTwoKings) {
4593             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4594             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4595             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4596         }
4597     } else { int r;
4598         r = boards[moveNum][CASTLING][0] = initialRights[0];
4599         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4600         r = boards[moveNum][CASTLING][1] = initialRights[1];
4601         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4602         r = boards[moveNum][CASTLING][3] = initialRights[3];
4603         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4604         r = boards[moveNum][CASTLING][4] = initialRights[4];
4605         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4606         /* wildcastle kludge: always assume King has rights */
4607         r = boards[moveNum][CASTLING][2] = initialRights[2];
4608         r = boards[moveNum][CASTLING][5] = initialRights[5];
4609     }
4610     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4611     boards[moveNum][EP_STATUS] = EP_NONE;
4612     if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4613     if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4614     if(double_push !=  -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4615
4616
4617     if (ics_getting_history == H_GOT_REQ_HEADER ||
4618         ics_getting_history == H_GOT_UNREQ_HEADER) {
4619         /* This was an initial position from a move list, not
4620            the current position */
4621         return;
4622     }
4623
4624     /* Update currentMove and known move number limits */
4625     newMove = newGame || moveNum > forwardMostMove;
4626
4627     if (newGame) {
4628         forwardMostMove = backwardMostMove = currentMove = moveNum;
4629         if (gameMode == IcsExamining && moveNum == 0) {
4630           /* Workaround for ICS limitation: we are not told the wild
4631              type when starting to examine a game.  But if we ask for
4632              the move list, the move list header will tell us */
4633             ics_getting_history = H_REQUESTED;
4634             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4635             SendToICS(str);
4636         }
4637     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4638                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4639 #if ZIPPY
4640         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4641         /* [HGM] applied this also to an engine that is silently watching        */
4642         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4643             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4644             gameInfo.variant == currentlyInitializedVariant) {
4645           takeback = forwardMostMove - moveNum;
4646           for (i = 0; i < takeback; i++) {
4647             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4648             SendToProgram("undo\n", &first);
4649           }
4650         }
4651 #endif
4652
4653         forwardMostMove = moveNum;
4654         if (!pausing || currentMove > forwardMostMove)
4655           currentMove = forwardMostMove;
4656     } else {
4657         /* New part of history that is not contiguous with old part */
4658         if (pausing && gameMode == IcsExamining) {
4659             pauseExamInvalid = TRUE;
4660             forwardMostMove = pauseExamForwardMostMove;
4661             return;
4662         }
4663         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4664 #if ZIPPY
4665             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4666                 // [HGM] when we will receive the move list we now request, it will be
4667                 // fed to the engine from the first move on. So if the engine is not
4668                 // in the initial position now, bring it there.
4669                 InitChessProgram(&first, 0);
4670             }
4671 #endif
4672             ics_getting_history = H_REQUESTED;
4673             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4674             SendToICS(str);
4675         }
4676         forwardMostMove = backwardMostMove = currentMove = moveNum;
4677     }
4678
4679     /* Update the clocks */
4680     if (strchr(elapsed_time, '.')) {
4681       /* Time is in ms */
4682       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4683       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4684     } else {
4685       /* Time is in seconds */
4686       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4687       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4688     }
4689
4690
4691 #if ZIPPY
4692     if (appData.zippyPlay && newGame &&
4693         gameMode != IcsObserving && gameMode != IcsIdle &&
4694         gameMode != IcsExamining)
4695       ZippyFirstBoard(moveNum, basetime, increment);
4696 #endif
4697
4698     /* Put the move on the move list, first converting
4699        to canonical algebraic form. */
4700     if (moveNum > 0) {
4701   if (appData.debugMode) {
4702     int f = forwardMostMove;
4703     fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4704             boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4705             boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4706     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4707     fprintf(debugFP, "moveNum = %d\n", moveNum);
4708     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4709     setbuf(debugFP, NULL);
4710   }
4711         if (moveNum <= backwardMostMove) {
4712             /* We don't know what the board looked like before
4713                this move.  Punt. */
4714           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4715             strcat(parseList[moveNum - 1], " ");
4716             strcat(parseList[moveNum - 1], elapsed_time);
4717             moveList[moveNum - 1][0] = NULLCHAR;
4718         } else if (strcmp(move_str, "none") == 0) {
4719             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4720             /* Again, we don't know what the board looked like;
4721                this is really the start of the game. */
4722             parseList[moveNum - 1][0] = NULLCHAR;
4723             moveList[moveNum - 1][0] = NULLCHAR;
4724             backwardMostMove = moveNum;
4725             startedFromSetupPosition = TRUE;
4726             fromX = fromY = toX = toY = -1;
4727         } else {
4728           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4729           //                 So we parse the long-algebraic move string in stead of the SAN move
4730           int valid; char buf[MSG_SIZ], *prom;
4731
4732           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4733                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4734           // str looks something like "Q/a1-a2"; kill the slash
4735           if(str[1] == '/')
4736             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4737           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4738           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4739                 strcat(buf, prom); // long move lacks promo specification!
4740           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4741                 if(appData.debugMode)
4742                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4743                 safeStrCpy(move_str, buf, MSG_SIZ);
4744           }
4745           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4746                                 &fromX, &fromY, &toX, &toY, &promoChar)
4747                || ParseOneMove(buf, moveNum - 1, &moveType,
4748                                 &fromX, &fromY, &toX, &toY, &promoChar);
4749           // end of long SAN patch
4750           if (valid) {
4751             (void) CoordsToAlgebraic(boards[moveNum - 1],
4752                                      PosFlags(moveNum - 1),
4753                                      fromY, fromX, toY, toX, promoChar,
4754                                      parseList[moveNum-1]);
4755             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4756               case MT_NONE:
4757               case MT_STALEMATE:
4758               default:
4759                 break;
4760               case MT_CHECK:
4761                 if(gameInfo.variant != VariantShogi)
4762                     strcat(parseList[moveNum - 1], "+");
4763                 break;
4764               case MT_CHECKMATE:
4765               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4766                 strcat(parseList[moveNum - 1], "#");
4767                 break;
4768             }
4769             strcat(parseList[moveNum - 1], " ");
4770             strcat(parseList[moveNum - 1], elapsed_time);
4771             /* currentMoveString is set as a side-effect of ParseOneMove */
4772             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4773             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4774             strcat(moveList[moveNum - 1], "\n");
4775
4776             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4777                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4778               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4779                 ChessSquare old, new = boards[moveNum][k][j];
4780                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4781                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4782                   if(old == new) continue;
4783                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4784                   else if(new == WhiteWazir || new == BlackWazir) {
4785                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4786                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4787                       else boards[moveNum][k][j] = old; // preserve type of Gold
4788                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4789                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4790               }
4791           } else {
4792             /* Move from ICS was illegal!?  Punt. */
4793             if (appData.debugMode) {
4794               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4795               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4796             }
4797             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4798             strcat(parseList[moveNum - 1], " ");
4799             strcat(parseList[moveNum - 1], elapsed_time);
4800             moveList[moveNum - 1][0] = NULLCHAR;
4801             fromX = fromY = toX = toY = -1;
4802           }
4803         }
4804   if (appData.debugMode) {
4805     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4806     setbuf(debugFP, NULL);
4807   }
4808
4809 #if ZIPPY
4810         /* Send move to chess program (BEFORE animating it). */
4811         if (appData.zippyPlay && !newGame && newMove &&
4812            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4813
4814             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4815                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4816                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4817                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4818                             move_str);
4819                     DisplayError(str, 0);
4820                 } else {
4821                     if (first.sendTime) {
4822                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4823                     }
4824                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4825                     if (firstMove && !bookHit) {
4826                         firstMove = FALSE;
4827                         if (first.useColors) {
4828                           SendToProgram(gameMode == IcsPlayingWhite ?
4829                                         "white\ngo\n" :
4830                                         "black\ngo\n", &first);
4831                         } else {
4832                           SendToProgram("go\n", &first);
4833                         }
4834                         first.maybeThinking = TRUE;
4835                     }
4836                 }
4837             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4838               if (moveList[moveNum - 1][0] == NULLCHAR) {
4839                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4840                 DisplayError(str, 0);
4841               } else {
4842                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4843                 SendMoveToProgram(moveNum - 1, &first);
4844               }
4845             }
4846         }
4847 #endif
4848     }
4849
4850     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4851         /* If move comes from a remote source, animate it.  If it
4852            isn't remote, it will have already been animated. */
4853         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4854             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4855         }
4856         if (!pausing && appData.highlightLastMove) {
4857             SetHighlights(fromX, fromY, toX, toY);
4858         }
4859     }
4860
4861     /* Start the clocks */
4862     whiteFlag = blackFlag = FALSE;
4863     appData.clockMode = !(basetime == 0 && increment == 0);
4864     if (ticking == 0) {
4865       ics_clock_paused = TRUE;
4866       StopClocks();
4867     } else if (ticking == 1) {
4868       ics_clock_paused = FALSE;
4869     }
4870     if (gameMode == IcsIdle ||
4871         relation == RELATION_OBSERVING_STATIC ||
4872         relation == RELATION_EXAMINING ||
4873         ics_clock_paused)
4874       DisplayBothClocks();
4875     else
4876       StartClocks();
4877
4878     /* Display opponents and material strengths */
4879     if (gameInfo.variant != VariantBughouse &&
4880         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4881         if (tinyLayout || smallLayout) {
4882             if(gameInfo.variant == VariantNormal)
4883               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4884                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4885                     basetime, increment);
4886             else
4887               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4888                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4889                     basetime, increment, (int) gameInfo.variant);
4890         } else {
4891             if(gameInfo.variant == VariantNormal)
4892               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4893                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4894                     basetime, increment);
4895             else
4896               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
4897                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4898                     basetime, increment, VariantName(gameInfo.variant));
4899         }
4900         DisplayTitle(str);
4901   if (appData.debugMode) {
4902     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4903   }
4904     }
4905
4906
4907     /* Display the board */
4908     if (!pausing && !appData.noGUI) {
4909
4910       if (appData.premove)
4911           if (!gotPremove ||
4912              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4913              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4914               ClearPremoveHighlights();
4915
4916       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4917         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4918       DrawPosition(j, boards[currentMove]);
4919
4920       DisplayMove(moveNum - 1);
4921       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4922             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4923               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4924         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4925       }
4926     }
4927
4928     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4929 #if ZIPPY
4930     if(bookHit) { // [HGM] book: simulate book reply
4931         static char bookMove[MSG_SIZ]; // a bit generous?
4932
4933         programStats.nodes = programStats.depth = programStats.time =
4934         programStats.score = programStats.got_only_move = 0;
4935         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4936
4937         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4938         strcat(bookMove, bookHit);
4939         HandleMachineMove(bookMove, &first);
4940     }
4941 #endif
4942 }
4943
4944 void
4945 GetMoveListEvent ()
4946 {
4947     char buf[MSG_SIZ];
4948     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4949         ics_getting_history = H_REQUESTED;
4950         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4951         SendToICS(buf);
4952     }
4953 }
4954
4955 void
4956 SendToBoth (char *msg)
4957 {   // to make it easy to keep two engines in step in dual analysis
4958     SendToProgram(msg, &first);
4959     if(second.analyzing) SendToProgram(msg, &second);
4960 }
4961
4962 void
4963 AnalysisPeriodicEvent (int force)
4964 {
4965     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4966          && !force) || !appData.periodicUpdates)
4967       return;
4968
4969     /* Send . command to Crafty to collect stats */
4970     SendToBoth(".\n");
4971
4972     /* Don't send another until we get a response (this makes
4973        us stop sending to old Crafty's which don't understand
4974        the "." command (sending illegal cmds resets node count & time,
4975        which looks bad)) */
4976     programStats.ok_to_send = 0;
4977 }
4978
4979 void
4980 ics_update_width (int new_width)
4981 {
4982         ics_printf("set width %d\n", new_width);
4983 }
4984
4985 void
4986 SendMoveToProgram (int moveNum, ChessProgramState *cps)
4987 {
4988     char buf[MSG_SIZ];
4989
4990     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
4991         // null move in variant where engine does not understand it (for analysis purposes)
4992         SendBoard(cps, moveNum + 1); // send position after move in stead.
4993         return;
4994     }
4995     if (cps->useUsermove) {
4996       SendToProgram("usermove ", cps);
4997     }
4998     if (cps->useSAN) {
4999       char *space;
5000       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
5001         int len = space - parseList[moveNum];
5002         memcpy(buf, parseList[moveNum], len);
5003         buf[len++] = '\n';
5004         buf[len] = NULLCHAR;
5005       } else {
5006         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
5007       }
5008       SendToProgram(buf, cps);
5009     } else {
5010       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
5011         AlphaRank(moveList[moveNum], 4);
5012         SendToProgram(moveList[moveNum], cps);
5013         AlphaRank(moveList[moveNum], 4); // and back
5014       } else
5015       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
5016        * the engine. It would be nice to have a better way to identify castle
5017        * moves here. */
5018       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
5019                                                                          && cps->useOOCastle) {
5020         int fromX = moveList[moveNum][0] - AAA;
5021         int fromY = moveList[moveNum][1] - ONE;
5022         int toX = moveList[moveNum][2] - AAA;
5023         int toY = moveList[moveNum][3] - ONE;
5024         if((boards[moveNum][fromY][fromX] == WhiteKing
5025             && boards[moveNum][toY][toX] == WhiteRook)
5026            || (boards[moveNum][fromY][fromX] == BlackKing
5027                && boards[moveNum][toY][toX] == BlackRook)) {
5028           if(toX > fromX) SendToProgram("O-O\n", cps);
5029           else SendToProgram("O-O-O\n", cps);
5030         }
5031         else SendToProgram(moveList[moveNum], cps);
5032       } else
5033       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5034         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5035           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5036           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5037                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5038         } else
5039           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5040                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5041         SendToProgram(buf, cps);
5042       }
5043       else SendToProgram(moveList[moveNum], cps);
5044       /* End of additions by Tord */
5045     }
5046
5047     /* [HGM] setting up the opening has brought engine in force mode! */
5048     /*       Send 'go' if we are in a mode where machine should play. */
5049     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5050         (gameMode == TwoMachinesPlay   ||
5051 #if ZIPPY
5052          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
5053 #endif
5054          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5055         SendToProgram("go\n", cps);
5056   if (appData.debugMode) {
5057     fprintf(debugFP, "(extra)\n");
5058   }
5059     }
5060     setboardSpoiledMachineBlack = 0;
5061 }
5062
5063 void
5064 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5065 {
5066     char user_move[MSG_SIZ];
5067     char suffix[4];
5068
5069     if(gameInfo.variant == VariantSChess && promoChar) {
5070         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5071         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5072     } else suffix[0] = NULLCHAR;
5073
5074     switch (moveType) {
5075       default:
5076         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5077                 (int)moveType, fromX, fromY, toX, toY);
5078         DisplayError(user_move + strlen("say "), 0);
5079         break;
5080       case WhiteKingSideCastle:
5081       case BlackKingSideCastle:
5082       case WhiteQueenSideCastleWild:
5083       case BlackQueenSideCastleWild:
5084       /* PUSH Fabien */
5085       case WhiteHSideCastleFR:
5086       case BlackHSideCastleFR:
5087       /* POP Fabien */
5088         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5089         break;
5090       case WhiteQueenSideCastle:
5091       case BlackQueenSideCastle:
5092       case WhiteKingSideCastleWild:
5093       case BlackKingSideCastleWild:
5094       /* PUSH Fabien */
5095       case WhiteASideCastleFR:
5096       case BlackASideCastleFR:
5097       /* POP Fabien */
5098         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5099         break;
5100       case WhiteNonPromotion:
5101       case BlackNonPromotion:
5102         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5103         break;
5104       case WhitePromotion:
5105       case BlackPromotion:
5106         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
5107            gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
5108           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5109                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5110                 PieceToChar(WhiteFerz));
5111         else if(gameInfo.variant == VariantGreat)
5112           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5113                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5114                 PieceToChar(WhiteMan));
5115         else
5116           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5117                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5118                 promoChar);
5119         break;
5120       case WhiteDrop:
5121       case BlackDrop:
5122       drop:
5123         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5124                  ToUpper(PieceToChar((ChessSquare) fromX)),
5125                  AAA + toX, ONE + toY);
5126         break;
5127       case IllegalMove:  /* could be a variant we don't quite understand */
5128         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5129       case NormalMove:
5130       case WhiteCapturesEnPassant:
5131       case BlackCapturesEnPassant:
5132         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5133                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5134         break;
5135     }
5136     SendToICS(user_move);
5137     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5138         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5139 }
5140
5141 void
5142 UploadGameEvent ()
5143 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5144     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5145     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5146     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5147       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5148       return;
5149     }
5150     if(gameMode != IcsExamining) { // is this ever not the case?
5151         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5152
5153         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5154           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5155         } else { // on FICS we must first go to general examine mode
5156           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5157         }
5158         if(gameInfo.variant != VariantNormal) {
5159             // try figure out wild number, as xboard names are not always valid on ICS
5160             for(i=1; i<=36; i++) {
5161               snprintf(buf, MSG_SIZ, "wild/%d", i);
5162                 if(StringToVariant(buf) == gameInfo.variant) break;
5163             }
5164             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5165             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5166             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5167         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5168         SendToICS(ics_prefix);
5169         SendToICS(buf);
5170         if(startedFromSetupPosition || backwardMostMove != 0) {
5171           fen = PositionToFEN(backwardMostMove, NULL, 1);
5172           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5173             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5174             SendToICS(buf);
5175           } else { // FICS: everything has to set by separate bsetup commands
5176             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5177             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5178             SendToICS(buf);
5179             if(!WhiteOnMove(backwardMostMove)) {
5180                 SendToICS("bsetup tomove black\n");
5181             }
5182             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5183             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5184             SendToICS(buf);
5185             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5186             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5187             SendToICS(buf);
5188             i = boards[backwardMostMove][EP_STATUS];
5189             if(i >= 0) { // set e.p.
5190               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5191                 SendToICS(buf);
5192             }
5193             bsetup++;
5194           }
5195         }
5196       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5197             SendToICS("bsetup done\n"); // switch to normal examining.
5198     }
5199     for(i = backwardMostMove; i<last; i++) {
5200         char buf[20];
5201         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5202         if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5203             int len = strlen(moveList[i]);
5204             snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5205             if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5206         }
5207         SendToICS(buf);
5208     }
5209     SendToICS(ics_prefix);
5210     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5211 }
5212
5213 void
5214 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5215 {
5216     if (rf == DROP_RANK) {
5217       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5218       sprintf(move, "%c@%c%c\n",
5219                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5220     } else {
5221         if (promoChar == 'x' || promoChar == NULLCHAR) {
5222           sprintf(move, "%c%c%c%c\n",
5223                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5224         } else {
5225             sprintf(move, "%c%c%c%c%c\n",
5226                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5227         }
5228     }
5229 }
5230
5231 void
5232 ProcessICSInitScript (FILE *f)
5233 {
5234     char buf[MSG_SIZ];
5235
5236     while (fgets(buf, MSG_SIZ, f)) {
5237         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5238     }
5239
5240     fclose(f);
5241 }
5242
5243
5244 static int lastX, lastY, lastLeftX, lastLeftY, selectFlag, dragging;
5245 static ClickType lastClickType;
5246
5247 void
5248 Sweep (int step)
5249 {
5250     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5251     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5252     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5253     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5254     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5255     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5256     do {
5257         promoSweep -= step;
5258         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5259         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5260         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5261         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5262         if(!step) step = -1;
5263     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5264             appData.testLegality && (promoSweep == king ||
5265             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5266     if(toX >= 0) {
5267         int victim = boards[currentMove][toY][toX];
5268         boards[currentMove][toY][toX] = promoSweep;
5269         DrawPosition(FALSE, boards[currentMove]);
5270         boards[currentMove][toY][toX] = victim;
5271     } else
5272     ChangeDragPiece(promoSweep);
5273 }
5274
5275 int
5276 PromoScroll (int x, int y)
5277 {
5278   int step = 0;
5279
5280   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5281   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5282   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5283   if(!step) return FALSE;
5284   lastX = x; lastY = y;
5285   if((promoSweep < BlackPawn) == flipView) step = -step;
5286   if(step > 0) selectFlag = 1;
5287   if(!selectFlag) Sweep(step);
5288   return FALSE;
5289 }
5290
5291 void
5292 NextPiece (int step)
5293 {
5294     ChessSquare piece = boards[currentMove][toY][toX];
5295     do {
5296         pieceSweep -= step;
5297         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5298         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5299         if(!step) step = -1;
5300     } while(PieceToChar(pieceSweep) == '.');
5301     boards[currentMove][toY][toX] = pieceSweep;
5302     DrawPosition(FALSE, boards[currentMove]);
5303     boards[currentMove][toY][toX] = piece;
5304 }
5305 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5306 void
5307 AlphaRank (char *move, int n)
5308 {
5309 //    char *p = move, c; int x, y;
5310
5311     if (appData.debugMode) {
5312         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5313     }
5314
5315     if(move[1]=='*' &&
5316        move[2]>='0' && move[2]<='9' &&
5317        move[3]>='a' && move[3]<='x'    ) {
5318         move[1] = '@';
5319         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5320         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5321     } else
5322     if(move[0]>='0' && move[0]<='9' &&
5323        move[1]>='a' && move[1]<='x' &&
5324        move[2]>='0' && move[2]<='9' &&
5325        move[3]>='a' && move[3]<='x'    ) {
5326         /* input move, Shogi -> normal */
5327         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5328         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5329         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5330         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5331     } else
5332     if(move[1]=='@' &&
5333        move[3]>='0' && move[3]<='9' &&
5334        move[2]>='a' && move[2]<='x'    ) {
5335         move[1] = '*';
5336         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5337         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5338     } else
5339     if(
5340        move[0]>='a' && move[0]<='x' &&
5341        move[3]>='0' && move[3]<='9' &&
5342        move[2]>='a' && move[2]<='x'    ) {
5343          /* output move, normal -> Shogi */
5344         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5345         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5346         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5347         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5348         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5349     }
5350     if (appData.debugMode) {
5351         fprintf(debugFP, "   out = '%s'\n", move);
5352     }
5353 }
5354
5355 char yy_textstr[8000];
5356
5357 /* Parser for moves from gnuchess, ICS, or user typein box */
5358 Boolean
5359 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5360 {
5361     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5362
5363     switch (*moveType) {
5364       case WhitePromotion:
5365       case BlackPromotion:
5366       case WhiteNonPromotion:
5367       case BlackNonPromotion:
5368       case NormalMove:
5369       case WhiteCapturesEnPassant:
5370       case BlackCapturesEnPassant:
5371       case WhiteKingSideCastle:
5372       case WhiteQueenSideCastle:
5373       case BlackKingSideCastle:
5374       case BlackQueenSideCastle:
5375       case WhiteKingSideCastleWild:
5376       case WhiteQueenSideCastleWild:
5377       case BlackKingSideCastleWild:
5378       case BlackQueenSideCastleWild:
5379       /* Code added by Tord: */
5380       case WhiteHSideCastleFR:
5381       case WhiteASideCastleFR:
5382       case BlackHSideCastleFR:
5383       case BlackASideCastleFR:
5384       /* End of code added by Tord */
5385       case IllegalMove:         /* bug or odd chess variant */
5386         *fromX = currentMoveString[0] - AAA;
5387         *fromY = currentMoveString[1] - ONE;
5388         *toX = currentMoveString[2] - AAA;
5389         *toY = currentMoveString[3] - ONE;
5390         *promoChar = currentMoveString[4];
5391         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5392             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5393     if (appData.debugMode) {
5394         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5395     }
5396             *fromX = *fromY = *toX = *toY = 0;
5397             return FALSE;
5398         }
5399         if (appData.testLegality) {
5400           return (*moveType != IllegalMove);
5401         } else {
5402           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5403                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5404         }
5405
5406       case WhiteDrop:
5407       case BlackDrop:
5408         *fromX = *moveType == WhiteDrop ?
5409           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5410           (int) CharToPiece(ToLower(currentMoveString[0]));
5411         *fromY = DROP_RANK;
5412         *toX = currentMoveString[2] - AAA;
5413         *toY = currentMoveString[3] - ONE;
5414         *promoChar = NULLCHAR;
5415         return TRUE;
5416
5417       case AmbiguousMove:
5418       case ImpossibleMove:
5419       case EndOfFile:
5420       case ElapsedTime:
5421       case Comment:
5422       case PGNTag:
5423       case NAG:
5424       case WhiteWins:
5425       case BlackWins:
5426       case GameIsDrawn:
5427       default:
5428     if (appData.debugMode) {
5429         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5430     }
5431         /* bug? */
5432         *fromX = *fromY = *toX = *toY = 0;
5433         *promoChar = NULLCHAR;
5434         return FALSE;
5435     }
5436 }
5437
5438 Boolean pushed = FALSE;
5439 char *lastParseAttempt;
5440
5441 void
5442 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5443 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5444   int fromX, fromY, toX, toY; char promoChar;
5445   ChessMove moveType;
5446   Boolean valid;
5447   int nr = 0;
5448
5449   lastParseAttempt = pv; if(!*pv) return;    // turns out we crash when we parse an empty PV
5450   if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5451     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5452     pushed = TRUE;
5453   }
5454   endPV = forwardMostMove;
5455   do {
5456     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5457     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5458     lastParseAttempt = pv;
5459     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5460     if(!valid && nr == 0 &&
5461        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5462         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5463         // Hande case where played move is different from leading PV move
5464         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5465         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5466         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5467         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5468           endPV += 2; // if position different, keep this
5469           moveList[endPV-1][0] = fromX + AAA;
5470           moveList[endPV-1][1] = fromY + ONE;
5471           moveList[endPV-1][2] = toX + AAA;
5472           moveList[endPV-1][3] = toY + ONE;
5473           parseList[endPV-1][0] = NULLCHAR;
5474           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5475         }
5476       }
5477     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5478     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5479     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5480     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5481         valid++; // allow comments in PV
5482         continue;
5483     }
5484     nr++;
5485     if(endPV+1 > framePtr) break; // no space, truncate
5486     if(!valid) break;
5487     endPV++;
5488     CopyBoard(boards[endPV], boards[endPV-1]);
5489     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5490     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5491     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5492     CoordsToAlgebraic(boards[endPV - 1],
5493                              PosFlags(endPV - 1),
5494                              fromY, fromX, toY, toX, promoChar,
5495                              parseList[endPV - 1]);
5496   } while(valid);
5497   if(atEnd == 2) return; // used hidden, for PV conversion
5498   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5499   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5500   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5501                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5502   DrawPosition(TRUE, boards[currentMove]);
5503 }
5504
5505 int
5506 MultiPV (ChessProgramState *cps)
5507 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5508         int i;
5509         for(i=0; i<cps->nrOptions; i++)
5510             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5511                 return i;
5512         return -1;
5513 }
5514
5515 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
5516
5517 Boolean
5518 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5519 {
5520         int startPV, multi, lineStart, origIndex = index;
5521         char *p, buf2[MSG_SIZ];
5522         ChessProgramState *cps = (pane ? &second : &first);
5523
5524         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5525         lastX = x; lastY = y;
5526         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5527         lineStart = startPV = index;
5528         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5529         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5530         index = startPV;
5531         do{ while(buf[index] && buf[index] != '\n') index++;
5532         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5533         buf[index] = 0;
5534         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(cps)) >= 0) {
5535                 int n = cps->option[multi].value;
5536                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5537                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5538                 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5539                 cps->option[multi].value = n;
5540                 *start = *end = 0;
5541                 return FALSE;
5542         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5543                 ExcludeClick(origIndex - lineStart);
5544                 return FALSE;
5545         }
5546         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5547         *start = startPV; *end = index-1;
5548         extendGame = (gameMode == AnalyzeMode && appData.autoExtend);
5549         return TRUE;
5550 }
5551
5552 char *
5553 PvToSAN (char *pv)
5554 {
5555         static char buf[10*MSG_SIZ];
5556         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5557         *buf = NULLCHAR;
5558         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5559         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5560         for(i = forwardMostMove; i<endPV; i++){
5561             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5562             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5563             k += strlen(buf+k);
5564         }
5565         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5566         if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5567         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5568         endPV = savedEnd;
5569         return buf;
5570 }
5571
5572 Boolean
5573 LoadPV (int x, int y)
5574 { // called on right mouse click to load PV
5575   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5576   lastX = x; lastY = y;
5577   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5578   extendGame = FALSE;
5579   return TRUE;
5580 }
5581
5582 void
5583 UnLoadPV ()
5584 {
5585   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5586   if(endPV < 0) return;
5587   if(appData.autoCopyPV) CopyFENToClipboard();
5588   endPV = -1;
5589   if(extendGame && currentMove > forwardMostMove) {
5590         Boolean saveAnimate = appData.animate;
5591         if(pushed) {
5592             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5593                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5594             } else storedGames--; // abandon shelved tail of original game
5595         }
5596         pushed = FALSE;
5597         forwardMostMove = currentMove;
5598         currentMove = oldFMM;
5599         appData.animate = FALSE;
5600         ToNrEvent(forwardMostMove);
5601         appData.animate = saveAnimate;
5602   }
5603   currentMove = forwardMostMove;
5604   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5605   ClearPremoveHighlights();
5606   DrawPosition(TRUE, boards[currentMove]);
5607 }
5608
5609 void
5610 MovePV (int x, int y, int h)
5611 { // step through PV based on mouse coordinates (called on mouse move)
5612   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5613
5614   // we must somehow check if right button is still down (might be released off board!)
5615   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5616   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5617   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5618   if(!step) return;
5619   lastX = x; lastY = y;
5620
5621   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5622   if(endPV < 0) return;
5623   if(y < margin) step = 1; else
5624   if(y > h - margin) step = -1;
5625   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5626   currentMove += step;
5627   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5628   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5629                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5630   DrawPosition(FALSE, boards[currentMove]);
5631 }
5632
5633
5634 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5635 // All positions will have equal probability, but the current method will not provide a unique
5636 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5637 #define DARK 1
5638 #define LITE 2
5639 #define ANY 3
5640
5641 int squaresLeft[4];
5642 int piecesLeft[(int)BlackPawn];
5643 int seed, nrOfShuffles;
5644
5645 void
5646 GetPositionNumber ()
5647 {       // sets global variable seed
5648         int i;
5649
5650         seed = appData.defaultFrcPosition;
5651         if(seed < 0) { // randomize based on time for negative FRC position numbers
5652                 for(i=0; i<50; i++) seed += random();
5653                 seed = random() ^ random() >> 8 ^ random() << 8;
5654                 if(seed<0) seed = -seed;
5655         }
5656 }
5657
5658 int
5659 put (Board board, int pieceType, int rank, int n, int shade)
5660 // put the piece on the (n-1)-th empty squares of the given shade
5661 {
5662         int i;
5663
5664         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5665                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5666                         board[rank][i] = (ChessSquare) pieceType;
5667                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5668                         squaresLeft[ANY]--;
5669                         piecesLeft[pieceType]--;
5670                         return i;
5671                 }
5672         }
5673         return -1;
5674 }
5675
5676
5677 void
5678 AddOnePiece (Board board, int pieceType, int rank, int shade)
5679 // calculate where the next piece goes, (any empty square), and put it there
5680 {
5681         int i;
5682
5683         i = seed % squaresLeft[shade];
5684         nrOfShuffles *= squaresLeft[shade];
5685         seed /= squaresLeft[shade];
5686         put(board, pieceType, rank, i, shade);
5687 }
5688
5689 void
5690 AddTwoPieces (Board board, int pieceType, int rank)
5691 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5692 {
5693         int i, n=squaresLeft[ANY], j=n-1, k;
5694
5695         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5696         i = seed % k;  // pick one
5697         nrOfShuffles *= k;
5698         seed /= k;
5699         while(i >= j) i -= j--;
5700         j = n - 1 - j; i += j;
5701         put(board, pieceType, rank, j, ANY);
5702         put(board, pieceType, rank, i, ANY);
5703 }
5704
5705 void
5706 SetUpShuffle (Board board, int number)
5707 {
5708         int i, p, first=1;
5709
5710         GetPositionNumber(); nrOfShuffles = 1;
5711
5712         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5713         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5714         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5715
5716         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5717
5718         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5719             p = (int) board[0][i];
5720             if(p < (int) BlackPawn) piecesLeft[p] ++;
5721             board[0][i] = EmptySquare;
5722         }
5723
5724         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5725             // shuffles restricted to allow normal castling put KRR first
5726             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5727                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5728             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5729                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5730             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5731                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5732             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5733                 put(board, WhiteRook, 0, 0, ANY);
5734             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5735         }
5736
5737         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5738             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5739             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5740                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5741                 while(piecesLeft[p] >= 2) {
5742                     AddOnePiece(board, p, 0, LITE);
5743                     AddOnePiece(board, p, 0, DARK);
5744                 }
5745                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5746             }
5747
5748         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5749             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5750             // but we leave King and Rooks for last, to possibly obey FRC restriction
5751             if(p == (int)WhiteRook) continue;
5752             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5753             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5754         }
5755
5756         // now everything is placed, except perhaps King (Unicorn) and Rooks
5757
5758         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5759             // Last King gets castling rights
5760             while(piecesLeft[(int)WhiteUnicorn]) {
5761                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5762                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5763             }
5764
5765             while(piecesLeft[(int)WhiteKing]) {
5766                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5767                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5768             }
5769
5770
5771         } else {
5772             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5773             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5774         }
5775
5776         // Only Rooks can be left; simply place them all
5777         while(piecesLeft[(int)WhiteRook]) {
5778                 i = put(board, WhiteRook, 0, 0, ANY);
5779                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5780                         if(first) {
5781                                 first=0;
5782                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5783                         }
5784                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5785                 }
5786         }
5787         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5788             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5789         }
5790
5791         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5792 }
5793
5794 int
5795 SetCharTable (char *table, const char * map)
5796 /* [HGM] moved here from winboard.c because of its general usefulness */
5797 /*       Basically a safe strcpy that uses the last character as King */
5798 {
5799     int result = FALSE; int NrPieces;
5800
5801     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5802                     && NrPieces >= 12 && !(NrPieces&1)) {
5803         int i; /* [HGM] Accept even length from 12 to 34 */
5804
5805         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5806         for( i=0; i<NrPieces/2-1; i++ ) {
5807             table[i] = map[i];
5808             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5809         }
5810         table[(int) WhiteKing]  = map[NrPieces/2-1];
5811         table[(int) BlackKing]  = map[NrPieces-1];
5812
5813         result = TRUE;
5814     }
5815
5816     return result;
5817 }
5818
5819 void
5820 Prelude (Board board)
5821 {       // [HGM] superchess: random selection of exo-pieces
5822         int i, j, k; ChessSquare p;
5823         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5824
5825         GetPositionNumber(); // use FRC position number
5826
5827         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5828             SetCharTable(pieceToChar, appData.pieceToCharTable);
5829             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5830                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5831         }
5832
5833         j = seed%4;                 seed /= 4;
5834         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5835         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5836         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5837         j = seed%3 + (seed%3 >= j); seed /= 3;
5838         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5839         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5840         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5841         j = seed%3;                 seed /= 3;
5842         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5843         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5844         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5845         j = seed%2 + (seed%2 >= j); seed /= 2;
5846         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5847         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5848         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5849         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5850         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5851         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5852         put(board, exoPieces[0],    0, 0, ANY);
5853         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5854 }
5855
5856 void
5857 InitPosition (int redraw)
5858 {
5859     ChessSquare (* pieces)[BOARD_FILES];
5860     int i, j, pawnRow, overrule,
5861     oldx = gameInfo.boardWidth,
5862     oldy = gameInfo.boardHeight,
5863     oldh = gameInfo.holdingsWidth;
5864     static int oldv;
5865
5866     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5867
5868     /* [AS] Initialize pv info list [HGM] and game status */
5869     {
5870         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5871             pvInfoList[i].depth = 0;
5872             boards[i][EP_STATUS] = EP_NONE;
5873             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5874         }
5875
5876         initialRulePlies = 0; /* 50-move counter start */
5877
5878         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5879         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5880     }
5881
5882
5883     /* [HGM] logic here is completely changed. In stead of full positions */
5884     /* the initialized data only consist of the two backranks. The switch */
5885     /* selects which one we will use, which is than copied to the Board   */
5886     /* initialPosition, which for the rest is initialized by Pawns and    */
5887     /* empty squares. This initial position is then copied to boards[0],  */
5888     /* possibly after shuffling, so that it remains available.            */
5889
5890     gameInfo.holdingsWidth = 0; /* default board sizes */
5891     gameInfo.boardWidth    = 8;
5892     gameInfo.boardHeight   = 8;
5893     gameInfo.holdingsSize  = 0;
5894     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5895     for(i=0; i<BOARD_FILES-2; i++)
5896       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5897     initialPosition[EP_STATUS] = EP_NONE;
5898     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5899     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5900          SetCharTable(pieceNickName, appData.pieceNickNames);
5901     else SetCharTable(pieceNickName, "............");
5902     pieces = FIDEArray;
5903
5904     switch (gameInfo.variant) {
5905     case VariantFischeRandom:
5906       shuffleOpenings = TRUE;
5907     default:
5908       break;
5909     case VariantShatranj:
5910       pieces = ShatranjArray;
5911       nrCastlingRights = 0;
5912       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5913       break;
5914     case VariantMakruk:
5915       pieces = makrukArray;
5916       nrCastlingRights = 0;
5917       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5918       break;
5919     case VariantASEAN:
5920       pieces = aseanArray;
5921       nrCastlingRights = 0;
5922       SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk");
5923       break;
5924     case VariantTwoKings:
5925       pieces = twoKingsArray;
5926       break;
5927     case VariantGrand:
5928       pieces = GrandArray;
5929       nrCastlingRights = 0;
5930       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5931       gameInfo.boardWidth = 10;
5932       gameInfo.boardHeight = 10;
5933       gameInfo.holdingsSize = 7;
5934       break;
5935     case VariantCapaRandom:
5936       shuffleOpenings = TRUE;
5937     case VariantCapablanca:
5938       pieces = CapablancaArray;
5939       gameInfo.boardWidth = 10;
5940       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5941       break;
5942     case VariantGothic:
5943       pieces = GothicArray;
5944       gameInfo.boardWidth = 10;
5945       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5946       break;
5947     case VariantSChess:
5948       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5949       gameInfo.holdingsSize = 7;
5950       for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
5951       break;
5952     case VariantJanus:
5953       pieces = JanusArray;
5954       gameInfo.boardWidth = 10;
5955       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5956       nrCastlingRights = 6;
5957         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5958         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5959         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5960         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5961         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5962         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5963       break;
5964     case VariantFalcon:
5965       pieces = FalconArray;
5966       gameInfo.boardWidth = 10;
5967       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5968       break;
5969     case VariantXiangqi:
5970       pieces = XiangqiArray;
5971       gameInfo.boardWidth  = 9;
5972       gameInfo.boardHeight = 10;
5973       nrCastlingRights = 0;
5974       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5975       break;
5976     case VariantShogi:
5977       pieces = ShogiArray;
5978       gameInfo.boardWidth  = 9;
5979       gameInfo.boardHeight = 9;
5980       gameInfo.holdingsSize = 7;
5981       nrCastlingRights = 0;
5982       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5983       break;
5984     case VariantCourier:
5985       pieces = CourierArray;
5986       gameInfo.boardWidth  = 12;
5987       nrCastlingRights = 0;
5988       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5989       break;
5990     case VariantKnightmate:
5991       pieces = KnightmateArray;
5992       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5993       break;
5994     case VariantSpartan:
5995       pieces = SpartanArray;
5996       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5997       break;
5998     case VariantFairy:
5999       pieces = fairyArray;
6000       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
6001       break;
6002     case VariantGreat:
6003       pieces = GreatArray;
6004       gameInfo.boardWidth = 10;
6005       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
6006       gameInfo.holdingsSize = 8;
6007       break;
6008     case VariantSuper:
6009       pieces = FIDEArray;
6010       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
6011       gameInfo.holdingsSize = 8;
6012       startedFromSetupPosition = TRUE;
6013       break;
6014     case VariantCrazyhouse:
6015     case VariantBughouse:
6016       pieces = FIDEArray;
6017       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
6018       gameInfo.holdingsSize = 5;
6019       break;
6020     case VariantWildCastle:
6021       pieces = FIDEArray;
6022       /* !!?shuffle with kings guaranteed to be on d or e file */
6023       shuffleOpenings = 1;
6024       break;
6025     case VariantNoCastle:
6026       pieces = FIDEArray;
6027       nrCastlingRights = 0;
6028       /* !!?unconstrained back-rank shuffle */
6029       shuffleOpenings = 1;
6030       break;
6031     }
6032
6033     overrule = 0;
6034     if(appData.NrFiles >= 0) {
6035         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6036         gameInfo.boardWidth = appData.NrFiles;
6037     }
6038     if(appData.NrRanks >= 0) {
6039         gameInfo.boardHeight = appData.NrRanks;
6040     }
6041     if(appData.holdingsSize >= 0) {
6042         i = appData.holdingsSize;
6043         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6044         gameInfo.holdingsSize = i;
6045     }
6046     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6047     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6048         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6049
6050     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6051     if(pawnRow < 1) pawnRow = 1;
6052     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN || gameInfo.variant == VariantGrand) pawnRow = 2;
6053
6054     /* User pieceToChar list overrules defaults */
6055     if(appData.pieceToCharTable != NULL)
6056         SetCharTable(pieceToChar, appData.pieceToCharTable);
6057
6058     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6059
6060         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6061             s = (ChessSquare) 0; /* account holding counts in guard band */
6062         for( i=0; i<BOARD_HEIGHT; i++ )
6063             initialPosition[i][j] = s;
6064
6065         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6066         initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
6067         initialPosition[pawnRow][j] = WhitePawn;
6068         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6069         if(gameInfo.variant == VariantXiangqi) {
6070             if(j&1) {
6071                 initialPosition[pawnRow][j] =
6072                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6073                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6074                    initialPosition[2][j] = WhiteCannon;
6075                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6076                 }
6077             }
6078         }
6079         if(gameInfo.variant == VariantGrand) {
6080             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6081                initialPosition[0][j] = WhiteRook;
6082                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6083             }
6084         }
6085         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] =  pieces[1][j-gameInfo.holdingsWidth];
6086     }
6087     if( (gameInfo.variant == VariantShogi) && !overrule ) {
6088
6089             j=BOARD_LEFT+1;
6090             initialPosition[1][j] = WhiteBishop;
6091             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6092             j=BOARD_RGHT-2;
6093             initialPosition[1][j] = WhiteRook;
6094             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6095     }
6096
6097     if( nrCastlingRights == -1) {
6098         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6099         /*       This sets default castling rights from none to normal corners   */
6100         /* Variants with other castling rights must set them themselves above    */
6101         nrCastlingRights = 6;
6102
6103         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6104         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6105         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6106         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6107         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6108         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6109      }
6110
6111      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6112      if(gameInfo.variant == VariantGreat) { // promotion commoners
6113         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6114         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6115         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6116         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6117      }
6118      if( gameInfo.variant == VariantSChess ) {
6119       initialPosition[1][0] = BlackMarshall;
6120       initialPosition[2][0] = BlackAngel;
6121       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6122       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6123       initialPosition[1][1] = initialPosition[2][1] =
6124       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6125      }
6126   if (appData.debugMode) {
6127     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6128   }
6129     if(shuffleOpenings) {
6130         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6131         startedFromSetupPosition = TRUE;
6132     }
6133     if(startedFromPositionFile) {
6134       /* [HGM] loadPos: use PositionFile for every new game */
6135       CopyBoard(initialPosition, filePosition);
6136       for(i=0; i<nrCastlingRights; i++)
6137           initialRights[i] = filePosition[CASTLING][i];
6138       startedFromSetupPosition = TRUE;
6139     }
6140
6141     CopyBoard(boards[0], initialPosition);
6142
6143     if(oldx != gameInfo.boardWidth ||
6144        oldy != gameInfo.boardHeight ||
6145        oldv != gameInfo.variant ||
6146        oldh != gameInfo.holdingsWidth
6147                                          )
6148             InitDrawingSizes(-2 ,0);
6149
6150     oldv = gameInfo.variant;
6151     if (redraw)
6152       DrawPosition(TRUE, boards[currentMove]);
6153 }
6154
6155 void
6156 SendBoard (ChessProgramState *cps, int moveNum)
6157 {
6158     char message[MSG_SIZ];
6159
6160     if (cps->useSetboard) {
6161       char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6162       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6163       SendToProgram(message, cps);
6164       free(fen);
6165
6166     } else {
6167       ChessSquare *bp;
6168       int i, j, left=0, right=BOARD_WIDTH;
6169       /* Kludge to set black to move, avoiding the troublesome and now
6170        * deprecated "black" command.
6171        */
6172       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6173         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6174
6175       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6176
6177       SendToProgram("edit\n", cps);
6178       SendToProgram("#\n", cps);
6179       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6180         bp = &boards[moveNum][i][left];
6181         for (j = left; j < right; j++, bp++) {
6182           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6183           if ((int) *bp < (int) BlackPawn) {
6184             if(j == BOARD_RGHT+1)
6185                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6186             else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6187             if(message[0] == '+' || message[0] == '~') {
6188               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6189                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6190                         AAA + j, ONE + i);
6191             }
6192             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6193                 message[1] = BOARD_RGHT   - 1 - j + '1';
6194                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6195             }
6196             SendToProgram(message, cps);
6197           }
6198         }
6199       }
6200
6201       SendToProgram("c\n", cps);
6202       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6203         bp = &boards[moveNum][i][left];
6204         for (j = left; j < right; j++, bp++) {
6205           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6206           if (((int) *bp != (int) EmptySquare)
6207               && ((int) *bp >= (int) BlackPawn)) {
6208             if(j == BOARD_LEFT-2)
6209                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6210             else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6211                     AAA + j, ONE + i);
6212             if(message[0] == '+' || message[0] == '~') {
6213               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6214                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6215                         AAA + j, ONE + i);
6216             }
6217             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6218                 message[1] = BOARD_RGHT   - 1 - j + '1';
6219                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6220             }
6221             SendToProgram(message, cps);
6222           }
6223         }
6224       }
6225
6226       SendToProgram(".\n", cps);
6227     }
6228     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6229 }
6230
6231 char exclusionHeader[MSG_SIZ];
6232 int exCnt, excludePtr;
6233 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6234 static Exclusion excluTab[200];
6235 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6236
6237 static void
6238 WriteMap (int s)
6239 {
6240     int j;
6241     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6242     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6243 }
6244
6245 static void
6246 ClearMap ()
6247 {
6248     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6249     excludePtr = 24; exCnt = 0;
6250     WriteMap(0);
6251 }
6252
6253 static void
6254 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6255 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6256     char buf[2*MOVE_LEN], *p;
6257     Exclusion *e = excluTab;
6258     int i;
6259     for(i=0; i<exCnt; i++)
6260         if(e[i].ff == fromX && e[i].fr == fromY &&
6261            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6262     if(i == exCnt) { // was not in exclude list; add it
6263         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6264         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6265             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6266             return; // abort
6267         }
6268         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6269         excludePtr++; e[i].mark = excludePtr++;
6270         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6271         exCnt++;
6272     }
6273     exclusionHeader[e[i].mark] = state;
6274 }
6275
6276 static int
6277 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6278 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6279     char buf[MSG_SIZ];
6280     int j, k;
6281     ChessMove moveType;
6282     if((signed char)promoChar == -1) { // kludge to indicate best move
6283         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6284             return 1; // if unparsable, abort
6285     }
6286     // update exclusion map (resolving toggle by consulting existing state)
6287     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6288     j = k%8; k >>= 3;
6289     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6290     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6291          excludeMap[k] |=   1<<j;
6292     else excludeMap[k] &= ~(1<<j);
6293     // update header
6294     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6295     // inform engine
6296     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6297     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6298     SendToBoth(buf);
6299     return (state == '+');
6300 }
6301
6302 static void
6303 ExcludeClick (int index)
6304 {
6305     int i, j;
6306     Exclusion *e = excluTab;
6307     if(index < 25) { // none, best or tail clicked
6308         if(index < 13) { // none: include all
6309             WriteMap(0); // clear map
6310             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6311             SendToBoth("include all\n"); // and inform engine
6312         } else if(index > 18) { // tail
6313             if(exclusionHeader[19] == '-') { // tail was excluded
6314                 SendToBoth("include all\n");
6315                 WriteMap(0); // clear map completely
6316                 // now re-exclude selected moves
6317                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6318                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6319             } else { // tail was included or in mixed state
6320                 SendToBoth("exclude all\n");
6321                 WriteMap(0xFF); // fill map completely
6322                 // now re-include selected moves
6323                 j = 0; // count them
6324                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6325                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6326                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6327             }
6328         } else { // best
6329             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6330         }
6331     } else {
6332         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6333             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6334             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6335             break;
6336         }
6337     }
6338 }
6339
6340 ChessSquare
6341 DefaultPromoChoice (int white)
6342 {
6343     ChessSquare result;
6344     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6345        gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
6346         result = WhiteFerz; // no choice
6347     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6348         result= WhiteKing; // in Suicide Q is the last thing we want
6349     else if(gameInfo.variant == VariantSpartan)
6350         result = white ? WhiteQueen : WhiteAngel;
6351     else result = WhiteQueen;
6352     if(!white) result = WHITE_TO_BLACK result;
6353     return result;
6354 }
6355
6356 static int autoQueen; // [HGM] oneclick
6357
6358 int
6359 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6360 {
6361     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6362     /* [HGM] add Shogi promotions */
6363     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6364     ChessSquare piece;
6365     ChessMove moveType;
6366     Boolean premove;
6367
6368     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6369     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6370
6371     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6372       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6373         return FALSE;
6374
6375     piece = boards[currentMove][fromY][fromX];
6376     if(gameInfo.variant == VariantShogi) {
6377         promotionZoneSize = BOARD_HEIGHT/3;
6378         highestPromotingPiece = (int)WhiteFerz;
6379     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6380         promotionZoneSize = 3;
6381     }
6382
6383     // Treat Lance as Pawn when it is not representing Amazon
6384     if(gameInfo.variant != VariantSuper) {
6385         if(piece == WhiteLance) piece = WhitePawn; else
6386         if(piece == BlackLance) piece = BlackPawn;
6387     }
6388
6389     // next weed out all moves that do not touch the promotion zone at all
6390     if((int)piece >= BlackPawn) {
6391         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6392              return FALSE;
6393         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6394     } else {
6395         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6396            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6397     }
6398
6399     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6400
6401     // weed out mandatory Shogi promotions
6402     if(gameInfo.variant == VariantShogi) {
6403         if(piece >= BlackPawn) {
6404             if(toY == 0 && piece == BlackPawn ||
6405                toY == 0 && piece == BlackQueen ||
6406                toY <= 1 && piece == BlackKnight) {
6407                 *promoChoice = '+';
6408                 return FALSE;
6409             }
6410         } else {
6411             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6412                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6413                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6414                 *promoChoice = '+';
6415                 return FALSE;
6416             }
6417         }
6418     }
6419
6420     // weed out obviously illegal Pawn moves
6421     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6422         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6423         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6424         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6425         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6426         // note we are not allowed to test for valid (non-)capture, due to premove
6427     }
6428
6429     // we either have a choice what to promote to, or (in Shogi) whether to promote
6430     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6431        gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN) {
6432         *promoChoice = PieceToChar(BlackFerz);  // no choice
6433         return FALSE;
6434     }
6435     // no sense asking what we must promote to if it is going to explode...
6436     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6437         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6438         return FALSE;
6439     }
6440     // give caller the default choice even if we will not make it
6441     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6442     if(gameInfo.variant == VariantShogi) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6443     if(        sweepSelect && gameInfo.variant != VariantGreat
6444                            && gameInfo.variant != VariantGrand
6445                            && gameInfo.variant != VariantSuper) return FALSE;
6446     if(autoQueen) return FALSE; // predetermined
6447
6448     // suppress promotion popup on illegal moves that are not premoves
6449     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6450               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6451     if(appData.testLegality && !premove) {
6452         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6453                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6454         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6455             return FALSE;
6456     }
6457
6458     return TRUE;
6459 }
6460
6461 int
6462 InPalace (int row, int column)
6463 {   /* [HGM] for Xiangqi */
6464     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6465          column < (BOARD_WIDTH + 4)/2 &&
6466          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6467     return FALSE;
6468 }
6469
6470 int
6471 PieceForSquare (int x, int y)
6472 {
6473   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6474      return -1;
6475   else
6476      return boards[currentMove][y][x];
6477 }
6478
6479 int
6480 OKToStartUserMove (int x, int y)
6481 {
6482     ChessSquare from_piece;
6483     int white_piece;
6484
6485     if (matchMode) return FALSE;
6486     if (gameMode == EditPosition) return TRUE;
6487
6488     if (x >= 0 && y >= 0)
6489       from_piece = boards[currentMove][y][x];
6490     else
6491       from_piece = EmptySquare;
6492
6493     if (from_piece == EmptySquare) return FALSE;
6494
6495     white_piece = (int)from_piece >= (int)WhitePawn &&
6496       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6497
6498     switch (gameMode) {
6499       case AnalyzeFile:
6500       case TwoMachinesPlay:
6501       case EndOfGame:
6502         return FALSE;
6503
6504       case IcsObserving:
6505       case IcsIdle:
6506         return FALSE;
6507
6508       case MachinePlaysWhite:
6509       case IcsPlayingBlack:
6510         if (appData.zippyPlay) return FALSE;
6511         if (white_piece) {
6512             DisplayMoveError(_("You are playing Black"));
6513             return FALSE;
6514         }
6515         break;
6516
6517       case MachinePlaysBlack:
6518       case IcsPlayingWhite:
6519         if (appData.zippyPlay) return FALSE;
6520         if (!white_piece) {
6521             DisplayMoveError(_("You are playing White"));
6522             return FALSE;
6523         }
6524         break;
6525
6526       case PlayFromGameFile:
6527             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6528       case EditGame:
6529         if (!white_piece && WhiteOnMove(currentMove)) {
6530             DisplayMoveError(_("It is White's turn"));
6531             return FALSE;
6532         }
6533         if (white_piece && !WhiteOnMove(currentMove)) {
6534             DisplayMoveError(_("It is Black's turn"));
6535             return FALSE;
6536         }
6537         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6538             /* Editing correspondence game history */
6539             /* Could disallow this or prompt for confirmation */
6540             cmailOldMove = -1;
6541         }
6542         break;
6543
6544       case BeginningOfGame:
6545         if (appData.icsActive) return FALSE;
6546         if (!appData.noChessProgram) {
6547             if (!white_piece) {
6548                 DisplayMoveError(_("You are playing White"));
6549                 return FALSE;
6550             }
6551         }
6552         break;
6553
6554       case Training:
6555         if (!white_piece && WhiteOnMove(currentMove)) {
6556             DisplayMoveError(_("It is White's turn"));
6557             return FALSE;
6558         }
6559         if (white_piece && !WhiteOnMove(currentMove)) {
6560             DisplayMoveError(_("It is Black's turn"));
6561             return FALSE;
6562         }
6563         break;
6564
6565       default:
6566       case IcsExamining:
6567         break;
6568     }
6569     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6570         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6571         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6572         && gameMode != AnalyzeFile && gameMode != Training) {
6573         DisplayMoveError(_("Displayed position is not current"));
6574         return FALSE;
6575     }
6576     return TRUE;
6577 }
6578
6579 Boolean
6580 OnlyMove (int *x, int *y, Boolean captures)
6581 {
6582     DisambiguateClosure cl;
6583     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6584     switch(gameMode) {
6585       case MachinePlaysBlack:
6586       case IcsPlayingWhite:
6587       case BeginningOfGame:
6588         if(!WhiteOnMove(currentMove)) return FALSE;
6589         break;
6590       case MachinePlaysWhite:
6591       case IcsPlayingBlack:
6592         if(WhiteOnMove(currentMove)) return FALSE;
6593         break;
6594       case EditGame:
6595         break;
6596       default:
6597         return FALSE;
6598     }
6599     cl.pieceIn = EmptySquare;
6600     cl.rfIn = *y;
6601     cl.ffIn = *x;
6602     cl.rtIn = -1;
6603     cl.ftIn = -1;
6604     cl.promoCharIn = NULLCHAR;
6605     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6606     if( cl.kind == NormalMove ||
6607         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6608         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6609         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6610       fromX = cl.ff;
6611       fromY = cl.rf;
6612       *x = cl.ft;
6613       *y = cl.rt;
6614       return TRUE;
6615     }
6616     if(cl.kind != ImpossibleMove) return FALSE;
6617     cl.pieceIn = EmptySquare;
6618     cl.rfIn = -1;
6619     cl.ffIn = -1;
6620     cl.rtIn = *y;
6621     cl.ftIn = *x;
6622     cl.promoCharIn = NULLCHAR;
6623     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6624     if( cl.kind == NormalMove ||
6625         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6626         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6627         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6628       fromX = cl.ff;
6629       fromY = cl.rf;
6630       *x = cl.ft;
6631       *y = cl.rt;
6632       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6633       return TRUE;
6634     }
6635     return FALSE;
6636 }
6637
6638 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6639 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6640 int lastLoadGameUseList = FALSE;
6641 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6642 ChessMove lastLoadGameStart = EndOfFile;
6643 int doubleClick;
6644
6645 void
6646 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6647 {
6648     ChessMove moveType;
6649     ChessSquare pup;
6650     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6651
6652     /* Check if the user is playing in turn.  This is complicated because we
6653        let the user "pick up" a piece before it is his turn.  So the piece he
6654        tried to pick up may have been captured by the time he puts it down!
6655        Therefore we use the color the user is supposed to be playing in this
6656        test, not the color of the piece that is currently on the starting
6657        square---except in EditGame mode, where the user is playing both
6658        sides; fortunately there the capture race can't happen.  (It can
6659        now happen in IcsExamining mode, but that's just too bad.  The user
6660        will get a somewhat confusing message in that case.)
6661        */
6662
6663     switch (gameMode) {
6664       case AnalyzeFile:
6665       case TwoMachinesPlay:
6666       case EndOfGame:
6667       case IcsObserving:
6668       case IcsIdle:
6669         /* We switched into a game mode where moves are not accepted,
6670            perhaps while the mouse button was down. */
6671         return;
6672
6673       case MachinePlaysWhite:
6674         /* User is moving for Black */
6675         if (WhiteOnMove(currentMove)) {
6676             DisplayMoveError(_("It is White's turn"));
6677             return;
6678         }
6679         break;
6680
6681       case MachinePlaysBlack:
6682         /* User is moving for White */
6683         if (!WhiteOnMove(currentMove)) {
6684             DisplayMoveError(_("It is Black's turn"));
6685             return;
6686         }
6687         break;
6688
6689       case PlayFromGameFile:
6690             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6691       case EditGame:
6692       case IcsExamining:
6693       case BeginningOfGame:
6694       case AnalyzeMode:
6695       case Training:
6696         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6697         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6698             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6699             /* User is moving for Black */
6700             if (WhiteOnMove(currentMove)) {
6701                 DisplayMoveError(_("It is White's turn"));
6702                 return;
6703             }
6704         } else {
6705             /* User is moving for White */
6706             if (!WhiteOnMove(currentMove)) {
6707                 DisplayMoveError(_("It is Black's turn"));
6708                 return;
6709             }
6710         }
6711         break;
6712
6713       case IcsPlayingBlack:
6714         /* User is moving for Black */
6715         if (WhiteOnMove(currentMove)) {
6716             if (!appData.premove) {
6717                 DisplayMoveError(_("It is White's turn"));
6718             } else if (toX >= 0 && toY >= 0) {
6719                 premoveToX = toX;
6720                 premoveToY = toY;
6721                 premoveFromX = fromX;
6722                 premoveFromY = fromY;
6723                 premovePromoChar = promoChar;
6724                 gotPremove = 1;
6725                 if (appData.debugMode)
6726                     fprintf(debugFP, "Got premove: fromX %d,"
6727                             "fromY %d, toX %d, toY %d\n",
6728                             fromX, fromY, toX, toY);
6729             }
6730             return;
6731         }
6732         break;
6733
6734       case IcsPlayingWhite:
6735         /* User is moving for White */
6736         if (!WhiteOnMove(currentMove)) {
6737             if (!appData.premove) {
6738                 DisplayMoveError(_("It is Black's turn"));
6739             } else if (toX >= 0 && toY >= 0) {
6740                 premoveToX = toX;
6741                 premoveToY = toY;
6742                 premoveFromX = fromX;
6743                 premoveFromY = fromY;
6744                 premovePromoChar = promoChar;
6745                 gotPremove = 1;
6746                 if (appData.debugMode)
6747                     fprintf(debugFP, "Got premove: fromX %d,"
6748                             "fromY %d, toX %d, toY %d\n",
6749                             fromX, fromY, toX, toY);
6750             }
6751             return;
6752         }
6753         break;
6754
6755       default:
6756         break;
6757
6758       case EditPosition:
6759         /* EditPosition, empty square, or different color piece;
6760            click-click move is possible */
6761         if (toX == -2 || toY == -2) {
6762             boards[0][fromY][fromX] = EmptySquare;
6763             DrawPosition(FALSE, boards[currentMove]);
6764             return;
6765         } else if (toX >= 0 && toY >= 0) {
6766             boards[0][toY][toX] = boards[0][fromY][fromX];
6767             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6768                 if(boards[0][fromY][0] != EmptySquare) {
6769                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6770                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6771                 }
6772             } else
6773             if(fromX == BOARD_RGHT+1) {
6774                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6775                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6776                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6777                 }
6778             } else
6779             boards[0][fromY][fromX] = gatingPiece;
6780             DrawPosition(FALSE, boards[currentMove]);
6781             return;
6782         }
6783         return;
6784     }
6785
6786     if(toX < 0 || toY < 0) return;
6787     pup = boards[currentMove][toY][toX];
6788
6789     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6790     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6791          if( pup != EmptySquare ) return;
6792          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6793            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6794                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6795            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6796            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6797            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6798            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6799          fromY = DROP_RANK;
6800     }
6801
6802     /* [HGM] always test for legality, to get promotion info */
6803     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6804                                          fromY, fromX, toY, toX, promoChar);
6805
6806     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6807
6808     /* [HGM] but possibly ignore an IllegalMove result */
6809     if (appData.testLegality) {
6810         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6811             DisplayMoveError(_("Illegal move"));
6812             return;
6813         }
6814     }
6815
6816     if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
6817         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
6818              ClearPremoveHighlights(); // was included
6819         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
6820         return;
6821     }
6822
6823     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6824 }
6825
6826 /* Common tail of UserMoveEvent and DropMenuEvent */
6827 int
6828 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
6829 {
6830     char *bookHit = 0;
6831
6832     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6833         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6834         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6835         if(WhiteOnMove(currentMove)) {
6836             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6837         } else {
6838             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6839         }
6840     }
6841
6842     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6843        move type in caller when we know the move is a legal promotion */
6844     if(moveType == NormalMove && promoChar)
6845         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6846
6847     /* [HGM] <popupFix> The following if has been moved here from
6848        UserMoveEvent(). Because it seemed to belong here (why not allow
6849        piece drops in training games?), and because it can only be
6850        performed after it is known to what we promote. */
6851     if (gameMode == Training) {
6852       /* compare the move played on the board to the next move in the
6853        * game. If they match, display the move and the opponent's response.
6854        * If they don't match, display an error message.
6855        */
6856       int saveAnimate;
6857       Board testBoard;
6858       CopyBoard(testBoard, boards[currentMove]);
6859       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6860
6861       if (CompareBoards(testBoard, boards[currentMove+1])) {
6862         ForwardInner(currentMove+1);
6863
6864         /* Autoplay the opponent's response.
6865          * if appData.animate was TRUE when Training mode was entered,
6866          * the response will be animated.
6867          */
6868         saveAnimate = appData.animate;
6869         appData.animate = animateTraining;
6870         ForwardInner(currentMove+1);
6871         appData.animate = saveAnimate;
6872
6873         /* check for the end of the game */
6874         if (currentMove >= forwardMostMove) {
6875           gameMode = PlayFromGameFile;
6876           ModeHighlight();
6877           SetTrainingModeOff();
6878           DisplayInformation(_("End of game"));
6879         }
6880       } else {
6881         DisplayError(_("Incorrect move"), 0);
6882       }
6883       return 1;
6884     }
6885
6886   /* Ok, now we know that the move is good, so we can kill
6887      the previous line in Analysis Mode */
6888   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6889                                 && currentMove < forwardMostMove) {
6890     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6891     else forwardMostMove = currentMove;
6892   }
6893
6894   ClearMap();
6895
6896   /* If we need the chess program but it's dead, restart it */
6897   ResurrectChessProgram();
6898
6899   /* A user move restarts a paused game*/
6900   if (pausing)
6901     PauseEvent();
6902
6903   thinkOutput[0] = NULLCHAR;
6904
6905   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6906
6907   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6908     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6909     return 1;
6910   }
6911
6912   if (gameMode == BeginningOfGame) {
6913     if (appData.noChessProgram) {
6914       gameMode = EditGame;
6915       SetGameInfo();
6916     } else {
6917       char buf[MSG_SIZ];
6918       gameMode = MachinePlaysBlack;
6919       StartClocks();
6920       SetGameInfo();
6921       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
6922       DisplayTitle(buf);
6923       if (first.sendName) {
6924         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6925         SendToProgram(buf, &first);
6926       }
6927       StartClocks();
6928     }
6929     ModeHighlight();
6930   }
6931
6932   /* Relay move to ICS or chess engine */
6933   if (appData.icsActive) {
6934     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6935         gameMode == IcsExamining) {
6936       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6937         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6938         SendToICS("draw ");
6939         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6940       }
6941       // also send plain move, in case ICS does not understand atomic claims
6942       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6943       ics_user_moved = 1;
6944     }
6945   } else {
6946     if (first.sendTime && (gameMode == BeginningOfGame ||
6947                            gameMode == MachinePlaysWhite ||
6948                            gameMode == MachinePlaysBlack)) {
6949       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6950     }
6951     if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
6952          // [HGM] book: if program might be playing, let it use book
6953         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6954         first.maybeThinking = TRUE;
6955     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
6956         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
6957         SendBoard(&first, currentMove+1);
6958         if(second.analyzing) {
6959             if(!second.useSetboard) SendToProgram("undo\n", &second);
6960             SendBoard(&second, currentMove+1);
6961         }
6962     } else {
6963         SendMoveToProgram(forwardMostMove-1, &first);
6964         if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
6965     }
6966     if (currentMove == cmailOldMove + 1) {
6967       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6968     }
6969   }
6970
6971   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6972
6973   switch (gameMode) {
6974   case EditGame:
6975     if(appData.testLegality)
6976     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6977     case MT_NONE:
6978     case MT_CHECK:
6979       break;
6980     case MT_CHECKMATE:
6981     case MT_STAINMATE:
6982       if (WhiteOnMove(currentMove)) {
6983         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6984       } else {
6985         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6986       }
6987       break;
6988     case MT_STALEMATE:
6989       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6990       break;
6991     }
6992     break;
6993
6994   case MachinePlaysBlack:
6995   case MachinePlaysWhite:
6996     /* disable certain menu options while machine is thinking */
6997     SetMachineThinkingEnables();
6998     break;
6999
7000   default:
7001     break;
7002   }
7003
7004   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7005   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7006
7007   if(bookHit) { // [HGM] book: simulate book reply
7008         static char bookMove[MSG_SIZ]; // a bit generous?
7009
7010         programStats.nodes = programStats.depth = programStats.time =
7011         programStats.score = programStats.got_only_move = 0;
7012         sprintf(programStats.movelist, "%s (xbook)", bookHit);
7013
7014         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7015         strcat(bookMove, bookHit);
7016         HandleMachineMove(bookMove, &first);
7017   }
7018   return 1;
7019 }
7020
7021 void
7022 MarkByFEN(char *fen)
7023 {
7024         int r, f;
7025         if(!appData.markers || !appData.highlightDragging) return;
7026         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 0;
7027         r=BOARD_HEIGHT-1; f=BOARD_LEFT;
7028         while(*fen) {
7029             int s = 0;
7030             marker[r][f] = 0;
7031             if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7032             if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 1; else
7033             if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7034             if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7035             if(*fen == 'T') marker[r][f++] = 0; else
7036             if(*fen == 'Y') marker[r][f++] = 1; else
7037             if(*fen == 'G') marker[r][f++] = 3; else
7038             if(*fen == 'B') marker[r][f++] = 4; else
7039             if(*fen == 'C') marker[r][f++] = 5; else
7040             if(*fen == 'M') marker[r][f++] = 6; else
7041             if(*fen == 'W') marker[r][f++] = 7; else
7042             if(*fen == 'D') marker[r][f++] = 8; else
7043             if(*fen == 'R') marker[r][f++] = 2; else {
7044                 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7045               f += s; fen -= s>0;
7046             }
7047             while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7048             if(r < 0) break;
7049             fen++;
7050         }
7051         DrawPosition(TRUE, NULL);
7052 }
7053
7054 void
7055 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7056 {
7057     typedef char Markers[BOARD_RANKS][BOARD_FILES];
7058     Markers *m = (Markers *) closure;
7059     if(rf == fromY && ff == fromX)
7060         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7061                          || kind == WhiteCapturesEnPassant
7062                          || kind == BlackCapturesEnPassant);
7063     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
7064 }
7065
7066 void
7067 MarkTargetSquares (int clear)
7068 {
7069   int x, y, sum=0;
7070   if(clear) { // no reason to ever suppress clearing
7071     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = 0;
7072     if(!sum) return; // nothing was cleared,no redraw needed
7073   } else {
7074     int capt = 0;
7075     if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7076        !appData.testLegality || gameMode == EditPosition) return;
7077     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7078     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7079       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7080       if(capt)
7081       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7082     }
7083   }
7084   DrawPosition(FALSE, NULL);
7085 }
7086
7087 int
7088 Explode (Board board, int fromX, int fromY, int toX, int toY)
7089 {
7090     if(gameInfo.variant == VariantAtomic &&
7091        (board[toY][toX] != EmptySquare ||                     // capture?
7092         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
7093                          board[fromY][fromX] == BlackPawn   )
7094       )) {
7095         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7096         return TRUE;
7097     }
7098     return FALSE;
7099 }
7100
7101 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7102
7103 int
7104 CanPromote (ChessSquare piece, int y)
7105 {
7106         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7107         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7108         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
7109            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7110            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7111          gameInfo.variant == VariantMakruk   || gameInfo.variant == VariantASEAN) return FALSE;
7112         return (piece == BlackPawn && y == 1 ||
7113                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
7114                 piece == BlackLance && y == 1 ||
7115                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
7116 }
7117
7118 void
7119 HoverEvent (int hiX, int hiY, int x, int y)
7120 {
7121         static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7122         int r, f;
7123         if(!first.highlight) return;
7124         if(hiX == -1 && hiY == -1 && x == fromX && y == fromY) // record markings 
7125           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7126             baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7127         else if(hiX != x || hiY != y) {
7128           // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7129           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7130             marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7131           if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7132             char buf[MSG_SIZ];
7133             snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7134             SendToProgram(buf, &first);
7135           }
7136           SetHighlights(fromX, fromY, x, y);
7137         }
7138 }
7139
7140 void ReportClick(char *action, int x, int y)
7141 {
7142         char buf[MSG_SIZ]; // Inform engine of what user does
7143         int r, f;
7144         if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7145           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 1, marker[r][f] = 0;
7146         if(!first.highlight || gameMode == EditPosition) return;
7147         snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7148         SendToProgram(buf, &first);
7149 }
7150
7151 void
7152 LeftClick (ClickType clickType, int xPix, int yPix)
7153 {
7154     int x, y;
7155     Boolean saveAnimate;
7156     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7157     char promoChoice = NULLCHAR;
7158     ChessSquare piece;
7159     static TimeMark lastClickTime, prevClickTime;
7160
7161     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7162
7163     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7164
7165     if (clickType == Press) ErrorPopDown();
7166     lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7167
7168     x = EventToSquare(xPix, BOARD_WIDTH);
7169     y = EventToSquare(yPix, BOARD_HEIGHT);
7170     if (!flipView && y >= 0) {
7171         y = BOARD_HEIGHT - 1 - y;
7172     }
7173     if (flipView && x >= 0) {
7174         x = BOARD_WIDTH - 1 - x;
7175     }
7176
7177     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7178         defaultPromoChoice = promoSweep;
7179         promoSweep = EmptySquare;   // terminate sweep
7180         promoDefaultAltered = TRUE;
7181         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7182     }
7183
7184     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7185         if(clickType == Release) return; // ignore upclick of click-click destination
7186         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7187         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7188         if(gameInfo.holdingsWidth &&
7189                 (WhiteOnMove(currentMove)
7190                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7191                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7192             // click in right holdings, for determining promotion piece
7193             ChessSquare p = boards[currentMove][y][x];
7194             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7195             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7196             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7197                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7198                 fromX = fromY = -1;
7199                 return;
7200             }
7201         }
7202         DrawPosition(FALSE, boards[currentMove]);
7203         return;
7204     }
7205
7206     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7207     if(clickType == Press
7208             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7209               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7210               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7211         return;
7212
7213     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7214         // could be static click on premove from-square: abort premove
7215         gotPremove = 0;
7216         ClearPremoveHighlights();
7217     }
7218
7219     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7220         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7221
7222     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7223         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7224                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7225         defaultPromoChoice = DefaultPromoChoice(side);
7226     }
7227
7228     autoQueen = appData.alwaysPromoteToQueen;
7229
7230     if (fromX == -1) {
7231       int originalY = y;
7232       gatingPiece = EmptySquare;
7233       if (clickType != Press) {
7234         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7235             DragPieceEnd(xPix, yPix); dragging = 0;
7236             DrawPosition(FALSE, NULL);
7237         }
7238         return;
7239       }
7240       doubleClick = FALSE;
7241       if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7242         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7243       }
7244       fromX = x; fromY = y; toX = toY = -1;
7245       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7246          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7247          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7248             /* First square */
7249             if (OKToStartUserMove(fromX, fromY)) {
7250                 second = 0;
7251                 ReportClick("lift", x, y);
7252                 MarkTargetSquares(0);
7253                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7254                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7255                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7256                     promoSweep = defaultPromoChoice;
7257                     selectFlag = 0; lastX = xPix; lastY = yPix;
7258                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7259                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7260                 }
7261                 if (appData.highlightDragging) {
7262                     SetHighlights(fromX, fromY, -1, -1);
7263                 } else {
7264                     ClearHighlights();
7265                 }
7266             } else fromX = fromY = -1;
7267             return;
7268         }
7269     }
7270
7271     /* fromX != -1 */
7272     if (clickType == Press && gameMode != EditPosition) {
7273         ChessSquare fromP;
7274         ChessSquare toP;
7275         int frc;
7276
7277         // ignore off-board to clicks
7278         if(y < 0 || x < 0) return;
7279
7280         /* Check if clicking again on the same color piece */
7281         fromP = boards[currentMove][fromY][fromX];
7282         toP = boards[currentMove][y][x];
7283         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
7284         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
7285              WhitePawn <= toP && toP <= WhiteKing &&
7286              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7287              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7288             (BlackPawn <= fromP && fromP <= BlackKing &&
7289              BlackPawn <= toP && toP <= BlackKing &&
7290              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7291              !(fromP == BlackKing && toP == BlackRook && frc))) {
7292             /* Clicked again on same color piece -- changed his mind */
7293             second = (x == fromX && y == fromY);
7294             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7295                 second = FALSE; // first double-click rather than scond click
7296                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7297             }
7298             promoDefaultAltered = FALSE;
7299             MarkTargetSquares(1);
7300            if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7301             if (appData.highlightDragging) {
7302                 SetHighlights(x, y, -1, -1);
7303             } else {
7304                 ClearHighlights();
7305             }
7306             if (OKToStartUserMove(x, y)) {
7307                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7308                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7309                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7310                  gatingPiece = boards[currentMove][fromY][fromX];
7311                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7312                 fromX = x;
7313                 fromY = y; dragging = 1;
7314                 ReportClick("lift", x, y);
7315                 MarkTargetSquares(0);
7316                 DragPieceBegin(xPix, yPix, FALSE);
7317                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7318                     promoSweep = defaultPromoChoice;
7319                     selectFlag = 0; lastX = xPix; lastY = yPix;
7320                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7321                 }
7322             }
7323            }
7324            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7325            second = FALSE;
7326         }
7327         // ignore clicks on holdings
7328         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7329     }
7330
7331     if (clickType == Release && x == fromX && y == fromY) {
7332         DragPieceEnd(xPix, yPix); dragging = 0;
7333         if(clearFlag) {
7334             // a deferred attempt to click-click move an empty square on top of a piece
7335             boards[currentMove][y][x] = EmptySquare;
7336             ClearHighlights();
7337             DrawPosition(FALSE, boards[currentMove]);
7338             fromX = fromY = -1; clearFlag = 0;
7339             return;
7340         }
7341         if (appData.animateDragging) {
7342             /* Undo animation damage if any */
7343             DrawPosition(FALSE, NULL);
7344         }
7345         if (second || sweepSelecting) {
7346             /* Second up/down in same square; just abort move */
7347             if(sweepSelecting) DrawPosition(FALSE, boards[currentMove]);
7348             second = sweepSelecting = 0;
7349             fromX = fromY = -1;
7350             gatingPiece = EmptySquare;
7351             MarkTargetSquares(1);
7352             ClearHighlights();
7353             gotPremove = 0;
7354             ClearPremoveHighlights();
7355         } else {
7356             /* First upclick in same square; start click-click mode */
7357             SetHighlights(x, y, -1, -1);
7358         }
7359         return;
7360     }
7361
7362     clearFlag = 0;
7363
7364     if(gameMode != EditPosition && !appData.testLegality && !legal[y][x]) {
7365         if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7366         DisplayMessage(_("only marked squares are legal"),"");
7367         DrawPosition(TRUE, NULL);
7368         return; // ignore to-click
7369     }
7370
7371     /* we now have a different from- and (possibly off-board) to-square */
7372     /* Completed move */
7373     if(!sweepSelecting) {
7374         toX = x;
7375         toY = y;
7376     } else sweepSelecting = 0; // this must be the up-click corresponding to the down-click that started the sweep
7377
7378     saveAnimate = appData.animate;
7379     if (clickType == Press) {
7380         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7381             // must be Edit Position mode with empty-square selected
7382             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7383             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7384             return;
7385         }
7386         if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7387           if(appData.sweepSelect) {
7388             ChessSquare piece = boards[currentMove][fromY][fromX];
7389             promoSweep = defaultPromoChoice;
7390             if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
7391             selectFlag = 0; lastX = xPix; lastY = yPix;
7392             Sweep(0); // Pawn that is going to promote: preview promotion piece
7393             sweepSelecting = 1;
7394             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7395             MarkTargetSquares(1);
7396           }
7397           return; // promo popup appears on up-click
7398         }
7399         /* Finish clickclick move */
7400         if (appData.animate || appData.highlightLastMove) {
7401             SetHighlights(fromX, fromY, toX, toY);
7402         } else {
7403             ClearHighlights();
7404         }
7405     } else {
7406 #if 0
7407 // [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
7408         /* Finish drag move */
7409         if (appData.highlightLastMove) {
7410             SetHighlights(fromX, fromY, toX, toY);
7411         } else {
7412             ClearHighlights();
7413         }
7414 #endif
7415         DragPieceEnd(xPix, yPix); dragging = 0;
7416         /* Don't animate move and drag both */
7417         appData.animate = FALSE;
7418     }
7419
7420     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7421     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7422         ChessSquare piece = boards[currentMove][fromY][fromX];
7423         if(gameMode == EditPosition && piece != EmptySquare &&
7424            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7425             int n;
7426
7427             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7428                 n = PieceToNumber(piece - (int)BlackPawn);
7429                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7430                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7431                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7432             } else
7433             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7434                 n = PieceToNumber(piece);
7435                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7436                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7437                 boards[currentMove][n][BOARD_WIDTH-2]++;
7438             }
7439             boards[currentMove][fromY][fromX] = EmptySquare;
7440         }
7441         ClearHighlights();
7442         fromX = fromY = -1;
7443         MarkTargetSquares(1);
7444         DrawPosition(TRUE, boards[currentMove]);
7445         return;
7446     }
7447
7448     // off-board moves should not be highlighted
7449     if(x < 0 || y < 0) ClearHighlights();
7450     else ReportClick("put", x, y);
7451
7452     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7453
7454     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7455         SetHighlights(fromX, fromY, toX, toY);
7456         MarkTargetSquares(1);
7457         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7458             // [HGM] super: promotion to captured piece selected from holdings
7459             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7460             promotionChoice = TRUE;
7461             // kludge follows to temporarily execute move on display, without promoting yet
7462             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7463             boards[currentMove][toY][toX] = p;
7464             DrawPosition(FALSE, boards[currentMove]);
7465             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7466             boards[currentMove][toY][toX] = q;
7467             DisplayMessage("Click in holdings to choose piece", "");
7468             return;
7469         }
7470         PromotionPopUp();
7471     } else {
7472         int oldMove = currentMove;
7473         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7474         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7475         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7476         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7477            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7478             DrawPosition(TRUE, boards[currentMove]);
7479         MarkTargetSquares(1);
7480         fromX = fromY = -1;
7481     }
7482     appData.animate = saveAnimate;
7483     if (appData.animate || appData.animateDragging) {
7484         /* Undo animation damage if needed */
7485         DrawPosition(FALSE, NULL);
7486     }
7487 }
7488
7489 int
7490 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7491 {   // front-end-free part taken out of PieceMenuPopup
7492     int whichMenu; int xSqr, ySqr;
7493
7494     if(seekGraphUp) { // [HGM] seekgraph
7495         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7496         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7497         return -2;
7498     }
7499
7500     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7501          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7502         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7503         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7504         if(action == Press)   {
7505             originalFlip = flipView;
7506             flipView = !flipView; // temporarily flip board to see game from partners perspective
7507             DrawPosition(TRUE, partnerBoard);
7508             DisplayMessage(partnerStatus, "");
7509             partnerUp = TRUE;
7510         } else if(action == Release) {
7511             flipView = originalFlip;
7512             DrawPosition(TRUE, boards[currentMove]);
7513             partnerUp = FALSE;
7514         }
7515         return -2;
7516     }
7517
7518     xSqr = EventToSquare(x, BOARD_WIDTH);
7519     ySqr = EventToSquare(y, BOARD_HEIGHT);
7520     if (action == Release) {
7521         if(pieceSweep != EmptySquare) {
7522             EditPositionMenuEvent(pieceSweep, toX, toY);
7523             pieceSweep = EmptySquare;
7524         } else UnLoadPV(); // [HGM] pv
7525     }
7526     if (action != Press) return -2; // return code to be ignored
7527     switch (gameMode) {
7528       case IcsExamining:
7529         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7530       case EditPosition:
7531         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7532         if (xSqr < 0 || ySqr < 0) return -1;
7533         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7534         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7535         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7536         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7537         NextPiece(0);
7538         return 2; // grab
7539       case IcsObserving:
7540         if(!appData.icsEngineAnalyze) return -1;
7541       case IcsPlayingWhite:
7542       case IcsPlayingBlack:
7543         if(!appData.zippyPlay) goto noZip;
7544       case AnalyzeMode:
7545       case AnalyzeFile:
7546       case MachinePlaysWhite:
7547       case MachinePlaysBlack:
7548       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7549         if (!appData.dropMenu) {
7550           LoadPV(x, y);
7551           return 2; // flag front-end to grab mouse events
7552         }
7553         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7554            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7555       case EditGame:
7556       noZip:
7557         if (xSqr < 0 || ySqr < 0) return -1;
7558         if (!appData.dropMenu || appData.testLegality &&
7559             gameInfo.variant != VariantBughouse &&
7560             gameInfo.variant != VariantCrazyhouse) return -1;
7561         whichMenu = 1; // drop menu
7562         break;
7563       default:
7564         return -1;
7565     }
7566
7567     if (((*fromX = xSqr) < 0) ||
7568         ((*fromY = ySqr) < 0)) {
7569         *fromX = *fromY = -1;
7570         return -1;
7571     }
7572     if (flipView)
7573       *fromX = BOARD_WIDTH - 1 - *fromX;
7574     else
7575       *fromY = BOARD_HEIGHT - 1 - *fromY;
7576
7577     return whichMenu;
7578 }
7579
7580 void
7581 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7582 {
7583 //    char * hint = lastHint;
7584     FrontEndProgramStats stats;
7585
7586     stats.which = cps == &first ? 0 : 1;
7587     stats.depth = cpstats->depth;
7588     stats.nodes = cpstats->nodes;
7589     stats.score = cpstats->score;
7590     stats.time = cpstats->time;
7591     stats.pv = cpstats->movelist;
7592     stats.hint = lastHint;
7593     stats.an_move_index = 0;
7594     stats.an_move_count = 0;
7595
7596     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7597         stats.hint = cpstats->move_name;
7598         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7599         stats.an_move_count = cpstats->nr_moves;
7600     }
7601
7602     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
7603
7604     SetProgramStats( &stats );
7605 }
7606
7607 void
7608 ClearEngineOutputPane (int which)
7609 {
7610     static FrontEndProgramStats dummyStats;
7611     dummyStats.which = which;
7612     dummyStats.pv = "#";
7613     SetProgramStats( &dummyStats );
7614 }
7615
7616 #define MAXPLAYERS 500
7617
7618 char *
7619 TourneyStandings (int display)
7620 {
7621     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7622     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7623     char result, *p, *names[MAXPLAYERS];
7624
7625     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7626         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7627     names[0] = p = strdup(appData.participants);
7628     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7629
7630     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7631
7632     while(result = appData.results[nr]) {
7633         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7634         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7635         wScore = bScore = 0;
7636         switch(result) {
7637           case '+': wScore = 2; break;
7638           case '-': bScore = 2; break;
7639           case '=': wScore = bScore = 1; break;
7640           case ' ':
7641           case '*': return strdup("busy"); // tourney not finished
7642         }
7643         score[w] += wScore;
7644         score[b] += bScore;
7645         games[w]++;
7646         games[b]++;
7647         nr++;
7648     }
7649     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7650     for(w=0; w<nPlayers; w++) {
7651         bScore = -1;
7652         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7653         ranking[w] = b; points[w] = bScore; score[b] = -2;
7654     }
7655     p = malloc(nPlayers*34+1);
7656     for(w=0; w<nPlayers && w<display; w++)
7657         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7658     free(names[0]);
7659     return p;
7660 }
7661
7662 void
7663 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7664 {       // count all piece types
7665         int p, f, r;
7666         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7667         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7668         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7669                 p = board[r][f];
7670                 pCnt[p]++;
7671                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7672                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7673                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7674                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7675                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7676                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7677         }
7678 }
7679
7680 int
7681 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7682 {
7683         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7684         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7685
7686         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7687         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7688         if(myPawns == 2 && nMine == 3) // KPP
7689             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7690         if(myPawns == 1 && nMine == 2) // KP
7691             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7692         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7693             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7694         if(myPawns) return FALSE;
7695         if(pCnt[WhiteRook+side])
7696             return pCnt[BlackRook-side] ||
7697                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7698                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7699                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7700         if(pCnt[WhiteCannon+side]) {
7701             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7702             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7703         }
7704         if(pCnt[WhiteKnight+side])
7705             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7706         return FALSE;
7707 }
7708
7709 int
7710 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7711 {
7712         VariantClass v = gameInfo.variant;
7713
7714         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7715         if(v == VariantShatranj) return TRUE; // always winnable through baring
7716         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7717         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7718
7719         if(v == VariantXiangqi) {
7720                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7721
7722                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7723                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7724                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7725                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7726                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7727                 if(stale) // we have at least one last-rank P plus perhaps C
7728                     return majors // KPKX
7729                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7730                 else // KCA*E*
7731                     return pCnt[WhiteFerz+side] // KCAK
7732                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7733                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7734                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7735
7736         } else if(v == VariantKnightmate) {
7737                 if(nMine == 1) return FALSE;
7738                 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
7739         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7740                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7741
7742                 if(nMine == 1) return FALSE; // bare King
7743                 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
7744                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7745                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7746                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7747                 if(pCnt[WhiteKnight+side])
7748                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7749                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7750                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7751                 if(nBishops)
7752                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7753                 if(pCnt[WhiteAlfil+side])
7754                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7755                 if(pCnt[WhiteWazir+side])
7756                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7757         }
7758
7759         return TRUE;
7760 }
7761
7762 int
7763 CompareWithRights (Board b1, Board b2)
7764 {
7765     int rights = 0;
7766     if(!CompareBoards(b1, b2)) return FALSE;
7767     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7768     /* compare castling rights */
7769     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7770            rights++; /* King lost rights, while rook still had them */
7771     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7772         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7773            rights++; /* but at least one rook lost them */
7774     }
7775     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7776            rights++;
7777     if( b1[CASTLING][5] != NoRights ) {
7778         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7779            rights++;
7780     }
7781     return rights == 0;
7782 }
7783
7784 int
7785 Adjudicate (ChessProgramState *cps)
7786 {       // [HGM] some adjudications useful with buggy engines
7787         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7788         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7789         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7790         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7791         int k, drop, count = 0; static int bare = 1;
7792         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7793         Boolean canAdjudicate = !appData.icsActive;
7794
7795         // most tests only when we understand the game, i.e. legality-checking on
7796             if( appData.testLegality )
7797             {   /* [HGM] Some more adjudications for obstinate engines */
7798                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7799                 static int moveCount = 6;
7800                 ChessMove result;
7801                 char *reason = NULL;
7802
7803                 /* Count what is on board. */
7804                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7805
7806                 /* Some material-based adjudications that have to be made before stalemate test */
7807                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7808                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7809                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7810                      if(canAdjudicate && appData.checkMates) {
7811                          if(engineOpponent)
7812                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7813                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7814                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7815                          return 1;
7816                      }
7817                 }
7818
7819                 /* Bare King in Shatranj (loses) or Losers (wins) */
7820                 if( nrW == 1 || nrB == 1) {
7821                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7822                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7823                      if(canAdjudicate && appData.checkMates) {
7824                          if(engineOpponent)
7825                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7826                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7827                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7828                          return 1;
7829                      }
7830                   } else
7831                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7832                   {    /* bare King */
7833                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7834                         if(canAdjudicate && appData.checkMates) {
7835                             /* but only adjudicate if adjudication enabled */
7836                             if(engineOpponent)
7837                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7838                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7839                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7840                             return 1;
7841                         }
7842                   }
7843                 } else bare = 1;
7844
7845
7846             // don't wait for engine to announce game end if we can judge ourselves
7847             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7848               case MT_CHECK:
7849                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7850                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7851                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7852                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7853                             checkCnt++;
7854                         if(checkCnt >= 2) {
7855                             reason = "Xboard adjudication: 3rd check";
7856                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7857                             break;
7858                         }
7859                     }
7860                 }
7861               case MT_NONE:
7862               default:
7863                 break;
7864               case MT_STALEMATE:
7865               case MT_STAINMATE:
7866                 reason = "Xboard adjudication: Stalemate";
7867                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7868                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7869                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7870                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7871                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7872                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7873                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7874                                                                         EP_CHECKMATE : EP_WINS);
7875                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
7876                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7877                 }
7878                 break;
7879               case MT_CHECKMATE:
7880                 reason = "Xboard adjudication: Checkmate";
7881                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7882                 if(gameInfo.variant == VariantShogi) {
7883                     if(forwardMostMove > backwardMostMove
7884                        && moveList[forwardMostMove-1][1] == '@'
7885                        && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
7886                         reason = "XBoard adjudication: pawn-drop mate";
7887                         boards[forwardMostMove][EP_STATUS] = EP_WINS;
7888                     }
7889                 }
7890                 break;
7891             }
7892
7893                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7894                     case EP_STALEMATE:
7895                         result = GameIsDrawn; break;
7896                     case EP_CHECKMATE:
7897                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7898                     case EP_WINS:
7899                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7900                     default:
7901                         result = EndOfFile;
7902                 }
7903                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7904                     if(engineOpponent)
7905                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7906                     GameEnds( result, reason, GE_XBOARD );
7907                     return 1;
7908                 }
7909
7910                 /* Next absolutely insufficient mating material. */
7911                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7912                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7913                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7914
7915                      /* always flag draws, for judging claims */
7916                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7917
7918                      if(canAdjudicate && appData.materialDraws) {
7919                          /* but only adjudicate them if adjudication enabled */
7920                          if(engineOpponent) {
7921                            SendToProgram("force\n", engineOpponent); // suppress reply
7922                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7923                          }
7924                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7925                          return 1;
7926                      }
7927                 }
7928
7929                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7930                 if(gameInfo.variant == VariantXiangqi ?
7931                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7932                  : nrW + nrB == 4 &&
7933                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7934                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7935                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7936                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7937                    ) ) {
7938                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7939                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7940                           if(engineOpponent) {
7941                             SendToProgram("force\n", engineOpponent); // suppress reply
7942                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7943                           }
7944                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7945                           return 1;
7946                      }
7947                 } else moveCount = 6;
7948             }
7949
7950         // Repetition draws and 50-move rule can be applied independently of legality testing
7951
7952                 /* Check for rep-draws */
7953                 count = 0;
7954                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
7955                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
7956                 for(k = forwardMostMove-2;
7957                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
7958                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7959                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
7960                     k-=2)
7961                 {   int rights=0;
7962                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7963                         /* compare castling rights */
7964                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7965                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7966                                 rights++; /* King lost rights, while rook still had them */
7967                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7968                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7969                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7970                                    rights++; /* but at least one rook lost them */
7971                         }
7972                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7973                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7974                                 rights++;
7975                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7976                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7977                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7978                                    rights++;
7979                         }
7980                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7981                             && appData.drawRepeats > 1) {
7982                              /* adjudicate after user-specified nr of repeats */
7983                              int result = GameIsDrawn;
7984                              char *details = "XBoard adjudication: repetition draw";
7985                              if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
7986                                 // [HGM] xiangqi: check for forbidden perpetuals
7987                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7988                                 for(m=forwardMostMove; m>k; m-=2) {
7989                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7990                                         ourPerpetual = 0; // the current mover did not always check
7991                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7992                                         hisPerpetual = 0; // the opponent did not always check
7993                                 }
7994                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7995                                                                         ourPerpetual, hisPerpetual);
7996                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7997                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7998                                     details = "Xboard adjudication: perpetual checking";
7999                                 } else
8000                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8001                                     break; // (or we would have caught him before). Abort repetition-checking loop.
8002                                 } else
8003                                 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8004                                     if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8005                                         result = BlackWins;
8006                                         details = "Xboard adjudication: repetition";
8007                                     }
8008                                 } else // it must be XQ
8009                                 // Now check for perpetual chases
8010                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8011                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
8012                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8013                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8014                                         static char resdet[MSG_SIZ];
8015                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8016                                         details = resdet;
8017                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8018                                     } else
8019                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
8020                                         break; // Abort repetition-checking loop.
8021                                 }
8022                                 // if neither of us is checking or chasing all the time, or both are, it is draw
8023                              }
8024                              if(engineOpponent) {
8025                                SendToProgram("force\n", engineOpponent); // suppress reply
8026                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8027                              }
8028                              GameEnds( result, details, GE_XBOARD );
8029                              return 1;
8030                         }
8031                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8032                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8033                     }
8034                 }
8035
8036                 /* Now we test for 50-move draws. Determine ply count */
8037                 count = forwardMostMove;
8038                 /* look for last irreversble move */
8039                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8040                     count--;
8041                 /* if we hit starting position, add initial plies */
8042                 if( count == backwardMostMove )
8043                     count -= initialRulePlies;
8044                 count = forwardMostMove - count;
8045                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8046                         // adjust reversible move counter for checks in Xiangqi
8047                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
8048                         if(i < backwardMostMove) i = backwardMostMove;
8049                         while(i <= forwardMostMove) {
8050                                 lastCheck = inCheck; // check evasion does not count
8051                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8052                                 if(inCheck || lastCheck) count--; // check does not count
8053                                 i++;
8054                         }
8055                 }
8056                 if( count >= 100)
8057                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8058                          /* this is used to judge if draw claims are legal */
8059                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8060                          if(engineOpponent) {
8061                            SendToProgram("force\n", engineOpponent); // suppress reply
8062                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8063                          }
8064                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8065                          return 1;
8066                 }
8067
8068                 /* if draw offer is pending, treat it as a draw claim
8069                  * when draw condition present, to allow engines a way to
8070                  * claim draws before making their move to avoid a race
8071                  * condition occurring after their move
8072                  */
8073                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8074                          char *p = NULL;
8075                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8076                              p = "Draw claim: 50-move rule";
8077                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8078                              p = "Draw claim: 3-fold repetition";
8079                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8080                              p = "Draw claim: insufficient mating material";
8081                          if( p != NULL && canAdjudicate) {
8082                              if(engineOpponent) {
8083                                SendToProgram("force\n", engineOpponent); // suppress reply
8084                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8085                              }
8086                              GameEnds( GameIsDrawn, p, GE_XBOARD );
8087                              return 1;
8088                          }
8089                 }
8090
8091                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8092                     if(engineOpponent) {
8093                       SendToProgram("force\n", engineOpponent); // suppress reply
8094                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8095                     }
8096                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8097                     return 1;
8098                 }
8099         return 0;
8100 }
8101
8102 char *
8103 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8104 {   // [HGM] book: this routine intercepts moves to simulate book replies
8105     char *bookHit = NULL;
8106
8107     //first determine if the incoming move brings opponent into his book
8108     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8109         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8110     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8111     if(bookHit != NULL && !cps->bookSuspend) {
8112         // make sure opponent is not going to reply after receiving move to book position
8113         SendToProgram("force\n", cps);
8114         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8115     }
8116     if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8117     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8118     // now arrange restart after book miss
8119     if(bookHit) {
8120         // after a book hit we never send 'go', and the code after the call to this routine
8121         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8122         char buf[MSG_SIZ], *move = bookHit;
8123         if(cps->useSAN) {
8124             int fromX, fromY, toX, toY;
8125             char promoChar;
8126             ChessMove moveType;
8127             move = buf + 30;
8128             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8129                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8130                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8131                                     PosFlags(forwardMostMove),
8132                                     fromY, fromX, toY, toX, promoChar, move);
8133             } else {
8134                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8135                 bookHit = NULL;
8136             }
8137         }
8138         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8139         SendToProgram(buf, cps);
8140         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8141     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8142         SendToProgram("go\n", cps);
8143         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8144     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8145         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8146             SendToProgram("go\n", cps);
8147         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8148     }
8149     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8150 }
8151
8152 int
8153 LoadError (char *errmess, ChessProgramState *cps)
8154 {   // unloads engine and switches back to -ncp mode if it was first
8155     if(cps->initDone) return FALSE;
8156     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8157     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8158     cps->pr = NoProc;
8159     if(cps == &first) {
8160         appData.noChessProgram = TRUE;
8161         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8162         gameMode = BeginningOfGame; ModeHighlight();
8163         SetNCPMode();
8164     }
8165     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8166     DisplayMessage("", ""); // erase waiting message
8167     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8168     return TRUE;
8169 }
8170
8171 char *savedMessage;
8172 ChessProgramState *savedState;
8173 void
8174 DeferredBookMove (void)
8175 {
8176         if(savedState->lastPing != savedState->lastPong)
8177                     ScheduleDelayedEvent(DeferredBookMove, 10);
8178         else
8179         HandleMachineMove(savedMessage, savedState);
8180 }
8181
8182 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8183 static ChessProgramState *stalledEngine;
8184 static char stashedInputMove[MSG_SIZ];
8185
8186 void
8187 HandleMachineMove (char *message, ChessProgramState *cps)
8188 {
8189     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8190     char realname[MSG_SIZ];
8191     int fromX, fromY, toX, toY;
8192     ChessMove moveType;
8193     char promoChar;
8194     char *p, *pv=buf1;
8195     int machineWhite, oldError;
8196     char *bookHit;
8197
8198     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8199         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8200         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8201             DisplayError(_("Invalid pairing from pairing engine"), 0);
8202             return;
8203         }
8204         pairingReceived = 1;
8205         NextMatchGame();
8206         return; // Skim the pairing messages here.
8207     }
8208
8209     oldError = cps->userError; cps->userError = 0;
8210
8211 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8212     /*
8213      * Kludge to ignore BEL characters
8214      */
8215     while (*message == '\007') message++;
8216
8217     /*
8218      * [HGM] engine debug message: ignore lines starting with '#' character
8219      */
8220     if(cps->debug && *message == '#') return;
8221
8222     /*
8223      * Look for book output
8224      */
8225     if (cps == &first && bookRequested) {
8226         if (message[0] == '\t' || message[0] == ' ') {
8227             /* Part of the book output is here; append it */
8228             strcat(bookOutput, message);
8229             strcat(bookOutput, "  \n");
8230             return;
8231         } else if (bookOutput[0] != NULLCHAR) {
8232             /* All of book output has arrived; display it */
8233             char *p = bookOutput;
8234             while (*p != NULLCHAR) {
8235                 if (*p == '\t') *p = ' ';
8236                 p++;
8237             }
8238             DisplayInformation(bookOutput);
8239             bookRequested = FALSE;
8240             /* Fall through to parse the current output */
8241         }
8242     }
8243
8244     /*
8245      * Look for machine move.
8246      */
8247     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8248         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8249     {
8250         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8251             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8252             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8253             stalledEngine = cps;
8254             if(appData.ponderNextMove) { // bring opponent out of ponder
8255                 if(gameMode == TwoMachinesPlay) {
8256                     if(cps->other->pause)
8257                         PauseEngine(cps->other);
8258                     else
8259                         SendToProgram("easy\n", cps->other);
8260                 }
8261             }
8262             StopClocks();
8263             return;
8264         }
8265
8266         /* This method is only useful on engines that support ping */
8267         if (cps->lastPing != cps->lastPong) {
8268           if (gameMode == BeginningOfGame) {
8269             /* Extra move from before last new; ignore */
8270             if (appData.debugMode) {
8271                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8272             }
8273           } else {
8274             if (appData.debugMode) {
8275                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8276                         cps->which, gameMode);
8277             }
8278
8279             SendToProgram("undo\n", cps);
8280           }
8281           return;
8282         }
8283
8284         switch (gameMode) {
8285           case BeginningOfGame:
8286             /* Extra move from before last reset; ignore */
8287             if (appData.debugMode) {
8288                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8289             }
8290             return;
8291
8292           case EndOfGame:
8293           case IcsIdle:
8294           default:
8295             /* Extra move after we tried to stop.  The mode test is
8296                not a reliable way of detecting this problem, but it's
8297                the best we can do on engines that don't support ping.
8298             */
8299             if (appData.debugMode) {
8300                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8301                         cps->which, gameMode);
8302             }
8303             SendToProgram("undo\n", cps);
8304             return;
8305
8306           case MachinePlaysWhite:
8307           case IcsPlayingWhite:
8308             machineWhite = TRUE;
8309             break;
8310
8311           case MachinePlaysBlack:
8312           case IcsPlayingBlack:
8313             machineWhite = FALSE;
8314             break;
8315
8316           case TwoMachinesPlay:
8317             machineWhite = (cps->twoMachinesColor[0] == 'w');
8318             break;
8319         }
8320         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8321             if (appData.debugMode) {
8322                 fprintf(debugFP,
8323                         "Ignoring move out of turn by %s, gameMode %d"
8324                         ", forwardMost %d\n",
8325                         cps->which, gameMode, forwardMostMove);
8326             }
8327             return;
8328         }
8329
8330         if(cps->alphaRank) AlphaRank(machineMove, 4);
8331         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8332                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8333             /* Machine move could not be parsed; ignore it. */
8334           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8335                     machineMove, _(cps->which));
8336             DisplayMoveError(buf1);
8337             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
8338                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
8339             if (gameMode == TwoMachinesPlay) {
8340               GameEnds(machineWhite ? BlackWins : WhiteWins,
8341                        buf1, GE_XBOARD);
8342             }
8343             return;
8344         }
8345
8346         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8347         /* So we have to redo legality test with true e.p. status here,  */
8348         /* to make sure an illegal e.p. capture does not slip through,   */
8349         /* to cause a forfeit on a justified illegal-move complaint      */
8350         /* of the opponent.                                              */
8351         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8352            ChessMove moveType;
8353            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8354                              fromY, fromX, toY, toX, promoChar);
8355             if(moveType == IllegalMove) {
8356               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8357                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8358                 GameEnds(machineWhite ? BlackWins : WhiteWins,
8359                            buf1, GE_XBOARD);
8360                 return;
8361            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
8362            /* [HGM] Kludge to handle engines that send FRC-style castling
8363               when they shouldn't (like TSCP-Gothic) */
8364            switch(moveType) {
8365              case WhiteASideCastleFR:
8366              case BlackASideCastleFR:
8367                toX+=2;
8368                currentMoveString[2]++;
8369                break;
8370              case WhiteHSideCastleFR:
8371              case BlackHSideCastleFR:
8372                toX--;
8373                currentMoveString[2]--;
8374                break;
8375              default: ; // nothing to do, but suppresses warning of pedantic compilers
8376            }
8377         }
8378         hintRequested = FALSE;
8379         lastHint[0] = NULLCHAR;
8380         bookRequested = FALSE;
8381         /* Program may be pondering now */
8382         cps->maybeThinking = TRUE;
8383         if (cps->sendTime == 2) cps->sendTime = 1;
8384         if (cps->offeredDraw) cps->offeredDraw--;
8385
8386         /* [AS] Save move info*/
8387         pvInfoList[ forwardMostMove ].score = programStats.score;
8388         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8389         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8390
8391         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8392
8393         /* Test suites abort the 'game' after one move */
8394         if(*appData.finger) {
8395            static FILE *f;
8396            char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8397            if(!f) f = fopen(appData.finger, "w");
8398            if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8399            else { DisplayFatalError("Bad output file", errno, 0); return; }
8400            free(fen);
8401            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8402         }
8403
8404         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8405         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8406             int count = 0;
8407
8408             while( count < adjudicateLossPlies ) {
8409                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8410
8411                 if( count & 1 ) {
8412                     score = -score; /* Flip score for winning side */
8413                 }
8414
8415                 if( score > adjudicateLossThreshold ) {
8416                     break;
8417                 }
8418
8419                 count++;
8420             }
8421
8422             if( count >= adjudicateLossPlies ) {
8423                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8424
8425                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8426                     "Xboard adjudication",
8427                     GE_XBOARD );
8428
8429                 return;
8430             }
8431         }
8432
8433         if(Adjudicate(cps)) {
8434             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8435             return; // [HGM] adjudicate: for all automatic game ends
8436         }
8437
8438 #if ZIPPY
8439         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8440             first.initDone) {
8441           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8442                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8443                 SendToICS("draw ");
8444                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8445           }
8446           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8447           ics_user_moved = 1;
8448           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8449                 char buf[3*MSG_SIZ];
8450
8451                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8452                         programStats.score / 100.,
8453                         programStats.depth,
8454                         programStats.time / 100.,
8455                         (unsigned int)programStats.nodes,
8456                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8457                         programStats.movelist);
8458                 SendToICS(buf);
8459 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8460           }
8461         }
8462 #endif
8463
8464         /* [AS] Clear stats for next move */
8465         ClearProgramStats();
8466         thinkOutput[0] = NULLCHAR;
8467         hiddenThinkOutputState = 0;
8468
8469         bookHit = NULL;
8470         if (gameMode == TwoMachinesPlay) {
8471             /* [HGM] relaying draw offers moved to after reception of move */
8472             /* and interpreting offer as claim if it brings draw condition */
8473             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8474                 SendToProgram("draw\n", cps->other);
8475             }
8476             if (cps->other->sendTime) {
8477                 SendTimeRemaining(cps->other,
8478                                   cps->other->twoMachinesColor[0] == 'w');
8479             }
8480             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8481             if (firstMove && !bookHit) {
8482                 firstMove = FALSE;
8483                 if (cps->other->useColors) {
8484                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8485                 }
8486                 SendToProgram("go\n", cps->other);
8487             }
8488             cps->other->maybeThinking = TRUE;
8489         }
8490
8491         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8492
8493         if (!pausing && appData.ringBellAfterMoves) {
8494             RingBell();
8495         }
8496
8497         /*
8498          * Reenable menu items that were disabled while
8499          * machine was thinking
8500          */
8501         if (gameMode != TwoMachinesPlay)
8502             SetUserThinkingEnables();
8503
8504         // [HGM] book: after book hit opponent has received move and is now in force mode
8505         // force the book reply into it, and then fake that it outputted this move by jumping
8506         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8507         if(bookHit) {
8508                 static char bookMove[MSG_SIZ]; // a bit generous?
8509
8510                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8511                 strcat(bookMove, bookHit);
8512                 message = bookMove;
8513                 cps = cps->other;
8514                 programStats.nodes = programStats.depth = programStats.time =
8515                 programStats.score = programStats.got_only_move = 0;
8516                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8517
8518                 if(cps->lastPing != cps->lastPong) {
8519                     savedMessage = message; // args for deferred call
8520                     savedState = cps;
8521                     ScheduleDelayedEvent(DeferredBookMove, 10);
8522                     return;
8523                 }
8524                 goto FakeBookMove;
8525         }
8526
8527         return;
8528     }
8529
8530     /* Set special modes for chess engines.  Later something general
8531      *  could be added here; for now there is just one kludge feature,
8532      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8533      *  when "xboard" is given as an interactive command.
8534      */
8535     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8536         cps->useSigint = FALSE;
8537         cps->useSigterm = FALSE;
8538     }
8539     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8540       ParseFeatures(message+8, cps);
8541       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8542     }
8543
8544     if (!strncmp(message, "setup ", 6) && 
8545         (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown || NonStandardBoardSize())
8546                                         ) { // [HGM] allow first engine to define opening position
8547       int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
8548       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8549       *buf = NULLCHAR;
8550       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8551       if(startedFromSetupPosition) return;
8552       dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
8553       if(dummy >= 3) {
8554         while(message[s] && message[s++] != ' ');
8555         if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
8556            dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
8557             appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
8558             if(dummy == 4) gameInfo.variant = StringToVariant(varName);     // parent variant
8559           InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
8560           if(*buf) SetCharTable(pieceToChar, buf); // do again, for it was spoiled by InitPosition
8561         }
8562       }
8563       ParseFEN(boards[0], &dummy, message+s);
8564       DrawPosition(TRUE, boards[0]);
8565       startedFromSetupPosition = TRUE;
8566       return;
8567     }
8568     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8569      * want this, I was asked to put it in, and obliged.
8570      */
8571     if (!strncmp(message, "setboard ", 9)) {
8572         Board initial_position;
8573
8574         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8575
8576         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8577             DisplayError(_("Bad FEN received from engine"), 0);
8578             return ;
8579         } else {
8580            Reset(TRUE, FALSE);
8581            CopyBoard(boards[0], initial_position);
8582            initialRulePlies = FENrulePlies;
8583            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8584            else gameMode = MachinePlaysBlack;
8585            DrawPosition(FALSE, boards[currentMove]);
8586         }
8587         return;
8588     }
8589
8590     /*
8591      * Look for communication commands
8592      */
8593     if (!strncmp(message, "telluser ", 9)) {
8594         if(message[9] == '\\' && message[10] == '\\')
8595             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8596         PlayTellSound();
8597         DisplayNote(message + 9);
8598         return;
8599     }
8600     if (!strncmp(message, "tellusererror ", 14)) {
8601         cps->userError = 1;
8602         if(message[14] == '\\' && message[15] == '\\')
8603             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8604         PlayTellSound();
8605         DisplayError(message + 14, 0);
8606         return;
8607     }
8608     if (!strncmp(message, "tellopponent ", 13)) {
8609       if (appData.icsActive) {
8610         if (loggedOn) {
8611           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8612           SendToICS(buf1);
8613         }
8614       } else {
8615         DisplayNote(message + 13);
8616       }
8617       return;
8618     }
8619     if (!strncmp(message, "tellothers ", 11)) {
8620       if (appData.icsActive) {
8621         if (loggedOn) {
8622           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8623           SendToICS(buf1);
8624         }
8625       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
8626       return;
8627     }
8628     if (!strncmp(message, "tellall ", 8)) {
8629       if (appData.icsActive) {
8630         if (loggedOn) {
8631           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8632           SendToICS(buf1);
8633         }
8634       } else {
8635         DisplayNote(message + 8);
8636       }
8637       return;
8638     }
8639     if (strncmp(message, "warning", 7) == 0) {
8640         /* Undocumented feature, use tellusererror in new code */
8641         DisplayError(message, 0);
8642         return;
8643     }
8644     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8645         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8646         strcat(realname, " query");
8647         AskQuestion(realname, buf2, buf1, cps->pr);
8648         return;
8649     }
8650     /* Commands from the engine directly to ICS.  We don't allow these to be
8651      *  sent until we are logged on. Crafty kibitzes have been known to
8652      *  interfere with the login process.
8653      */
8654     if (loggedOn) {
8655         if (!strncmp(message, "tellics ", 8)) {
8656             SendToICS(message + 8);
8657             SendToICS("\n");
8658             return;
8659         }
8660         if (!strncmp(message, "tellicsnoalias ", 15)) {
8661             SendToICS(ics_prefix);
8662             SendToICS(message + 15);
8663             SendToICS("\n");
8664             return;
8665         }
8666         /* The following are for backward compatibility only */
8667         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8668             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8669             SendToICS(ics_prefix);
8670             SendToICS(message);
8671             SendToICS("\n");
8672             return;
8673         }
8674     }
8675     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8676         return;
8677     }
8678     if(!strncmp(message, "highlight ", 10)) {
8679         if(appData.testLegality && appData.markers) return;
8680         MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
8681         return;
8682     }
8683     if(!strncmp(message, "click ", 6)) {
8684         char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
8685         if(appData.testLegality || !appData.oneClick) return;
8686         sscanf(message+6, "%c%d%c", &f, &y, &c);
8687         x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
8688         if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
8689         x = x*squareSize + (x+1)*lineGap + squareSize/2;
8690         y = y*squareSize + (y+1)*lineGap + squareSize/2;
8691         f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
8692         if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
8693             LeftClick(Release, lastLeftX, lastLeftY);
8694         controlKey  = (c == ',');
8695         LeftClick(Press, x, y);
8696         LeftClick(Release, x, y);
8697         first.highlight = f;
8698         return;
8699     }
8700     /*
8701      * If the move is illegal, cancel it and redraw the board.
8702      * Also deal with other error cases.  Matching is rather loose
8703      * here to accommodate engines written before the spec.
8704      */
8705     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8706         strncmp(message, "Error", 5) == 0) {
8707         if (StrStr(message, "name") ||
8708             StrStr(message, "rating") || StrStr(message, "?") ||
8709             StrStr(message, "result") || StrStr(message, "board") ||
8710             StrStr(message, "bk") || StrStr(message, "computer") ||
8711             StrStr(message, "variant") || StrStr(message, "hint") ||
8712             StrStr(message, "random") || StrStr(message, "depth") ||
8713             StrStr(message, "accepted")) {
8714             return;
8715         }
8716         if (StrStr(message, "protover")) {
8717           /* Program is responding to input, so it's apparently done
8718              initializing, and this error message indicates it is
8719              protocol version 1.  So we don't need to wait any longer
8720              for it to initialize and send feature commands. */
8721           FeatureDone(cps, 1);
8722           cps->protocolVersion = 1;
8723           return;
8724         }
8725         cps->maybeThinking = FALSE;
8726
8727         if (StrStr(message, "draw")) {
8728             /* Program doesn't have "draw" command */
8729             cps->sendDrawOffers = 0;
8730             return;
8731         }
8732         if (cps->sendTime != 1 &&
8733             (StrStr(message, "time") || StrStr(message, "otim"))) {
8734           /* Program apparently doesn't have "time" or "otim" command */
8735           cps->sendTime = 0;
8736           return;
8737         }
8738         if (StrStr(message, "analyze")) {
8739             cps->analysisSupport = FALSE;
8740             cps->analyzing = FALSE;
8741 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8742             EditGameEvent(); // [HGM] try to preserve loaded game
8743             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8744             DisplayError(buf2, 0);
8745             return;
8746         }
8747         if (StrStr(message, "(no matching move)st")) {
8748           /* Special kludge for GNU Chess 4 only */
8749           cps->stKludge = TRUE;
8750           SendTimeControl(cps, movesPerSession, timeControl,
8751                           timeIncrement, appData.searchDepth,
8752                           searchTime);
8753           return;
8754         }
8755         if (StrStr(message, "(no matching move)sd")) {
8756           /* Special kludge for GNU Chess 4 only */
8757           cps->sdKludge = TRUE;
8758           SendTimeControl(cps, movesPerSession, timeControl,
8759                           timeIncrement, appData.searchDepth,
8760                           searchTime);
8761           return;
8762         }
8763         if (!StrStr(message, "llegal")) {
8764             return;
8765         }
8766         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8767             gameMode == IcsIdle) return;
8768         if (forwardMostMove <= backwardMostMove) return;
8769         if (pausing) PauseEvent();
8770       if(appData.forceIllegal) {
8771             // [HGM] illegal: machine refused move; force position after move into it
8772           SendToProgram("force\n", cps);
8773           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8774                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8775                 // when black is to move, while there might be nothing on a2 or black
8776                 // might already have the move. So send the board as if white has the move.
8777                 // But first we must change the stm of the engine, as it refused the last move
8778                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8779                 if(WhiteOnMove(forwardMostMove)) {
8780                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8781                     SendBoard(cps, forwardMostMove); // kludgeless board
8782                 } else {
8783                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8784                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8785                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8786                 }
8787           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8788             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8789                  gameMode == TwoMachinesPlay)
8790               SendToProgram("go\n", cps);
8791             return;
8792       } else
8793         if (gameMode == PlayFromGameFile) {
8794             /* Stop reading this game file */
8795             gameMode = EditGame;
8796             ModeHighlight();
8797         }
8798         /* [HGM] illegal-move claim should forfeit game when Xboard */
8799         /* only passes fully legal moves                            */
8800         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8801             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8802                                 "False illegal-move claim", GE_XBOARD );
8803             return; // do not take back move we tested as valid
8804         }
8805         currentMove = forwardMostMove-1;
8806         DisplayMove(currentMove-1); /* before DisplayMoveError */
8807         SwitchClocks(forwardMostMove-1); // [HGM] race
8808         DisplayBothClocks();
8809         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8810                 parseList[currentMove], _(cps->which));
8811         DisplayMoveError(buf1);
8812         DrawPosition(FALSE, boards[currentMove]);
8813
8814         SetUserThinkingEnables();
8815         return;
8816     }
8817     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8818         /* Program has a broken "time" command that
8819            outputs a string not ending in newline.
8820            Don't use it. */
8821         cps->sendTime = 0;
8822     }
8823
8824     /*
8825      * If chess program startup fails, exit with an error message.
8826      * Attempts to recover here are futile. [HGM] Well, we try anyway
8827      */
8828     if ((StrStr(message, "unknown host") != NULL)
8829         || (StrStr(message, "No remote directory") != NULL)
8830         || (StrStr(message, "not found") != NULL)
8831         || (StrStr(message, "No such file") != NULL)
8832         || (StrStr(message, "can't alloc") != NULL)
8833         || (StrStr(message, "Permission denied") != NULL)) {
8834
8835         cps->maybeThinking = FALSE;
8836         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8837                 _(cps->which), cps->program, cps->host, message);
8838         RemoveInputSource(cps->isr);
8839         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8840             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
8841             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
8842         }
8843         return;
8844     }
8845
8846     /*
8847      * Look for hint output
8848      */
8849     if (sscanf(message, "Hint: %s", buf1) == 1) {
8850         if (cps == &first && hintRequested) {
8851             hintRequested = FALSE;
8852             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8853                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8854                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8855                                     PosFlags(forwardMostMove),
8856                                     fromY, fromX, toY, toX, promoChar, buf1);
8857                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8858                 DisplayInformation(buf2);
8859             } else {
8860                 /* Hint move could not be parsed!? */
8861               snprintf(buf2, sizeof(buf2),
8862                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8863                         buf1, _(cps->which));
8864                 DisplayError(buf2, 0);
8865             }
8866         } else {
8867           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8868         }
8869         return;
8870     }
8871
8872     /*
8873      * Ignore other messages if game is not in progress
8874      */
8875     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8876         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8877
8878     /*
8879      * look for win, lose, draw, or draw offer
8880      */
8881     if (strncmp(message, "1-0", 3) == 0) {
8882         char *p, *q, *r = "";
8883         p = strchr(message, '{');
8884         if (p) {
8885             q = strchr(p, '}');
8886             if (q) {
8887                 *q = NULLCHAR;
8888                 r = p + 1;
8889             }
8890         }
8891         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8892         return;
8893     } else if (strncmp(message, "0-1", 3) == 0) {
8894         char *p, *q, *r = "";
8895         p = strchr(message, '{');
8896         if (p) {
8897             q = strchr(p, '}');
8898             if (q) {
8899                 *q = NULLCHAR;
8900                 r = p + 1;
8901             }
8902         }
8903         /* Kludge for Arasan 4.1 bug */
8904         if (strcmp(r, "Black resigns") == 0) {
8905             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8906             return;
8907         }
8908         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8909         return;
8910     } else if (strncmp(message, "1/2", 3) == 0) {
8911         char *p, *q, *r = "";
8912         p = strchr(message, '{');
8913         if (p) {
8914             q = strchr(p, '}');
8915             if (q) {
8916                 *q = NULLCHAR;
8917                 r = p + 1;
8918             }
8919         }
8920
8921         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8922         return;
8923
8924     } else if (strncmp(message, "White resign", 12) == 0) {
8925         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8926         return;
8927     } else if (strncmp(message, "Black resign", 12) == 0) {
8928         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8929         return;
8930     } else if (strncmp(message, "White matches", 13) == 0 ||
8931                strncmp(message, "Black matches", 13) == 0   ) {
8932         /* [HGM] ignore GNUShogi noises */
8933         return;
8934     } else if (strncmp(message, "White", 5) == 0 &&
8935                message[5] != '(' &&
8936                StrStr(message, "Black") == NULL) {
8937         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8938         return;
8939     } else if (strncmp(message, "Black", 5) == 0 &&
8940                message[5] != '(') {
8941         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8942         return;
8943     } else if (strcmp(message, "resign") == 0 ||
8944                strcmp(message, "computer resigns") == 0) {
8945         switch (gameMode) {
8946           case MachinePlaysBlack:
8947           case IcsPlayingBlack:
8948             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8949             break;
8950           case MachinePlaysWhite:
8951           case IcsPlayingWhite:
8952             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8953             break;
8954           case TwoMachinesPlay:
8955             if (cps->twoMachinesColor[0] == 'w')
8956               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8957             else
8958               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8959             break;
8960           default:
8961             /* can't happen */
8962             break;
8963         }
8964         return;
8965     } else if (strncmp(message, "opponent mates", 14) == 0) {
8966         switch (gameMode) {
8967           case MachinePlaysBlack:
8968           case IcsPlayingBlack:
8969             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8970             break;
8971           case MachinePlaysWhite:
8972           case IcsPlayingWhite:
8973             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8974             break;
8975           case TwoMachinesPlay:
8976             if (cps->twoMachinesColor[0] == 'w')
8977               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8978             else
8979               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8980             break;
8981           default:
8982             /* can't happen */
8983             break;
8984         }
8985         return;
8986     } else if (strncmp(message, "computer mates", 14) == 0) {
8987         switch (gameMode) {
8988           case MachinePlaysBlack:
8989           case IcsPlayingBlack:
8990             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8991             break;
8992           case MachinePlaysWhite:
8993           case IcsPlayingWhite:
8994             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8995             break;
8996           case TwoMachinesPlay:
8997             if (cps->twoMachinesColor[0] == 'w')
8998               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8999             else
9000               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9001             break;
9002           default:
9003             /* can't happen */
9004             break;
9005         }
9006         return;
9007     } else if (strncmp(message, "checkmate", 9) == 0) {
9008         if (WhiteOnMove(forwardMostMove)) {
9009             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9010         } else {
9011             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9012         }
9013         return;
9014     } else if (strstr(message, "Draw") != NULL ||
9015                strstr(message, "game is a draw") != NULL) {
9016         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9017         return;
9018     } else if (strstr(message, "offer") != NULL &&
9019                strstr(message, "draw") != NULL) {
9020 #if ZIPPY
9021         if (appData.zippyPlay && first.initDone) {
9022             /* Relay offer to ICS */
9023             SendToICS(ics_prefix);
9024             SendToICS("draw\n");
9025         }
9026 #endif
9027         cps->offeredDraw = 2; /* valid until this engine moves twice */
9028         if (gameMode == TwoMachinesPlay) {
9029             if (cps->other->offeredDraw) {
9030                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9031             /* [HGM] in two-machine mode we delay relaying draw offer      */
9032             /* until after we also have move, to see if it is really claim */
9033             }
9034         } else if (gameMode == MachinePlaysWhite ||
9035                    gameMode == MachinePlaysBlack) {
9036           if (userOfferedDraw) {
9037             DisplayInformation(_("Machine accepts your draw offer"));
9038             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9039           } else {
9040             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
9041           }
9042         }
9043     }
9044
9045
9046     /*
9047      * Look for thinking output
9048      */
9049     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9050           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9051                                 ) {
9052         int plylev, mvleft, mvtot, curscore, time;
9053         char mvname[MOVE_LEN];
9054         u64 nodes; // [DM]
9055         char plyext;
9056         int ignore = FALSE;
9057         int prefixHint = FALSE;
9058         mvname[0] = NULLCHAR;
9059
9060         switch (gameMode) {
9061           case MachinePlaysBlack:
9062           case IcsPlayingBlack:
9063             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9064             break;
9065           case MachinePlaysWhite:
9066           case IcsPlayingWhite:
9067             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9068             break;
9069           case AnalyzeMode:
9070           case AnalyzeFile:
9071             break;
9072           case IcsObserving: /* [DM] icsEngineAnalyze */
9073             if (!appData.icsEngineAnalyze) ignore = TRUE;
9074             break;
9075           case TwoMachinesPlay:
9076             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9077                 ignore = TRUE;
9078             }
9079             break;
9080           default:
9081             ignore = TRUE;
9082             break;
9083         }
9084
9085         if (!ignore) {
9086             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9087             buf1[0] = NULLCHAR;
9088             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9089                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9090
9091                 if (plyext != ' ' && plyext != '\t') {
9092                     time *= 100;
9093                 }
9094
9095                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9096                 if( cps->scoreIsAbsolute &&
9097                     ( gameMode == MachinePlaysBlack ||
9098                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9099                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
9100                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9101                      !WhiteOnMove(currentMove)
9102                     ) )
9103                 {
9104                     curscore = -curscore;
9105                 }
9106
9107                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9108
9109                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9110                         char buf[MSG_SIZ];
9111                         FILE *f;
9112                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9113                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9114                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9115                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9116                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
9117                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9118                                 fclose(f);
9119                         } else DisplayError(_("failed writing PV"), 0);
9120                 }
9121
9122                 tempStats.depth = plylev;
9123                 tempStats.nodes = nodes;
9124                 tempStats.time = time;
9125                 tempStats.score = curscore;
9126                 tempStats.got_only_move = 0;
9127
9128                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9129                         int ticklen;
9130
9131                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
9132                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9133                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9134                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9135                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9136                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9137                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9138                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9139                 }
9140
9141                 /* Buffer overflow protection */
9142                 if (pv[0] != NULLCHAR) {
9143                     if (strlen(pv) >= sizeof(tempStats.movelist)
9144                         && appData.debugMode) {
9145                         fprintf(debugFP,
9146                                 "PV is too long; using the first %u bytes.\n",
9147                                 (unsigned) sizeof(tempStats.movelist) - 1);
9148                     }
9149
9150                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9151                 } else {
9152                     sprintf(tempStats.movelist, " no PV\n");
9153                 }
9154
9155                 if (tempStats.seen_stat) {
9156                     tempStats.ok_to_send = 1;
9157                 }
9158
9159                 if (strchr(tempStats.movelist, '(') != NULL) {
9160                     tempStats.line_is_book = 1;
9161                     tempStats.nr_moves = 0;
9162                     tempStats.moves_left = 0;
9163                 } else {
9164                     tempStats.line_is_book = 0;
9165                 }
9166
9167                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9168                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9169
9170                 SendProgramStatsToFrontend( cps, &tempStats );
9171
9172                 /*
9173                     [AS] Protect the thinkOutput buffer from overflow... this
9174                     is only useful if buf1 hasn't overflowed first!
9175                 */
9176                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
9177                          plylev,
9178                          (gameMode == TwoMachinesPlay ?
9179                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9180                          ((double) curscore) / 100.0,
9181                          prefixHint ? lastHint : "",
9182                          prefixHint ? " " : "" );
9183
9184                 if( buf1[0] != NULLCHAR ) {
9185                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9186
9187                     if( strlen(pv) > max_len ) {
9188                         if( appData.debugMode) {
9189                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9190                         }
9191                         pv[max_len+1] = '\0';
9192                     }
9193
9194                     strcat( thinkOutput, pv);
9195                 }
9196
9197                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9198                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9199                     DisplayMove(currentMove - 1);
9200                 }
9201                 return;
9202
9203             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9204                 /* crafty (9.25+) says "(only move) <move>"
9205                  * if there is only 1 legal move
9206                  */
9207                 sscanf(p, "(only move) %s", buf1);
9208                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9209                 sprintf(programStats.movelist, "%s (only move)", buf1);
9210                 programStats.depth = 1;
9211                 programStats.nr_moves = 1;
9212                 programStats.moves_left = 1;
9213                 programStats.nodes = 1;
9214                 programStats.time = 1;
9215                 programStats.got_only_move = 1;
9216
9217                 /* Not really, but we also use this member to
9218                    mean "line isn't going to change" (Crafty
9219                    isn't searching, so stats won't change) */
9220                 programStats.line_is_book = 1;
9221
9222                 SendProgramStatsToFrontend( cps, &programStats );
9223
9224                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9225                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9226                     DisplayMove(currentMove - 1);
9227                 }
9228                 return;
9229             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9230                               &time, &nodes, &plylev, &mvleft,
9231                               &mvtot, mvname) >= 5) {
9232                 /* The stat01: line is from Crafty (9.29+) in response
9233                    to the "." command */
9234                 programStats.seen_stat = 1;
9235                 cps->maybeThinking = TRUE;
9236
9237                 if (programStats.got_only_move || !appData.periodicUpdates)
9238                   return;
9239
9240                 programStats.depth = plylev;
9241                 programStats.time = time;
9242                 programStats.nodes = nodes;
9243                 programStats.moves_left = mvleft;
9244                 programStats.nr_moves = mvtot;
9245                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9246                 programStats.ok_to_send = 1;
9247                 programStats.movelist[0] = '\0';
9248
9249                 SendProgramStatsToFrontend( cps, &programStats );
9250
9251                 return;
9252
9253             } else if (strncmp(message,"++",2) == 0) {
9254                 /* Crafty 9.29+ outputs this */
9255                 programStats.got_fail = 2;
9256                 return;
9257
9258             } else if (strncmp(message,"--",2) == 0) {
9259                 /* Crafty 9.29+ outputs this */
9260                 programStats.got_fail = 1;
9261                 return;
9262
9263             } else if (thinkOutput[0] != NULLCHAR &&
9264                        strncmp(message, "    ", 4) == 0) {
9265                 unsigned message_len;
9266
9267                 p = message;
9268                 while (*p && *p == ' ') p++;
9269
9270                 message_len = strlen( p );
9271
9272                 /* [AS] Avoid buffer overflow */
9273                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9274                     strcat(thinkOutput, " ");
9275                     strcat(thinkOutput, p);
9276                 }
9277
9278                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9279                     strcat(programStats.movelist, " ");
9280                     strcat(programStats.movelist, p);
9281                 }
9282
9283                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9284                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9285                     DisplayMove(currentMove - 1);
9286                 }
9287                 return;
9288             }
9289         }
9290         else {
9291             buf1[0] = NULLCHAR;
9292
9293             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9294                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9295             {
9296                 ChessProgramStats cpstats;
9297
9298                 if (plyext != ' ' && plyext != '\t') {
9299                     time *= 100;
9300                 }
9301
9302                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9303                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9304                     curscore = -curscore;
9305                 }
9306
9307                 cpstats.depth = plylev;
9308                 cpstats.nodes = nodes;
9309                 cpstats.time = time;
9310                 cpstats.score = curscore;
9311                 cpstats.got_only_move = 0;
9312                 cpstats.movelist[0] = '\0';
9313
9314                 if (buf1[0] != NULLCHAR) {
9315                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9316                 }
9317
9318                 cpstats.ok_to_send = 0;
9319                 cpstats.line_is_book = 0;
9320                 cpstats.nr_moves = 0;
9321                 cpstats.moves_left = 0;
9322
9323                 SendProgramStatsToFrontend( cps, &cpstats );
9324             }
9325         }
9326     }
9327 }
9328
9329
9330 /* Parse a game score from the character string "game", and
9331    record it as the history of the current game.  The game
9332    score is NOT assumed to start from the standard position.
9333    The display is not updated in any way.
9334    */
9335 void
9336 ParseGameHistory (char *game)
9337 {
9338     ChessMove moveType;
9339     int fromX, fromY, toX, toY, boardIndex;
9340     char promoChar;
9341     char *p, *q;
9342     char buf[MSG_SIZ];
9343
9344     if (appData.debugMode)
9345       fprintf(debugFP, "Parsing game history: %s\n", game);
9346
9347     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9348     gameInfo.site = StrSave(appData.icsHost);
9349     gameInfo.date = PGNDate();
9350     gameInfo.round = StrSave("-");
9351
9352     /* Parse out names of players */
9353     while (*game == ' ') game++;
9354     p = buf;
9355     while (*game != ' ') *p++ = *game++;
9356     *p = NULLCHAR;
9357     gameInfo.white = StrSave(buf);
9358     while (*game == ' ') game++;
9359     p = buf;
9360     while (*game != ' ' && *game != '\n') *p++ = *game++;
9361     *p = NULLCHAR;
9362     gameInfo.black = StrSave(buf);
9363
9364     /* Parse moves */
9365     boardIndex = blackPlaysFirst ? 1 : 0;
9366     yynewstr(game);
9367     for (;;) {
9368         yyboardindex = boardIndex;
9369         moveType = (ChessMove) Myylex();
9370         switch (moveType) {
9371           case IllegalMove:             /* maybe suicide chess, etc. */
9372   if (appData.debugMode) {
9373     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9374     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9375     setbuf(debugFP, NULL);
9376   }
9377           case WhitePromotion:
9378           case BlackPromotion:
9379           case WhiteNonPromotion:
9380           case BlackNonPromotion:
9381           case NormalMove:
9382           case WhiteCapturesEnPassant:
9383           case BlackCapturesEnPassant:
9384           case WhiteKingSideCastle:
9385           case WhiteQueenSideCastle:
9386           case BlackKingSideCastle:
9387           case BlackQueenSideCastle:
9388           case WhiteKingSideCastleWild:
9389           case WhiteQueenSideCastleWild:
9390           case BlackKingSideCastleWild:
9391           case BlackQueenSideCastleWild:
9392           /* PUSH Fabien */
9393           case WhiteHSideCastleFR:
9394           case WhiteASideCastleFR:
9395           case BlackHSideCastleFR:
9396           case BlackASideCastleFR:
9397           /* POP Fabien */
9398             fromX = currentMoveString[0] - AAA;
9399             fromY = currentMoveString[1] - ONE;
9400             toX = currentMoveString[2] - AAA;
9401             toY = currentMoveString[3] - ONE;
9402             promoChar = currentMoveString[4];
9403             break;
9404           case WhiteDrop:
9405           case BlackDrop:
9406             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9407             fromX = moveType == WhiteDrop ?
9408               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9409             (int) CharToPiece(ToLower(currentMoveString[0]));
9410             fromY = DROP_RANK;
9411             toX = currentMoveString[2] - AAA;
9412             toY = currentMoveString[3] - ONE;
9413             promoChar = NULLCHAR;
9414             break;
9415           case AmbiguousMove:
9416             /* bug? */
9417             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9418   if (appData.debugMode) {
9419     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9420     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9421     setbuf(debugFP, NULL);
9422   }
9423             DisplayError(buf, 0);
9424             return;
9425           case ImpossibleMove:
9426             /* bug? */
9427             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9428   if (appData.debugMode) {
9429     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9430     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9431     setbuf(debugFP, NULL);
9432   }
9433             DisplayError(buf, 0);
9434             return;
9435           case EndOfFile:
9436             if (boardIndex < backwardMostMove) {
9437                 /* Oops, gap.  How did that happen? */
9438                 DisplayError(_("Gap in move list"), 0);
9439                 return;
9440             }
9441             backwardMostMove =  blackPlaysFirst ? 1 : 0;
9442             if (boardIndex > forwardMostMove) {
9443                 forwardMostMove = boardIndex;
9444             }
9445             return;
9446           case ElapsedTime:
9447             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9448                 strcat(parseList[boardIndex-1], " ");
9449                 strcat(parseList[boardIndex-1], yy_text);
9450             }
9451             continue;
9452           case Comment:
9453           case PGNTag:
9454           case NAG:
9455           default:
9456             /* ignore */
9457             continue;
9458           case WhiteWins:
9459           case BlackWins:
9460           case GameIsDrawn:
9461           case GameUnfinished:
9462             if (gameMode == IcsExamining) {
9463                 if (boardIndex < backwardMostMove) {
9464                     /* Oops, gap.  How did that happen? */
9465                     return;
9466                 }
9467                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9468                 return;
9469             }
9470             gameInfo.result = moveType;
9471             p = strchr(yy_text, '{');
9472             if (p == NULL) p = strchr(yy_text, '(');
9473             if (p == NULL) {
9474                 p = yy_text;
9475                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9476             } else {
9477                 q = strchr(p, *p == '{' ? '}' : ')');
9478                 if (q != NULL) *q = NULLCHAR;
9479                 p++;
9480             }
9481             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9482             gameInfo.resultDetails = StrSave(p);
9483             continue;
9484         }
9485         if (boardIndex >= forwardMostMove &&
9486             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9487             backwardMostMove = blackPlaysFirst ? 1 : 0;
9488             return;
9489         }
9490         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9491                                  fromY, fromX, toY, toX, promoChar,
9492                                  parseList[boardIndex]);
9493         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9494         /* currentMoveString is set as a side-effect of yylex */
9495         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9496         strcat(moveList[boardIndex], "\n");
9497         boardIndex++;
9498         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9499         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9500           case MT_NONE:
9501           case MT_STALEMATE:
9502           default:
9503             break;
9504           case MT_CHECK:
9505             if(gameInfo.variant != VariantShogi)
9506                 strcat(parseList[boardIndex - 1], "+");
9507             break;
9508           case MT_CHECKMATE:
9509           case MT_STAINMATE:
9510             strcat(parseList[boardIndex - 1], "#");
9511             break;
9512         }
9513     }
9514 }
9515
9516
9517 /* Apply a move to the given board  */
9518 void
9519 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9520 {
9521   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9522   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9523
9524     /* [HGM] compute & store e.p. status and castling rights for new position */
9525     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9526
9527       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9528       oldEP = (signed char)board[EP_STATUS];
9529       board[EP_STATUS] = EP_NONE;
9530
9531   if (fromY == DROP_RANK) {
9532         /* must be first */
9533         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9534             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9535             return;
9536         }
9537         piece = board[toY][toX] = (ChessSquare) fromX;
9538   } else {
9539       int i;
9540
9541       if( board[toY][toX] != EmptySquare )
9542            board[EP_STATUS] = EP_CAPTURE;
9543
9544       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9545            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9546                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9547       } else
9548       if( board[fromY][fromX] == WhitePawn ) {
9549            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9550                board[EP_STATUS] = EP_PAWN_MOVE;
9551            if( toY-fromY==2) {
9552                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9553                         gameInfo.variant != VariantBerolina || toX < fromX)
9554                       board[EP_STATUS] = toX | berolina;
9555                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9556                         gameInfo.variant != VariantBerolina || toX > fromX)
9557                       board[EP_STATUS] = toX;
9558            }
9559       } else
9560       if( board[fromY][fromX] == BlackPawn ) {
9561            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9562                board[EP_STATUS] = EP_PAWN_MOVE;
9563            if( toY-fromY== -2) {
9564                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9565                         gameInfo.variant != VariantBerolina || toX < fromX)
9566                       board[EP_STATUS] = toX | berolina;
9567                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9568                         gameInfo.variant != VariantBerolina || toX > fromX)
9569                       board[EP_STATUS] = toX;
9570            }
9571        }
9572
9573        for(i=0; i<nrCastlingRights; i++) {
9574            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9575               board[CASTLING][i] == toX   && castlingRank[i] == toY
9576              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9577        }
9578
9579        if(gameInfo.variant == VariantSChess) { // update virginity
9580            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
9581            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
9582            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
9583            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
9584        }
9585
9586      if (fromX == toX && fromY == toY) return;
9587
9588      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9589      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9590      if(gameInfo.variant == VariantKnightmate)
9591          king += (int) WhiteUnicorn - (int) WhiteKing;
9592
9593     /* Code added by Tord: */
9594     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9595     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9596         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9597       board[fromY][fromX] = EmptySquare;
9598       board[toY][toX] = EmptySquare;
9599       if((toX > fromX) != (piece == WhiteRook)) {
9600         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9601       } else {
9602         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9603       }
9604     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9605                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9606       board[fromY][fromX] = EmptySquare;
9607       board[toY][toX] = EmptySquare;
9608       if((toX > fromX) != (piece == BlackRook)) {
9609         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9610       } else {
9611         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9612       }
9613     /* End of code added by Tord */
9614
9615     } else if (board[fromY][fromX] == king
9616         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9617         && toY == fromY && toX > fromX+1) {
9618         board[fromY][fromX] = EmptySquare;
9619         board[toY][toX] = king;
9620         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9621         board[fromY][BOARD_RGHT-1] = EmptySquare;
9622     } else if (board[fromY][fromX] == king
9623         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9624                && toY == fromY && toX < fromX-1) {
9625         board[fromY][fromX] = EmptySquare;
9626         board[toY][toX] = king;
9627         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9628         board[fromY][BOARD_LEFT] = EmptySquare;
9629     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9630                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9631                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9632                ) {
9633         /* white pawn promotion */
9634         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9635         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9636             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9637         board[fromY][fromX] = EmptySquare;
9638     } else if ((fromY >= BOARD_HEIGHT>>1)
9639                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9640                && (toX != fromX)
9641                && gameInfo.variant != VariantXiangqi
9642                && gameInfo.variant != VariantBerolina
9643                && (board[fromY][fromX] == WhitePawn)
9644                && (board[toY][toX] == EmptySquare)) {
9645         board[fromY][fromX] = EmptySquare;
9646         board[toY][toX] = WhitePawn;
9647         captured = board[toY - 1][toX];
9648         board[toY - 1][toX] = EmptySquare;
9649     } else if ((fromY == BOARD_HEIGHT-4)
9650                && (toX == fromX)
9651                && gameInfo.variant == VariantBerolina
9652                && (board[fromY][fromX] == WhitePawn)
9653                && (board[toY][toX] == EmptySquare)) {
9654         board[fromY][fromX] = EmptySquare;
9655         board[toY][toX] = WhitePawn;
9656         if(oldEP & EP_BEROLIN_A) {
9657                 captured = board[fromY][fromX-1];
9658                 board[fromY][fromX-1] = EmptySquare;
9659         }else{  captured = board[fromY][fromX+1];
9660                 board[fromY][fromX+1] = EmptySquare;
9661         }
9662     } else if (board[fromY][fromX] == king
9663         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9664                && toY == fromY && toX > fromX+1) {
9665         board[fromY][fromX] = EmptySquare;
9666         board[toY][toX] = king;
9667         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9668         board[fromY][BOARD_RGHT-1] = EmptySquare;
9669     } else if (board[fromY][fromX] == king
9670         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9671                && toY == fromY && toX < fromX-1) {
9672         board[fromY][fromX] = EmptySquare;
9673         board[toY][toX] = king;
9674         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9675         board[fromY][BOARD_LEFT] = EmptySquare;
9676     } else if (fromY == 7 && fromX == 3
9677                && board[fromY][fromX] == BlackKing
9678                && toY == 7 && toX == 5) {
9679         board[fromY][fromX] = EmptySquare;
9680         board[toY][toX] = BlackKing;
9681         board[fromY][7] = EmptySquare;
9682         board[toY][4] = BlackRook;
9683     } else if (fromY == 7 && fromX == 3
9684                && board[fromY][fromX] == BlackKing
9685                && toY == 7 && toX == 1) {
9686         board[fromY][fromX] = EmptySquare;
9687         board[toY][toX] = BlackKing;
9688         board[fromY][0] = EmptySquare;
9689         board[toY][2] = BlackRook;
9690     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9691                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9692                && toY < promoRank && promoChar
9693                ) {
9694         /* black pawn promotion */
9695         board[toY][toX] = CharToPiece(ToLower(promoChar));
9696         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9697             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9698         board[fromY][fromX] = EmptySquare;
9699     } else if ((fromY < BOARD_HEIGHT>>1)
9700                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9701                && (toX != fromX)
9702                && gameInfo.variant != VariantXiangqi
9703                && gameInfo.variant != VariantBerolina
9704                && (board[fromY][fromX] == BlackPawn)
9705                && (board[toY][toX] == EmptySquare)) {
9706         board[fromY][fromX] = EmptySquare;
9707         board[toY][toX] = BlackPawn;
9708         captured = board[toY + 1][toX];
9709         board[toY + 1][toX] = EmptySquare;
9710     } else if ((fromY == 3)
9711                && (toX == fromX)
9712                && gameInfo.variant == VariantBerolina
9713                && (board[fromY][fromX] == BlackPawn)
9714                && (board[toY][toX] == EmptySquare)) {
9715         board[fromY][fromX] = EmptySquare;
9716         board[toY][toX] = BlackPawn;
9717         if(oldEP & EP_BEROLIN_A) {
9718                 captured = board[fromY][fromX-1];
9719                 board[fromY][fromX-1] = EmptySquare;
9720         }else{  captured = board[fromY][fromX+1];
9721                 board[fromY][fromX+1] = EmptySquare;
9722         }
9723     } else {
9724         board[toY][toX] = board[fromY][fromX];
9725         board[fromY][fromX] = EmptySquare;
9726     }
9727   }
9728
9729     if (gameInfo.holdingsWidth != 0) {
9730
9731       /* !!A lot more code needs to be written to support holdings  */
9732       /* [HGM] OK, so I have written it. Holdings are stored in the */
9733       /* penultimate board files, so they are automaticlly stored   */
9734       /* in the game history.                                       */
9735       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9736                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9737         /* Delete from holdings, by decreasing count */
9738         /* and erasing image if necessary            */
9739         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9740         if(p < (int) BlackPawn) { /* white drop */
9741              p -= (int)WhitePawn;
9742                  p = PieceToNumber((ChessSquare)p);
9743              if(p >= gameInfo.holdingsSize) p = 0;
9744              if(--board[p][BOARD_WIDTH-2] <= 0)
9745                   board[p][BOARD_WIDTH-1] = EmptySquare;
9746              if((int)board[p][BOARD_WIDTH-2] < 0)
9747                         board[p][BOARD_WIDTH-2] = 0;
9748         } else {                  /* black drop */
9749              p -= (int)BlackPawn;
9750                  p = PieceToNumber((ChessSquare)p);
9751              if(p >= gameInfo.holdingsSize) p = 0;
9752              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9753                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9754              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9755                         board[BOARD_HEIGHT-1-p][1] = 0;
9756         }
9757       }
9758       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9759           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9760         /* [HGM] holdings: Add to holdings, if holdings exist */
9761         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9762                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9763                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9764         }
9765         p = (int) captured;
9766         if (p >= (int) BlackPawn) {
9767           p -= (int)BlackPawn;
9768           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9769                   /* in Shogi restore piece to its original  first */
9770                   captured = (ChessSquare) (DEMOTED captured);
9771                   p = DEMOTED p;
9772           }
9773           p = PieceToNumber((ChessSquare)p);
9774           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9775           board[p][BOARD_WIDTH-2]++;
9776           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9777         } else {
9778           p -= (int)WhitePawn;
9779           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9780                   captured = (ChessSquare) (DEMOTED captured);
9781                   p = DEMOTED p;
9782           }
9783           p = PieceToNumber((ChessSquare)p);
9784           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9785           board[BOARD_HEIGHT-1-p][1]++;
9786           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9787         }
9788       }
9789     } else if (gameInfo.variant == VariantAtomic) {
9790       if (captured != EmptySquare) {
9791         int y, x;
9792         for (y = toY-1; y <= toY+1; y++) {
9793           for (x = toX-1; x <= toX+1; x++) {
9794             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9795                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9796               board[y][x] = EmptySquare;
9797             }
9798           }
9799         }
9800         board[toY][toX] = EmptySquare;
9801       }
9802     }
9803     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9804         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9805     } else
9806     if(promoChar == '+') {
9807         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
9808         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9809     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9810         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9811         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
9812            && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
9813         board[toY][toX] = newPiece;
9814     }
9815     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9816                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9817         // [HGM] superchess: take promotion piece out of holdings
9818         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9819         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9820             if(!--board[k][BOARD_WIDTH-2])
9821                 board[k][BOARD_WIDTH-1] = EmptySquare;
9822         } else {
9823             if(!--board[BOARD_HEIGHT-1-k][1])
9824                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9825         }
9826     }
9827
9828 }
9829
9830 /* Updates forwardMostMove */
9831 void
9832 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
9833 {
9834 //    forwardMostMove++; // [HGM] bare: moved downstream
9835
9836     (void) CoordsToAlgebraic(boards[forwardMostMove],
9837                              PosFlags(forwardMostMove),
9838                              fromY, fromX, toY, toX, promoChar,
9839                              parseList[forwardMostMove]);
9840
9841     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9842         int timeLeft; static int lastLoadFlag=0; int king, piece;
9843         piece = boards[forwardMostMove][fromY][fromX];
9844         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9845         if(gameInfo.variant == VariantKnightmate)
9846             king += (int) WhiteUnicorn - (int) WhiteKing;
9847         if(forwardMostMove == 0) {
9848             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9849                 fprintf(serverMoves, "%s;", UserName());
9850             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9851                 fprintf(serverMoves, "%s;", second.tidy);
9852             fprintf(serverMoves, "%s;", first.tidy);
9853             if(gameMode == MachinePlaysWhite)
9854                 fprintf(serverMoves, "%s;", UserName());
9855             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9856                 fprintf(serverMoves, "%s;", second.tidy);
9857         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9858         lastLoadFlag = loadFlag;
9859         // print base move
9860         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9861         // print castling suffix
9862         if( toY == fromY && piece == king ) {
9863             if(toX-fromX > 1)
9864                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9865             if(fromX-toX >1)
9866                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9867         }
9868         // e.p. suffix
9869         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9870              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9871              boards[forwardMostMove][toY][toX] == EmptySquare
9872              && fromX != toX && fromY != toY)
9873                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9874         // promotion suffix
9875         if(promoChar != NULLCHAR) {
9876             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
9877                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
9878                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
9879             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
9880         }
9881         if(!loadFlag) {
9882                 char buf[MOVE_LEN*2], *p; int len;
9883             fprintf(serverMoves, "/%d/%d",
9884                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9885             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9886             else                      timeLeft = blackTimeRemaining/1000;
9887             fprintf(serverMoves, "/%d", timeLeft);
9888                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
9889                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
9890                 if(p = strchr(buf, '=')) *p = NULLCHAR;
9891                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
9892             fprintf(serverMoves, "/%s", buf);
9893         }
9894         fflush(serverMoves);
9895     }
9896
9897     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
9898         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
9899       return;
9900     }
9901     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9902     if (commentList[forwardMostMove+1] != NULL) {
9903         free(commentList[forwardMostMove+1]);
9904         commentList[forwardMostMove+1] = NULL;
9905     }
9906     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9907     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9908     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9909     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9910     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9911     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9912     adjustedClock = FALSE;
9913     gameInfo.result = GameUnfinished;
9914     if (gameInfo.resultDetails != NULL) {
9915         free(gameInfo.resultDetails);
9916         gameInfo.resultDetails = NULL;
9917     }
9918     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9919                               moveList[forwardMostMove - 1]);
9920     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9921       case MT_NONE:
9922       case MT_STALEMATE:
9923       default:
9924         break;
9925       case MT_CHECK:
9926         if(gameInfo.variant != VariantShogi)
9927             strcat(parseList[forwardMostMove - 1], "+");
9928         break;
9929       case MT_CHECKMATE:
9930       case MT_STAINMATE:
9931         strcat(parseList[forwardMostMove - 1], "#");
9932         break;
9933     }
9934
9935 }
9936
9937 /* Updates currentMove if not pausing */
9938 void
9939 ShowMove (int fromX, int fromY, int toX, int toY)
9940 {
9941     int instant = (gameMode == PlayFromGameFile) ?
9942         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9943     if(appData.noGUI) return;
9944     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9945         if (!instant) {
9946             if (forwardMostMove == currentMove + 1) {
9947                 AnimateMove(boards[forwardMostMove - 1],
9948                             fromX, fromY, toX, toY);
9949             }
9950         }
9951         currentMove = forwardMostMove;
9952     }
9953
9954     if (instant) return;
9955
9956     DisplayMove(currentMove - 1);
9957     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9958             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
9959                 SetHighlights(fromX, fromY, toX, toY);
9960             }
9961     }
9962     DrawPosition(FALSE, boards[currentMove]);
9963     DisplayBothClocks();
9964     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9965 }
9966
9967 void
9968 SendEgtPath (ChessProgramState *cps)
9969 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9970         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9971
9972         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9973
9974         while(*p) {
9975             char c, *q = name+1, *r, *s;
9976
9977             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9978             while(*p && *p != ',') *q++ = *p++;
9979             *q++ = ':'; *q = 0;
9980             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9981                 strcmp(name, ",nalimov:") == 0 ) {
9982                 // take nalimov path from the menu-changeable option first, if it is defined
9983               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9984                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9985             } else
9986             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9987                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9988                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9989                 s = r = StrStr(s, ":") + 1; // beginning of path info
9990                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9991                 c = *r; *r = 0;             // temporarily null-terminate path info
9992                     *--q = 0;               // strip of trailig ':' from name
9993                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9994                 *r = c;
9995                 SendToProgram(buf,cps);     // send egtbpath command for this format
9996             }
9997             if(*p == ',') p++; // read away comma to position for next format name
9998         }
9999 }
10000
10001 static int
10002 NonStandardBoardSize ()
10003 {
10004       /* [HGM] Awkward testing. Should really be a table */
10005       int overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
10006       if( gameInfo.variant == VariantXiangqi )
10007            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
10008       if( gameInfo.variant == VariantShogi )
10009            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
10010       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
10011            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
10012       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
10013           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
10014            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
10015       if( gameInfo.variant == VariantCourier )
10016            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
10017       if( gameInfo.variant == VariantSuper )
10018            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
10019       if( gameInfo.variant == VariantGreat )
10020            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
10021       if( gameInfo.variant == VariantSChess )
10022            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
10023       if( gameInfo.variant == VariantGrand )
10024            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
10025       return overruled;
10026 }
10027
10028 void
10029 InitChessProgram (ChessProgramState *cps, int setup)
10030 /* setup needed to setup FRC opening position */
10031 {
10032     char buf[MSG_SIZ], b[MSG_SIZ];
10033     if (appData.noChessProgram) return;
10034     hintRequested = FALSE;
10035     bookRequested = FALSE;
10036
10037     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10038     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10039     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10040     if(cps->memSize) { /* [HGM] memory */
10041       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10042         SendToProgram(buf, cps);
10043     }
10044     SendEgtPath(cps); /* [HGM] EGT */
10045     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10046       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10047         SendToProgram(buf, cps);
10048     }
10049
10050     SendToProgram(cps->initString, cps);
10051     if (gameInfo.variant != VariantNormal &&
10052         gameInfo.variant != VariantLoadable
10053         /* [HGM] also send variant if board size non-standard */
10054         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
10055                                             ) {
10056       char *v = VariantName(gameInfo.variant);
10057       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
10058         /* [HGM] in protocol 1 we have to assume all variants valid */
10059         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
10060         DisplayFatalError(buf, 0, 1);
10061         return;
10062       }
10063
10064       if(NonStandardBoardSize()) { /* [HGM] make prefix for non-standard board size. */
10065         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
10066                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
10067            /* [HGM] varsize: try first if this defiant size variant is specifically known */
10068            if(StrStr(cps->variants, b) == NULL) {
10069                // specific sized variant not known, check if general sizing allowed
10070                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
10071                    if(StrStr(cps->variants, "boardsize") == NULL) {
10072                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10073                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
10074                        DisplayFatalError(buf, 0, 1);
10075                        return;
10076                    }
10077                    /* [HGM] here we really should compare with the maximum supported board size */
10078                }
10079            }
10080       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
10081       snprintf(buf, MSG_SIZ, "variant %s\n", b);
10082       SendToProgram(buf, cps);
10083     }
10084     currentlyInitializedVariant = gameInfo.variant;
10085
10086     /* [HGM] send opening position in FRC to first engine */
10087     if(setup) {
10088           SendToProgram("force\n", cps);
10089           SendBoard(cps, 0);
10090           /* engine is now in force mode! Set flag to wake it up after first move. */
10091           setboardSpoiledMachineBlack = 1;
10092     }
10093
10094     if (cps->sendICS) {
10095       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10096       SendToProgram(buf, cps);
10097     }
10098     cps->maybeThinking = FALSE;
10099     cps->offeredDraw = 0;
10100     if (!appData.icsActive) {
10101         SendTimeControl(cps, movesPerSession, timeControl,
10102                         timeIncrement, appData.searchDepth,
10103                         searchTime);
10104     }
10105     if (appData.showThinking
10106         // [HGM] thinking: four options require thinking output to be sent
10107         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10108                                 ) {
10109         SendToProgram("post\n", cps);
10110     }
10111     SendToProgram("hard\n", cps);
10112     if (!appData.ponderNextMove) {
10113         /* Warning: "easy" is a toggle in GNU Chess, so don't send
10114            it without being sure what state we are in first.  "hard"
10115            is not a toggle, so that one is OK.
10116          */
10117         SendToProgram("easy\n", cps);
10118     }
10119     if (cps->usePing) {
10120       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
10121       SendToProgram(buf, cps);
10122     }
10123     cps->initDone = TRUE;
10124     ClearEngineOutputPane(cps == &second);
10125 }
10126
10127
10128 void
10129 ResendOptions (ChessProgramState *cps)
10130 { // send the stored value of the options
10131   int i;
10132   char buf[MSG_SIZ];
10133   Option *opt = cps->option;
10134   for(i=0; i<cps->nrOptions; i++, opt++) {
10135       switch(opt->type) {
10136         case Spin:
10137         case Slider:
10138         case CheckBox:
10139             snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10140           break;
10141         case ComboBox:
10142           snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10143           break;
10144         default:
10145             snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10146           break;
10147         case Button:
10148         case SaveButton:
10149           continue;
10150       }
10151       SendToProgram(buf, cps);
10152   }
10153 }
10154
10155 void
10156 StartChessProgram (ChessProgramState *cps)
10157 {
10158     char buf[MSG_SIZ];
10159     int err;
10160
10161     if (appData.noChessProgram) return;
10162     cps->initDone = FALSE;
10163
10164     if (strcmp(cps->host, "localhost") == 0) {
10165         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10166     } else if (*appData.remoteShell == NULLCHAR) {
10167         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10168     } else {
10169         if (*appData.remoteUser == NULLCHAR) {
10170           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10171                     cps->program);
10172         } else {
10173           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10174                     cps->host, appData.remoteUser, cps->program);
10175         }
10176         err = StartChildProcess(buf, "", &cps->pr);
10177     }
10178
10179     if (err != 0) {
10180       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10181         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10182         if(cps != &first) return;
10183         appData.noChessProgram = TRUE;
10184         ThawUI();
10185         SetNCPMode();
10186 //      DisplayFatalError(buf, err, 1);
10187 //      cps->pr = NoProc;
10188 //      cps->isr = NULL;
10189         return;
10190     }
10191
10192     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10193     if (cps->protocolVersion > 1) {
10194       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10195       if(!cps->reload) { // do not clear options when reloading because of -xreuse
10196         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10197         cps->comboCnt = 0;  //                and values of combo boxes
10198       }
10199       SendToProgram(buf, cps);
10200       if(cps->reload) ResendOptions(cps);
10201     } else {
10202       SendToProgram("xboard\n", cps);
10203     }
10204 }
10205
10206 void
10207 TwoMachinesEventIfReady P((void))
10208 {
10209   static int curMess = 0;
10210   if (first.lastPing != first.lastPong) {
10211     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10212     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10213     return;
10214   }
10215   if (second.lastPing != second.lastPong) {
10216     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10217     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10218     return;
10219   }
10220   DisplayMessage("", ""); curMess = 0;
10221   TwoMachinesEvent();
10222 }
10223
10224 char *
10225 MakeName (char *template)
10226 {
10227     time_t clock;
10228     struct tm *tm;
10229     static char buf[MSG_SIZ];
10230     char *p = buf;
10231     int i;
10232
10233     clock = time((time_t *)NULL);
10234     tm = localtime(&clock);
10235
10236     while(*p++ = *template++) if(p[-1] == '%') {
10237         switch(*template++) {
10238           case 0:   *p = 0; return buf;
10239           case 'Y': i = tm->tm_year+1900; break;
10240           case 'y': i = tm->tm_year-100; break;
10241           case 'M': i = tm->tm_mon+1; break;
10242           case 'd': i = tm->tm_mday; break;
10243           case 'h': i = tm->tm_hour; break;
10244           case 'm': i = tm->tm_min; break;
10245           case 's': i = tm->tm_sec; break;
10246           default:  i = 0;
10247         }
10248         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10249     }
10250     return buf;
10251 }
10252
10253 int
10254 CountPlayers (char *p)
10255 {
10256     int n = 0;
10257     while(p = strchr(p, '\n')) p++, n++; // count participants
10258     return n;
10259 }
10260
10261 FILE *
10262 WriteTourneyFile (char *results, FILE *f)
10263 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10264     if(f == NULL) f = fopen(appData.tourneyFile, "w");
10265     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10266         // create a file with tournament description
10267         fprintf(f, "-participants {%s}\n", appData.participants);
10268         fprintf(f, "-seedBase %d\n", appData.seedBase);
10269         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10270         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10271         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10272         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10273         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10274         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10275         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10276         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10277         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10278         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10279         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10280         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10281         fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
10282         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10283         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10284         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10285         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10286         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10287         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10288         fprintf(f, "-smpCores %d\n", appData.smpCores);
10289         if(searchTime > 0)
10290                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10291         else {
10292                 fprintf(f, "-mps %d\n", appData.movesPerSession);
10293                 fprintf(f, "-tc %s\n", appData.timeControl);
10294                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10295         }
10296         fprintf(f, "-results \"%s\"\n", results);
10297     }
10298     return f;
10299 }
10300
10301 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10302
10303 void
10304 Substitute (char *participants, int expunge)
10305 {
10306     int i, changed, changes=0, nPlayers=0;
10307     char *p, *q, *r, buf[MSG_SIZ];
10308     if(participants == NULL) return;
10309     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10310     r = p = participants; q = appData.participants;
10311     while(*p && *p == *q) {
10312         if(*p == '\n') r = p+1, nPlayers++;
10313         p++; q++;
10314     }
10315     if(*p) { // difference
10316         while(*p && *p++ != '\n');
10317         while(*q && *q++ != '\n');
10318       changed = nPlayers;
10319         changes = 1 + (strcmp(p, q) != 0);
10320     }
10321     if(changes == 1) { // a single engine mnemonic was changed
10322         q = r; while(*q) nPlayers += (*q++ == '\n');
10323         p = buf; while(*r && (*p = *r++) != '\n') p++;
10324         *p = NULLCHAR;
10325         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10326         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10327         if(mnemonic[i]) { // The substitute is valid
10328             FILE *f;
10329             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10330                 flock(fileno(f), LOCK_EX);
10331                 ParseArgsFromFile(f);
10332                 fseek(f, 0, SEEK_SET);
10333                 FREE(appData.participants); appData.participants = participants;
10334                 if(expunge) { // erase results of replaced engine
10335                     int len = strlen(appData.results), w, b, dummy;
10336                     for(i=0; i<len; i++) {
10337                         Pairing(i, nPlayers, &w, &b, &dummy);
10338                         if((w == changed || b == changed) && appData.results[i] == '*') {
10339                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10340                             fclose(f);
10341                             return;
10342                         }
10343                     }
10344                     for(i=0; i<len; i++) {
10345                         Pairing(i, nPlayers, &w, &b, &dummy);
10346                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10347                     }
10348                 }
10349                 WriteTourneyFile(appData.results, f);
10350                 fclose(f); // release lock
10351                 return;
10352             }
10353         } else DisplayError(_("No engine with the name you gave is installed"), 0);
10354     }
10355     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10356     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
10357     free(participants);
10358     return;
10359 }
10360
10361 int
10362 CheckPlayers (char *participants)
10363 {
10364         int i;
10365         char buf[MSG_SIZ], *p;
10366         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10367         while(p = strchr(participants, '\n')) {
10368             *p = NULLCHAR;
10369             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10370             if(!mnemonic[i]) {
10371                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10372                 *p = '\n';
10373                 DisplayError(buf, 0);
10374                 return 1;
10375             }
10376             *p = '\n';
10377             participants = p + 1;
10378         }
10379         return 0;
10380 }
10381
10382 int
10383 CreateTourney (char *name)
10384 {
10385         FILE *f;
10386         if(matchMode && strcmp(name, appData.tourneyFile)) {
10387              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10388         }
10389         if(name[0] == NULLCHAR) {
10390             if(appData.participants[0])
10391                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10392             return 0;
10393         }
10394         f = fopen(name, "r");
10395         if(f) { // file exists
10396             ASSIGN(appData.tourneyFile, name);
10397             ParseArgsFromFile(f); // parse it
10398         } else {
10399             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10400             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10401                 DisplayError(_("Not enough participants"), 0);
10402                 return 0;
10403             }
10404             if(CheckPlayers(appData.participants)) return 0;
10405             ASSIGN(appData.tourneyFile, name);
10406             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10407             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10408         }
10409         fclose(f);
10410         appData.noChessProgram = FALSE;
10411         appData.clockMode = TRUE;
10412         SetGNUMode();
10413         return 1;
10414 }
10415
10416 int
10417 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10418 {
10419     char buf[MSG_SIZ], *p, *q;
10420     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10421     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10422     skip = !all && group[0]; // if group requested, we start in skip mode
10423     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10424         p = names; q = buf; header = 0;
10425         while(*p && *p != '\n') *q++ = *p++;
10426         *q = 0;
10427         if(*p == '\n') p++;
10428         if(buf[0] == '#') {
10429             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10430             depth++; // we must be entering a new group
10431             if(all) continue; // suppress printing group headers when complete list requested
10432             header = 1;
10433             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10434         }
10435         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10436         if(engineList[i]) free(engineList[i]);
10437         engineList[i] = strdup(buf);
10438         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10439         if(engineMnemonic[i]) free(engineMnemonic[i]);
10440         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10441             strcat(buf, " (");
10442             sscanf(q + 8, "%s", buf + strlen(buf));
10443             strcat(buf, ")");
10444         }
10445         engineMnemonic[i] = strdup(buf);
10446         i++;
10447     }
10448     engineList[i] = engineMnemonic[i] = NULL;
10449     return i;
10450 }
10451
10452 // following implemented as macro to avoid type limitations
10453 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10454
10455 void
10456 SwapEngines (int n)
10457 {   // swap settings for first engine and other engine (so far only some selected options)
10458     int h;
10459     char *p;
10460     if(n == 0) return;
10461     SWAP(directory, p)
10462     SWAP(chessProgram, p)
10463     SWAP(isUCI, h)
10464     SWAP(hasOwnBookUCI, h)
10465     SWAP(protocolVersion, h)
10466     SWAP(reuse, h)
10467     SWAP(scoreIsAbsolute, h)
10468     SWAP(timeOdds, h)
10469     SWAP(logo, p)
10470     SWAP(pgnName, p)
10471     SWAP(pvSAN, h)
10472     SWAP(engOptions, p)
10473     SWAP(engInitString, p)
10474     SWAP(computerString, p)
10475     SWAP(features, p)
10476     SWAP(fenOverride, p)
10477     SWAP(NPS, h)
10478     SWAP(accumulateTC, h)
10479     SWAP(host, p)
10480 }
10481
10482 int
10483 GetEngineLine (char *s, int n)
10484 {
10485     int i;
10486     char buf[MSG_SIZ];
10487     extern char *icsNames;
10488     if(!s || !*s) return 0;
10489     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
10490     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
10491     if(!mnemonic[i]) return 0;
10492     if(n == 11) return 1; // just testing if there was a match
10493     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
10494     if(n == 1) SwapEngines(n);
10495     ParseArgsFromString(buf);
10496     if(n == 1) SwapEngines(n);
10497     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
10498         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
10499         ParseArgsFromString(buf);
10500     }
10501     return 1;
10502 }
10503
10504 int
10505 SetPlayer (int player, char *p)
10506 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
10507     int i;
10508     char buf[MSG_SIZ], *engineName;
10509     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10510     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10511     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10512     if(mnemonic[i]) {
10513         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10514         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10515         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10516         ParseArgsFromString(buf);
10517     } else { // no engine with this nickname is installed!
10518         snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
10519         ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
10520         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10521         ModeHighlight();
10522         DisplayError(buf, 0);
10523         return 0;
10524     }
10525     free(engineName);
10526     return i;
10527 }
10528
10529 char *recentEngines;
10530
10531 void
10532 RecentEngineEvent (int nr)
10533 {
10534     int n;
10535 //    SwapEngines(1); // bump first to second
10536 //    ReplaceEngine(&second, 1); // and load it there
10537     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10538     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10539     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10540         ReplaceEngine(&first, 0);
10541         FloatToFront(&appData.recentEngineList, command[n]);
10542     }
10543 }
10544
10545 int
10546 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10547 {   // determine players from game number
10548     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10549
10550     if(appData.tourneyType == 0) {
10551         roundsPerCycle = (nPlayers - 1) | 1;
10552         pairingsPerRound = nPlayers / 2;
10553     } else if(appData.tourneyType > 0) {
10554         roundsPerCycle = nPlayers - appData.tourneyType;
10555         pairingsPerRound = appData.tourneyType;
10556     }
10557     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10558     gamesPerCycle = gamesPerRound * roundsPerCycle;
10559     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10560     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10561     curRound = nr / gamesPerRound; nr %= gamesPerRound;
10562     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10563     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10564     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10565
10566     if(appData.cycleSync) *syncInterval = gamesPerCycle;
10567     if(appData.roundSync) *syncInterval = gamesPerRound;
10568
10569     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10570
10571     if(appData.tourneyType == 0) {
10572         if(curPairing == (nPlayers-1)/2 ) {
10573             *whitePlayer = curRound;
10574             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10575         } else {
10576             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10577             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10578             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10579             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10580         }
10581     } else if(appData.tourneyType > 1) {
10582         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10583         *whitePlayer = curRound + appData.tourneyType;
10584     } else if(appData.tourneyType > 0) {
10585         *whitePlayer = curPairing;
10586         *blackPlayer = curRound + appData.tourneyType;
10587     }
10588
10589     // take care of white/black alternation per round.
10590     // For cycles and games this is already taken care of by default, derived from matchGame!
10591     return curRound & 1;
10592 }
10593
10594 int
10595 NextTourneyGame (int nr, int *swapColors)
10596 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10597     char *p, *q;
10598     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
10599     FILE *tf;
10600     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10601     tf = fopen(appData.tourneyFile, "r");
10602     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10603     ParseArgsFromFile(tf); fclose(tf);
10604     InitTimeControls(); // TC might be altered from tourney file
10605
10606     nPlayers = CountPlayers(appData.participants); // count participants
10607     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10608     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10609
10610     if(syncInterval) {
10611         p = q = appData.results;
10612         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10613         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10614             DisplayMessage(_("Waiting for other game(s)"),"");
10615             waitingForGame = TRUE;
10616             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10617             return 0;
10618         }
10619         waitingForGame = FALSE;
10620     }
10621
10622     if(appData.tourneyType < 0) {
10623         if(nr>=0 && !pairingReceived) {
10624             char buf[1<<16];
10625             if(pairing.pr == NoProc) {
10626                 if(!appData.pairingEngine[0]) {
10627                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10628                     return 0;
10629                 }
10630                 StartChessProgram(&pairing); // starts the pairing engine
10631             }
10632             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10633             SendToProgram(buf, &pairing);
10634             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10635             SendToProgram(buf, &pairing);
10636             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10637         }
10638         pairingReceived = 0;                              // ... so we continue here
10639         *swapColors = 0;
10640         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10641         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10642         matchGame = 1; roundNr = nr / syncInterval + 1;
10643     }
10644
10645     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10646
10647     // redefine engines, engine dir, etc.
10648     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10649     if(first.pr == NoProc) {
10650       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
10651       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10652     }
10653     if(second.pr == NoProc) {
10654       SwapEngines(1);
10655       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
10656       SwapEngines(1);         // and make that valid for second engine by swapping
10657       InitEngine(&second, 1);
10658     }
10659     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10660     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10661     return OK;
10662 }
10663
10664 void
10665 NextMatchGame ()
10666 {   // performs game initialization that does not invoke engines, and then tries to start the game
10667     int res, firstWhite, swapColors = 0;
10668     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10669     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
10670         char buf[MSG_SIZ];
10671         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
10672         if(strcmp(buf, currentDebugFile)) { // name has changed
10673             FILE *f = fopen(buf, "w");
10674             if(f) { // if opening the new file failed, just keep using the old one
10675                 ASSIGN(currentDebugFile, buf);
10676                 fclose(debugFP);
10677                 debugFP = f;
10678             }
10679             if(appData.serverFileName) {
10680                 if(serverFP) fclose(serverFP);
10681                 serverFP = fopen(appData.serverFileName, "w");
10682                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
10683                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
10684             }
10685         }
10686     }
10687     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10688     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10689     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
10690     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10691     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10692     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10693     Reset(FALSE, first.pr != NoProc);
10694     res = LoadGameOrPosition(matchGame); // setup game
10695     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10696     if(!res) return; // abort when bad game/pos file
10697     TwoMachinesEvent();
10698 }
10699
10700 void
10701 UserAdjudicationEvent (int result)
10702 {
10703     ChessMove gameResult = GameIsDrawn;
10704
10705     if( result > 0 ) {
10706         gameResult = WhiteWins;
10707     }
10708     else if( result < 0 ) {
10709         gameResult = BlackWins;
10710     }
10711
10712     if( gameMode == TwoMachinesPlay ) {
10713         GameEnds( gameResult, "User adjudication", GE_XBOARD );
10714     }
10715 }
10716
10717
10718 // [HGM] save: calculate checksum of game to make games easily identifiable
10719 int
10720 StringCheckSum (char *s)
10721 {
10722         int i = 0;
10723         if(s==NULL) return 0;
10724         while(*s) i = i*259 + *s++;
10725         return i;
10726 }
10727
10728 int
10729 GameCheckSum ()
10730 {
10731         int i, sum=0;
10732         for(i=backwardMostMove; i<forwardMostMove; i++) {
10733                 sum += pvInfoList[i].depth;
10734                 sum += StringCheckSum(parseList[i]);
10735                 sum += StringCheckSum(commentList[i]);
10736                 sum *= 261;
10737         }
10738         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10739         return sum + StringCheckSum(commentList[i]);
10740 } // end of save patch
10741
10742 void
10743 GameEnds (ChessMove result, char *resultDetails, int whosays)
10744 {
10745     GameMode nextGameMode;
10746     int isIcsGame;
10747     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10748
10749     if(endingGame) return; /* [HGM] crash: forbid recursion */
10750     endingGame = 1;
10751     if(twoBoards) { // [HGM] dual: switch back to one board
10752         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10753         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10754     }
10755     if (appData.debugMode) {
10756       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10757               result, resultDetails ? resultDetails : "(null)", whosays);
10758     }
10759
10760     fromX = fromY = -1; // [HGM] abort any move the user is entering.
10761
10762     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
10763
10764     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10765         /* If we are playing on ICS, the server decides when the
10766            game is over, but the engine can offer to draw, claim
10767            a draw, or resign.
10768          */
10769 #if ZIPPY
10770         if (appData.zippyPlay && first.initDone) {
10771             if (result == GameIsDrawn) {
10772                 /* In case draw still needs to be claimed */
10773                 SendToICS(ics_prefix);
10774                 SendToICS("draw\n");
10775             } else if (StrCaseStr(resultDetails, "resign")) {
10776                 SendToICS(ics_prefix);
10777                 SendToICS("resign\n");
10778             }
10779         }
10780 #endif
10781         endingGame = 0; /* [HGM] crash */
10782         return;
10783     }
10784
10785     /* If we're loading the game from a file, stop */
10786     if (whosays == GE_FILE) {
10787       (void) StopLoadGameTimer();
10788       gameFileFP = NULL;
10789     }
10790
10791     /* Cancel draw offers */
10792     first.offeredDraw = second.offeredDraw = 0;
10793
10794     /* If this is an ICS game, only ICS can really say it's done;
10795        if not, anyone can. */
10796     isIcsGame = (gameMode == IcsPlayingWhite ||
10797                  gameMode == IcsPlayingBlack ||
10798                  gameMode == IcsObserving    ||
10799                  gameMode == IcsExamining);
10800
10801     if (!isIcsGame || whosays == GE_ICS) {
10802         /* OK -- not an ICS game, or ICS said it was done */
10803         StopClocks();
10804         if (!isIcsGame && !appData.noChessProgram)
10805           SetUserThinkingEnables();
10806
10807         /* [HGM] if a machine claims the game end we verify this claim */
10808         if(gameMode == TwoMachinesPlay && appData.testClaims) {
10809             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10810                 char claimer;
10811                 ChessMove trueResult = (ChessMove) -1;
10812
10813                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
10814                                             first.twoMachinesColor[0] :
10815                                             second.twoMachinesColor[0] ;
10816
10817                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10818                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10819                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10820                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10821                 } else
10822                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10823                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10824                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10825                 } else
10826                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10827                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10828                 }
10829
10830                 // now verify win claims, but not in drop games, as we don't understand those yet
10831                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10832                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10833                     (result == WhiteWins && claimer == 'w' ||
10834                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
10835                       if (appData.debugMode) {
10836                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
10837                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10838                       }
10839                       if(result != trueResult) {
10840                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10841                               result = claimer == 'w' ? BlackWins : WhiteWins;
10842                               resultDetails = buf;
10843                       }
10844                 } else
10845                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10846                     && (forwardMostMove <= backwardMostMove ||
10847                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10848                         (claimer=='b')==(forwardMostMove&1))
10849                                                                                   ) {
10850                       /* [HGM] verify: draws that were not flagged are false claims */
10851                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10852                       result = claimer == 'w' ? BlackWins : WhiteWins;
10853                       resultDetails = buf;
10854                 }
10855                 /* (Claiming a loss is accepted no questions asked!) */
10856             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
10857                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
10858                 result = GameUnfinished;
10859                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
10860             }
10861             /* [HGM] bare: don't allow bare King to win */
10862             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10863                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10864                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10865                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10866                && result != GameIsDrawn)
10867             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10868                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10869                         int p = (signed char)boards[forwardMostMove][i][j] - color;
10870                         if(p >= 0 && p <= (int)WhiteKing) k++;
10871                 }
10872                 if (appData.debugMode) {
10873                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10874                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10875                 }
10876                 if(k <= 1) {
10877                         result = GameIsDrawn;
10878                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10879                         resultDetails = buf;
10880                 }
10881             }
10882         }
10883
10884
10885         if(serverMoves != NULL && !loadFlag) { char c = '=';
10886             if(result==WhiteWins) c = '+';
10887             if(result==BlackWins) c = '-';
10888             if(resultDetails != NULL)
10889                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
10890         }
10891         if (resultDetails != NULL) {
10892             gameInfo.result = result;
10893             gameInfo.resultDetails = StrSave(resultDetails);
10894
10895             /* display last move only if game was not loaded from file */
10896             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10897                 DisplayMove(currentMove - 1);
10898
10899             if (forwardMostMove != 0) {
10900                 if (gameMode != PlayFromGameFile && gameMode != EditGame
10901                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10902                                                                 ) {
10903                     if (*appData.saveGameFile != NULLCHAR) {
10904                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
10905                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
10906                         else
10907                         SaveGameToFile(appData.saveGameFile, TRUE);
10908                     } else if (appData.autoSaveGames) {
10909                         if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
10910                     }
10911                     if (*appData.savePositionFile != NULLCHAR) {
10912                         SavePositionToFile(appData.savePositionFile);
10913                     }
10914                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
10915                 }
10916             }
10917
10918             /* Tell program how game ended in case it is learning */
10919             /* [HGM] Moved this to after saving the PGN, just in case */
10920             /* engine died and we got here through time loss. In that */
10921             /* case we will get a fatal error writing the pipe, which */
10922             /* would otherwise lose us the PGN.                       */
10923             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
10924             /* output during GameEnds should never be fatal anymore   */
10925             if (gameMode == MachinePlaysWhite ||
10926                 gameMode == MachinePlaysBlack ||
10927                 gameMode == TwoMachinesPlay ||
10928                 gameMode == IcsPlayingWhite ||
10929                 gameMode == IcsPlayingBlack ||
10930                 gameMode == BeginningOfGame) {
10931                 char buf[MSG_SIZ];
10932                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10933                         resultDetails);
10934                 if (first.pr != NoProc) {
10935                     SendToProgram(buf, &first);
10936                 }
10937                 if (second.pr != NoProc &&
10938                     gameMode == TwoMachinesPlay) {
10939                     SendToProgram(buf, &second);
10940                 }
10941             }
10942         }
10943
10944         if (appData.icsActive) {
10945             if (appData.quietPlay &&
10946                 (gameMode == IcsPlayingWhite ||
10947                  gameMode == IcsPlayingBlack)) {
10948                 SendToICS(ics_prefix);
10949                 SendToICS("set shout 1\n");
10950             }
10951             nextGameMode = IcsIdle;
10952             ics_user_moved = FALSE;
10953             /* clean up premove.  It's ugly when the game has ended and the
10954              * premove highlights are still on the board.
10955              */
10956             if (gotPremove) {
10957               gotPremove = FALSE;
10958               ClearPremoveHighlights();
10959               DrawPosition(FALSE, boards[currentMove]);
10960             }
10961             if (whosays == GE_ICS) {
10962                 switch (result) {
10963                 case WhiteWins:
10964                     if (gameMode == IcsPlayingWhite)
10965                         PlayIcsWinSound();
10966                     else if(gameMode == IcsPlayingBlack)
10967                         PlayIcsLossSound();
10968                     break;
10969                 case BlackWins:
10970                     if (gameMode == IcsPlayingBlack)
10971                         PlayIcsWinSound();
10972                     else if(gameMode == IcsPlayingWhite)
10973                         PlayIcsLossSound();
10974                     break;
10975                 case GameIsDrawn:
10976                     PlayIcsDrawSound();
10977                     break;
10978                 default:
10979                     PlayIcsUnfinishedSound();
10980                 }
10981             }
10982             if(appData.quitNext) { ExitEvent(0); return; }
10983         } else if (gameMode == EditGame ||
10984                    gameMode == PlayFromGameFile ||
10985                    gameMode == AnalyzeMode ||
10986                    gameMode == AnalyzeFile) {
10987             nextGameMode = gameMode;
10988         } else {
10989             nextGameMode = EndOfGame;
10990         }
10991         pausing = FALSE;
10992         ModeHighlight();
10993     } else {
10994         nextGameMode = gameMode;
10995     }
10996
10997     if (appData.noChessProgram) {
10998         gameMode = nextGameMode;
10999         ModeHighlight();
11000         endingGame = 0; /* [HGM] crash */
11001         return;
11002     }
11003
11004     if (first.reuse) {
11005         /* Put first chess program into idle state */
11006         if (first.pr != NoProc &&
11007             (gameMode == MachinePlaysWhite ||
11008              gameMode == MachinePlaysBlack ||
11009              gameMode == TwoMachinesPlay ||
11010              gameMode == IcsPlayingWhite ||
11011              gameMode == IcsPlayingBlack ||
11012              gameMode == BeginningOfGame)) {
11013             SendToProgram("force\n", &first);
11014             if (first.usePing) {
11015               char buf[MSG_SIZ];
11016               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11017               SendToProgram(buf, &first);
11018             }
11019         }
11020     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11021         /* Kill off first chess program */
11022         if (first.isr != NULL)
11023           RemoveInputSource(first.isr);
11024         first.isr = NULL;
11025
11026         if (first.pr != NoProc) {
11027             ExitAnalyzeMode();
11028             DoSleep( appData.delayBeforeQuit );
11029             SendToProgram("quit\n", &first);
11030             DoSleep( appData.delayAfterQuit );
11031             DestroyChildProcess(first.pr, first.useSigterm);
11032             first.reload = TRUE;
11033         }
11034         first.pr = NoProc;
11035     }
11036     if (second.reuse) {
11037         /* Put second chess program into idle state */
11038         if (second.pr != NoProc &&
11039             gameMode == TwoMachinesPlay) {
11040             SendToProgram("force\n", &second);
11041             if (second.usePing) {
11042               char buf[MSG_SIZ];
11043               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11044               SendToProgram(buf, &second);
11045             }
11046         }
11047     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11048         /* Kill off second chess program */
11049         if (second.isr != NULL)
11050           RemoveInputSource(second.isr);
11051         second.isr = NULL;
11052
11053         if (second.pr != NoProc) {
11054             DoSleep( appData.delayBeforeQuit );
11055             SendToProgram("quit\n", &second);
11056             DoSleep( appData.delayAfterQuit );
11057             DestroyChildProcess(second.pr, second.useSigterm);
11058             second.reload = TRUE;
11059         }
11060         second.pr = NoProc;
11061     }
11062
11063     if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11064         char resChar = '=';
11065         switch (result) {
11066         case WhiteWins:
11067           resChar = '+';
11068           if (first.twoMachinesColor[0] == 'w') {
11069             first.matchWins++;
11070           } else {
11071             second.matchWins++;
11072           }
11073           break;
11074         case BlackWins:
11075           resChar = '-';
11076           if (first.twoMachinesColor[0] == 'b') {
11077             first.matchWins++;
11078           } else {
11079             second.matchWins++;
11080           }
11081           break;
11082         case GameUnfinished:
11083           resChar = ' ';
11084         default:
11085           break;
11086         }
11087
11088         if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11089         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11090             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11091             ReserveGame(nextGame, resChar); // sets nextGame
11092             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11093             else ranking = strdup("busy"); //suppress popup when aborted but not finished
11094         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11095
11096         if (nextGame <= appData.matchGames && !abortMatch) {
11097             gameMode = nextGameMode;
11098             matchGame = nextGame; // this will be overruled in tourney mode!
11099             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11100             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11101             endingGame = 0; /* [HGM] crash */
11102             return;
11103         } else {
11104             gameMode = nextGameMode;
11105             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11106                      first.tidy, second.tidy,
11107                      first.matchWins, second.matchWins,
11108                      appData.matchGames - (first.matchWins + second.matchWins));
11109             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11110             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11111             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11112             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11113                 first.twoMachinesColor = "black\n";
11114                 second.twoMachinesColor = "white\n";
11115             } else {
11116                 first.twoMachinesColor = "white\n";
11117                 second.twoMachinesColor = "black\n";
11118             }
11119         }
11120     }
11121     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11122         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11123       ExitAnalyzeMode();
11124     gameMode = nextGameMode;
11125     ModeHighlight();
11126     endingGame = 0;  /* [HGM] crash */
11127     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11128         if(matchMode == TRUE) { // match through command line: exit with or without popup
11129             if(ranking) {
11130                 ToNrEvent(forwardMostMove);
11131                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11132                 else ExitEvent(0);
11133             } else DisplayFatalError(buf, 0, 0);
11134         } else { // match through menu; just stop, with or without popup
11135             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11136             ModeHighlight();
11137             if(ranking){
11138                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11139             } else DisplayNote(buf);
11140       }
11141       if(ranking) free(ranking);
11142     }
11143 }
11144
11145 /* Assumes program was just initialized (initString sent).
11146    Leaves program in force mode. */
11147 void
11148 FeedMovesToProgram (ChessProgramState *cps, int upto)
11149 {
11150     int i;
11151
11152     if (appData.debugMode)
11153       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11154               startedFromSetupPosition ? "position and " : "",
11155               backwardMostMove, upto, cps->which);
11156     if(currentlyInitializedVariant != gameInfo.variant) {
11157       char buf[MSG_SIZ];
11158         // [HGM] variantswitch: make engine aware of new variant
11159         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
11160                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11161         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11162         SendToProgram(buf, cps);
11163         currentlyInitializedVariant = gameInfo.variant;
11164     }
11165     SendToProgram("force\n", cps);
11166     if (startedFromSetupPosition) {
11167         SendBoard(cps, backwardMostMove);
11168     if (appData.debugMode) {
11169         fprintf(debugFP, "feedMoves\n");
11170     }
11171     }
11172     for (i = backwardMostMove; i < upto; i++) {
11173         SendMoveToProgram(i, cps);
11174     }
11175 }
11176
11177
11178 int
11179 ResurrectChessProgram ()
11180 {
11181      /* The chess program may have exited.
11182         If so, restart it and feed it all the moves made so far. */
11183     static int doInit = 0;
11184
11185     if (appData.noChessProgram) return 1;
11186
11187     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11188         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11189         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11190         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11191     } else {
11192         if (first.pr != NoProc) return 1;
11193         StartChessProgram(&first);
11194     }
11195     InitChessProgram(&first, FALSE);
11196     FeedMovesToProgram(&first, currentMove);
11197
11198     if (!first.sendTime) {
11199         /* can't tell gnuchess what its clock should read,
11200            so we bow to its notion. */
11201         ResetClocks();
11202         timeRemaining[0][currentMove] = whiteTimeRemaining;
11203         timeRemaining[1][currentMove] = blackTimeRemaining;
11204     }
11205
11206     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11207                 appData.icsEngineAnalyze) && first.analysisSupport) {
11208       SendToProgram("analyze\n", &first);
11209       first.analyzing = TRUE;
11210     }
11211     return 1;
11212 }
11213
11214 /*
11215  * Button procedures
11216  */
11217 void
11218 Reset (int redraw, int init)
11219 {
11220     int i;
11221
11222     if (appData.debugMode) {
11223         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11224                 redraw, init, gameMode);
11225     }
11226     CleanupTail(); // [HGM] vari: delete any stored variations
11227     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11228     pausing = pauseExamInvalid = FALSE;
11229     startedFromSetupPosition = blackPlaysFirst = FALSE;
11230     firstMove = TRUE;
11231     whiteFlag = blackFlag = FALSE;
11232     userOfferedDraw = FALSE;
11233     hintRequested = bookRequested = FALSE;
11234     first.maybeThinking = FALSE;
11235     second.maybeThinking = FALSE;
11236     first.bookSuspend = FALSE; // [HGM] book
11237     second.bookSuspend = FALSE;
11238     thinkOutput[0] = NULLCHAR;
11239     lastHint[0] = NULLCHAR;
11240     ClearGameInfo(&gameInfo);
11241     gameInfo.variant = StringToVariant(appData.variant);
11242     if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) gameInfo.variant = VariantUnknown;
11243     ics_user_moved = ics_clock_paused = FALSE;
11244     ics_getting_history = H_FALSE;
11245     ics_gamenum = -1;
11246     white_holding[0] = black_holding[0] = NULLCHAR;
11247     ClearProgramStats();
11248     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11249
11250     ResetFrontEnd();
11251     ClearHighlights();
11252     flipView = appData.flipView;
11253     ClearPremoveHighlights();
11254     gotPremove = FALSE;
11255     alarmSounded = FALSE;
11256
11257     GameEnds(EndOfFile, NULL, GE_PLAYER);
11258     if(appData.serverMovesName != NULL) {
11259         /* [HGM] prepare to make moves file for broadcasting */
11260         clock_t t = clock();
11261         if(serverMoves != NULL) fclose(serverMoves);
11262         serverMoves = fopen(appData.serverMovesName, "r");
11263         if(serverMoves != NULL) {
11264             fclose(serverMoves);
11265             /* delay 15 sec before overwriting, so all clients can see end */
11266             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11267         }
11268         serverMoves = fopen(appData.serverMovesName, "w");
11269     }
11270
11271     ExitAnalyzeMode();
11272     gameMode = BeginningOfGame;
11273     ModeHighlight();
11274     if(appData.icsActive) gameInfo.variant = VariantNormal;
11275     currentMove = forwardMostMove = backwardMostMove = 0;
11276     MarkTargetSquares(1);
11277     InitPosition(redraw);
11278     for (i = 0; i < MAX_MOVES; i++) {
11279         if (commentList[i] != NULL) {
11280             free(commentList[i]);
11281             commentList[i] = NULL;
11282         }
11283     }
11284     ResetClocks();
11285     timeRemaining[0][0] = whiteTimeRemaining;
11286     timeRemaining[1][0] = blackTimeRemaining;
11287
11288     if (first.pr == NoProc) {
11289         StartChessProgram(&first);
11290     }
11291     if (init) {
11292             InitChessProgram(&first, startedFromSetupPosition);
11293     }
11294     DisplayTitle("");
11295     DisplayMessage("", "");
11296     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11297     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11298     ClearMap();        // [HGM] exclude: invalidate map
11299 }
11300
11301 void
11302 AutoPlayGameLoop ()
11303 {
11304     for (;;) {
11305         if (!AutoPlayOneMove())
11306           return;
11307         if (matchMode || appData.timeDelay == 0)
11308           continue;
11309         if (appData.timeDelay < 0)
11310           return;
11311         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11312         break;
11313     }
11314 }
11315
11316 void
11317 AnalyzeNextGame()
11318 {
11319     ReloadGame(1); // next game
11320 }
11321
11322 int
11323 AutoPlayOneMove ()
11324 {
11325     int fromX, fromY, toX, toY;
11326
11327     if (appData.debugMode) {
11328       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11329     }
11330
11331     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11332       return FALSE;
11333
11334     if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
11335       pvInfoList[currentMove].depth = programStats.depth;
11336       pvInfoList[currentMove].score = programStats.score;
11337       pvInfoList[currentMove].time  = 0;
11338       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11339       else { // append analysis of final position as comment
11340         char buf[MSG_SIZ];
11341         snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
11342         AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
11343       }
11344       programStats.depth = 0;
11345     }
11346
11347     if (currentMove >= forwardMostMove) {
11348       if(gameMode == AnalyzeFile) {
11349           if(appData.loadGameIndex == -1) {
11350             GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
11351           ScheduleDelayedEvent(AnalyzeNextGame, 10);
11352           } else {
11353           ExitAnalyzeMode(); SendToProgram("force\n", &first);
11354         }
11355       }
11356 //      gameMode = EndOfGame;
11357 //      ModeHighlight();
11358
11359       /* [AS] Clear current move marker at the end of a game */
11360       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11361
11362       return FALSE;
11363     }
11364
11365     toX = moveList[currentMove][2] - AAA;
11366     toY = moveList[currentMove][3] - ONE;
11367
11368     if (moveList[currentMove][1] == '@') {
11369         if (appData.highlightLastMove) {
11370             SetHighlights(-1, -1, toX, toY);
11371         }
11372     } else {
11373         fromX = moveList[currentMove][0] - AAA;
11374         fromY = moveList[currentMove][1] - ONE;
11375
11376         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11377
11378         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11379
11380         if (appData.highlightLastMove) {
11381             SetHighlights(fromX, fromY, toX, toY);
11382         }
11383     }
11384     DisplayMove(currentMove);
11385     SendMoveToProgram(currentMove++, &first);
11386     DisplayBothClocks();
11387     DrawPosition(FALSE, boards[currentMove]);
11388     // [HGM] PV info: always display, routine tests if empty
11389     DisplayComment(currentMove - 1, commentList[currentMove]);
11390     return TRUE;
11391 }
11392
11393
11394 int
11395 LoadGameOneMove (ChessMove readAhead)
11396 {
11397     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11398     char promoChar = NULLCHAR;
11399     ChessMove moveType;
11400     char move[MSG_SIZ];
11401     char *p, *q;
11402
11403     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11404         gameMode != AnalyzeMode && gameMode != Training) {
11405         gameFileFP = NULL;
11406         return FALSE;
11407     }
11408
11409     yyboardindex = forwardMostMove;
11410     if (readAhead != EndOfFile) {
11411       moveType = readAhead;
11412     } else {
11413       if (gameFileFP == NULL)
11414           return FALSE;
11415       moveType = (ChessMove) Myylex();
11416     }
11417
11418     done = FALSE;
11419     switch (moveType) {
11420       case Comment:
11421         if (appData.debugMode)
11422           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11423         p = yy_text;
11424
11425         /* append the comment but don't display it */
11426         AppendComment(currentMove, p, FALSE);
11427         return TRUE;
11428
11429       case WhiteCapturesEnPassant:
11430       case BlackCapturesEnPassant:
11431       case WhitePromotion:
11432       case BlackPromotion:
11433       case WhiteNonPromotion:
11434       case BlackNonPromotion:
11435       case NormalMove:
11436       case WhiteKingSideCastle:
11437       case WhiteQueenSideCastle:
11438       case BlackKingSideCastle:
11439       case BlackQueenSideCastle:
11440       case WhiteKingSideCastleWild:
11441       case WhiteQueenSideCastleWild:
11442       case BlackKingSideCastleWild:
11443       case BlackQueenSideCastleWild:
11444       /* PUSH Fabien */
11445       case WhiteHSideCastleFR:
11446       case WhiteASideCastleFR:
11447       case BlackHSideCastleFR:
11448       case BlackASideCastleFR:
11449       /* POP Fabien */
11450         if (appData.debugMode)
11451           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11452         fromX = currentMoveString[0] - AAA;
11453         fromY = currentMoveString[1] - ONE;
11454         toX = currentMoveString[2] - AAA;
11455         toY = currentMoveString[3] - ONE;
11456         promoChar = currentMoveString[4];
11457         break;
11458
11459       case WhiteDrop:
11460       case BlackDrop:
11461         if (appData.debugMode)
11462           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11463         fromX = moveType == WhiteDrop ?
11464           (int) CharToPiece(ToUpper(currentMoveString[0])) :
11465         (int) CharToPiece(ToLower(currentMoveString[0]));
11466         fromY = DROP_RANK;
11467         toX = currentMoveString[2] - AAA;
11468         toY = currentMoveString[3] - ONE;
11469         break;
11470
11471       case WhiteWins:
11472       case BlackWins:
11473       case GameIsDrawn:
11474       case GameUnfinished:
11475         if (appData.debugMode)
11476           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11477         p = strchr(yy_text, '{');
11478         if (p == NULL) p = strchr(yy_text, '(');
11479         if (p == NULL) {
11480             p = yy_text;
11481             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11482         } else {
11483             q = strchr(p, *p == '{' ? '}' : ')');
11484             if (q != NULL) *q = NULLCHAR;
11485             p++;
11486         }
11487         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11488         GameEnds(moveType, p, GE_FILE);
11489         done = TRUE;
11490         if (cmailMsgLoaded) {
11491             ClearHighlights();
11492             flipView = WhiteOnMove(currentMove);
11493             if (moveType == GameUnfinished) flipView = !flipView;
11494             if (appData.debugMode)
11495               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11496         }
11497         break;
11498
11499       case EndOfFile:
11500         if (appData.debugMode)
11501           fprintf(debugFP, "Parser hit end of file\n");
11502         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11503           case MT_NONE:
11504           case MT_CHECK:
11505             break;
11506           case MT_CHECKMATE:
11507           case MT_STAINMATE:
11508             if (WhiteOnMove(currentMove)) {
11509                 GameEnds(BlackWins, "Black mates", GE_FILE);
11510             } else {
11511                 GameEnds(WhiteWins, "White mates", GE_FILE);
11512             }
11513             break;
11514           case MT_STALEMATE:
11515             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11516             break;
11517         }
11518         done = TRUE;
11519         break;
11520
11521       case MoveNumberOne:
11522         if (lastLoadGameStart == GNUChessGame) {
11523             /* GNUChessGames have numbers, but they aren't move numbers */
11524             if (appData.debugMode)
11525               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11526                       yy_text, (int) moveType);
11527             return LoadGameOneMove(EndOfFile); /* tail recursion */
11528         }
11529         /* else fall thru */
11530
11531       case XBoardGame:
11532       case GNUChessGame:
11533       case PGNTag:
11534         /* Reached start of next game in file */
11535         if (appData.debugMode)
11536           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11537         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11538           case MT_NONE:
11539           case MT_CHECK:
11540             break;
11541           case MT_CHECKMATE:
11542           case MT_STAINMATE:
11543             if (WhiteOnMove(currentMove)) {
11544                 GameEnds(BlackWins, "Black mates", GE_FILE);
11545             } else {
11546                 GameEnds(WhiteWins, "White mates", GE_FILE);
11547             }
11548             break;
11549           case MT_STALEMATE:
11550             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11551             break;
11552         }
11553         done = TRUE;
11554         break;
11555
11556       case PositionDiagram:     /* should not happen; ignore */
11557       case ElapsedTime:         /* ignore */
11558       case NAG:                 /* ignore */
11559         if (appData.debugMode)
11560           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11561                   yy_text, (int) moveType);
11562         return LoadGameOneMove(EndOfFile); /* tail recursion */
11563
11564       case IllegalMove:
11565         if (appData.testLegality) {
11566             if (appData.debugMode)
11567               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11568             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11569                     (forwardMostMove / 2) + 1,
11570                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11571             DisplayError(move, 0);
11572             done = TRUE;
11573         } else {
11574             if (appData.debugMode)
11575               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11576                       yy_text, currentMoveString);
11577             fromX = currentMoveString[0] - AAA;
11578             fromY = currentMoveString[1] - ONE;
11579             toX = currentMoveString[2] - AAA;
11580             toY = currentMoveString[3] - ONE;
11581             promoChar = currentMoveString[4];
11582         }
11583         break;
11584
11585       case AmbiguousMove:
11586         if (appData.debugMode)
11587           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11588         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11589                 (forwardMostMove / 2) + 1,
11590                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11591         DisplayError(move, 0);
11592         done = TRUE;
11593         break;
11594
11595       default:
11596       case ImpossibleMove:
11597         if (appData.debugMode)
11598           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11599         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11600                 (forwardMostMove / 2) + 1,
11601                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11602         DisplayError(move, 0);
11603         done = TRUE;
11604         break;
11605     }
11606
11607     if (done) {
11608         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11609             DrawPosition(FALSE, boards[currentMove]);
11610             DisplayBothClocks();
11611             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11612               DisplayComment(currentMove - 1, commentList[currentMove]);
11613         }
11614         (void) StopLoadGameTimer();
11615         gameFileFP = NULL;
11616         cmailOldMove = forwardMostMove;
11617         return FALSE;
11618     } else {
11619         /* currentMoveString is set as a side-effect of yylex */
11620
11621         thinkOutput[0] = NULLCHAR;
11622         MakeMove(fromX, fromY, toX, toY, promoChar);
11623         currentMove = forwardMostMove;
11624         return TRUE;
11625     }
11626 }
11627
11628 /* Load the nth game from the given file */
11629 int
11630 LoadGameFromFile (char *filename, int n, char *title, int useList)
11631 {
11632     FILE *f;
11633     char buf[MSG_SIZ];
11634
11635     if (strcmp(filename, "-") == 0) {
11636         f = stdin;
11637         title = "stdin";
11638     } else {
11639         f = fopen(filename, "rb");
11640         if (f == NULL) {
11641           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
11642             DisplayError(buf, errno);
11643             return FALSE;
11644         }
11645     }
11646     if (fseek(f, 0, 0) == -1) {
11647         /* f is not seekable; probably a pipe */
11648         useList = FALSE;
11649     }
11650     if (useList && n == 0) {
11651         int error = GameListBuild(f);
11652         if (error) {
11653             DisplayError(_("Cannot build game list"), error);
11654         } else if (!ListEmpty(&gameList) &&
11655                    ((ListGame *) gameList.tailPred)->number > 1) {
11656             GameListPopUp(f, title);
11657             return TRUE;
11658         }
11659         GameListDestroy();
11660         n = 1;
11661     }
11662     if (n == 0) n = 1;
11663     return LoadGame(f, n, title, FALSE);
11664 }
11665
11666
11667 void
11668 MakeRegisteredMove ()
11669 {
11670     int fromX, fromY, toX, toY;
11671     char promoChar;
11672     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11673         switch (cmailMoveType[lastLoadGameNumber - 1]) {
11674           case CMAIL_MOVE:
11675           case CMAIL_DRAW:
11676             if (appData.debugMode)
11677               fprintf(debugFP, "Restoring %s for game %d\n",
11678                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11679
11680             thinkOutput[0] = NULLCHAR;
11681             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11682             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11683             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11684             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11685             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11686             promoChar = cmailMove[lastLoadGameNumber - 1][4];
11687             MakeMove(fromX, fromY, toX, toY, promoChar);
11688             ShowMove(fromX, fromY, toX, toY);
11689
11690             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11691               case MT_NONE:
11692               case MT_CHECK:
11693                 break;
11694
11695               case MT_CHECKMATE:
11696               case MT_STAINMATE:
11697                 if (WhiteOnMove(currentMove)) {
11698                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
11699                 } else {
11700                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
11701                 }
11702                 break;
11703
11704               case MT_STALEMATE:
11705                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11706                 break;
11707             }
11708
11709             break;
11710
11711           case CMAIL_RESIGN:
11712             if (WhiteOnMove(currentMove)) {
11713                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11714             } else {
11715                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11716             }
11717             break;
11718
11719           case CMAIL_ACCEPT:
11720             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11721             break;
11722
11723           default:
11724             break;
11725         }
11726     }
11727
11728     return;
11729 }
11730
11731 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11732 int
11733 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11734 {
11735     int retVal;
11736
11737     if (gameNumber > nCmailGames) {
11738         DisplayError(_("No more games in this message"), 0);
11739         return FALSE;
11740     }
11741     if (f == lastLoadGameFP) {
11742         int offset = gameNumber - lastLoadGameNumber;
11743         if (offset == 0) {
11744             cmailMsg[0] = NULLCHAR;
11745             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11746                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11747                 nCmailMovesRegistered--;
11748             }
11749             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11750             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11751                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11752             }
11753         } else {
11754             if (! RegisterMove()) return FALSE;
11755         }
11756     }
11757
11758     retVal = LoadGame(f, gameNumber, title, useList);
11759
11760     /* Make move registered during previous look at this game, if any */
11761     MakeRegisteredMove();
11762
11763     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11764         commentList[currentMove]
11765           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11766         DisplayComment(currentMove - 1, commentList[currentMove]);
11767     }
11768
11769     return retVal;
11770 }
11771
11772 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11773 int
11774 ReloadGame (int offset)
11775 {
11776     int gameNumber = lastLoadGameNumber + offset;
11777     if (lastLoadGameFP == NULL) {
11778         DisplayError(_("No game has been loaded yet"), 0);
11779         return FALSE;
11780     }
11781     if (gameNumber <= 0) {
11782         DisplayError(_("Can't back up any further"), 0);
11783         return FALSE;
11784     }
11785     if (cmailMsgLoaded) {
11786         return CmailLoadGame(lastLoadGameFP, gameNumber,
11787                              lastLoadGameTitle, lastLoadGameUseList);
11788     } else {
11789         return LoadGame(lastLoadGameFP, gameNumber,
11790                         lastLoadGameTitle, lastLoadGameUseList);
11791     }
11792 }
11793
11794 int keys[EmptySquare+1];
11795
11796 int
11797 PositionMatches (Board b1, Board b2)
11798 {
11799     int r, f, sum=0;
11800     switch(appData.searchMode) {
11801         case 1: return CompareWithRights(b1, b2);
11802         case 2:
11803             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11804                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11805             }
11806             return TRUE;
11807         case 3:
11808             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11809               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11810                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11811             }
11812             return sum==0;
11813         case 4:
11814             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11815                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11816             }
11817             return sum==0;
11818     }
11819     return TRUE;
11820 }
11821
11822 #define Q_PROMO  4
11823 #define Q_EP     3
11824 #define Q_BCASTL 2
11825 #define Q_WCASTL 1
11826
11827 int pieceList[256], quickBoard[256];
11828 ChessSquare pieceType[256] = { EmptySquare };
11829 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11830 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11831 int soughtTotal, turn;
11832 Boolean epOK, flipSearch;
11833
11834 typedef struct {
11835     unsigned char piece, to;
11836 } Move;
11837
11838 #define DSIZE (250000)
11839
11840 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11841 Move *moveDatabase = initialSpace;
11842 unsigned int movePtr, dataSize = DSIZE;
11843
11844 int
11845 MakePieceList (Board board, int *counts)
11846 {
11847     int r, f, n=Q_PROMO, total=0;
11848     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11849     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11850         int sq = f + (r<<4);
11851         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
11852             quickBoard[sq] = ++n;
11853             pieceList[n] = sq;
11854             pieceType[n] = board[r][f];
11855             counts[board[r][f]]++;
11856             if(board[r][f] == WhiteKing) pieceList[1] = n; else
11857             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
11858             total++;
11859         }
11860     }
11861     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
11862     return total;
11863 }
11864
11865 void
11866 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
11867 {
11868     int sq = fromX + (fromY<<4);
11869     int piece = quickBoard[sq];
11870     quickBoard[sq] = 0;
11871     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
11872     if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11873         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
11874         moveDatabase[movePtr++].piece = Q_WCASTL;
11875         quickBoard[sq] = piece;
11876         piece = quickBoard[from]; quickBoard[from] = 0;
11877         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11878     } else
11879     if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11880         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
11881         moveDatabase[movePtr++].piece = Q_BCASTL;
11882         quickBoard[sq] = piece;
11883         piece = quickBoard[from]; quickBoard[from] = 0;
11884         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11885     } else
11886     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
11887         quickBoard[(fromY<<4)+toX] = 0;
11888         moveDatabase[movePtr].piece = Q_EP;
11889         moveDatabase[movePtr++].to = (fromY<<4)+toX;
11890         moveDatabase[movePtr].to = sq;
11891     } else
11892     if(promoPiece != pieceType[piece]) {
11893         moveDatabase[movePtr++].piece = Q_PROMO;
11894         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
11895     }
11896     moveDatabase[movePtr].piece = piece;
11897     quickBoard[sq] = piece;
11898     movePtr++;
11899 }
11900
11901 int
11902 PackGame (Board board)
11903 {
11904     Move *newSpace = NULL;
11905     moveDatabase[movePtr].piece = 0; // terminate previous game
11906     if(movePtr > dataSize) {
11907         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
11908         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
11909         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
11910         if(newSpace) {
11911             int i;
11912             Move *p = moveDatabase, *q = newSpace;
11913             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
11914             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
11915             moveDatabase = newSpace;
11916         } else { // calloc failed, we must be out of memory. Too bad...
11917             dataSize = 0; // prevent calloc events for all subsequent games
11918             return 0;     // and signal this one isn't cached
11919         }
11920     }
11921     movePtr++;
11922     MakePieceList(board, counts);
11923     return movePtr;
11924 }
11925
11926 int
11927 QuickCompare (Board board, int *minCounts, int *maxCounts)
11928 {   // compare according to search mode
11929     int r, f;
11930     switch(appData.searchMode)
11931     {
11932       case 1: // exact position match
11933         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
11934         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11935             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11936         }
11937         break;
11938       case 2: // can have extra material on empty squares
11939         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11940             if(board[r][f] == EmptySquare) continue;
11941             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11942         }
11943         break;
11944       case 3: // material with exact Pawn structure
11945         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11946             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
11947             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11948         } // fall through to material comparison
11949       case 4: // exact material
11950         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
11951         break;
11952       case 6: // material range with given imbalance
11953         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
11954         // fall through to range comparison
11955       case 5: // material range
11956         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
11957     }
11958     return TRUE;
11959 }
11960
11961 int
11962 QuickScan (Board board, Move *move)
11963 {   // reconstruct game,and compare all positions in it
11964     int cnt=0, stretch=0, total = MakePieceList(board, counts);
11965     do {
11966         int piece = move->piece;
11967         int to = move->to, from = pieceList[piece];
11968         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
11969           if(!piece) return -1;
11970           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
11971             piece = (++move)->piece;
11972             from = pieceList[piece];
11973             counts[pieceType[piece]]--;
11974             pieceType[piece] = (ChessSquare) move->to;
11975             counts[move->to]++;
11976           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
11977             counts[pieceType[quickBoard[to]]]--;
11978             quickBoard[to] = 0; total--;
11979             move++;
11980             continue;
11981           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
11982             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
11983             from  = pieceList[piece]; // so this must be King
11984             quickBoard[from] = 0;
11985             pieceList[piece] = to;
11986             from = pieceList[(++move)->piece]; // for FRC this has to be done here
11987             quickBoard[from] = 0; // rook
11988             quickBoard[to] = piece;
11989             to = move->to; piece = move->piece;
11990             goto aftercastle;
11991           }
11992         }
11993         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
11994         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
11995         quickBoard[from] = 0;
11996       aftercastle:
11997         quickBoard[to] = piece;
11998         pieceList[piece] = to;
11999         cnt++; turn ^= 3;
12000         if(QuickCompare(soughtBoard, minSought, maxSought) ||
12001            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12002            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12003                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12004           ) {
12005             static int lastCounts[EmptySquare+1];
12006             int i;
12007             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12008             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12009         } else stretch = 0;
12010         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
12011         move++;
12012     } while(1);
12013 }
12014
12015 void
12016 InitSearch ()
12017 {
12018     int r, f;
12019     flipSearch = FALSE;
12020     CopyBoard(soughtBoard, boards[currentMove]);
12021     soughtTotal = MakePieceList(soughtBoard, maxSought);
12022     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12023     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12024     CopyBoard(reverseBoard, boards[currentMove]);
12025     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12026         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12027         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12028         reverseBoard[r][f] = piece;
12029     }
12030     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12031     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12032     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12033                  || (boards[currentMove][CASTLING][2] == NoRights ||
12034                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12035                  && (boards[currentMove][CASTLING][5] == NoRights ||
12036                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12037       ) {
12038         flipSearch = TRUE;
12039         CopyBoard(flipBoard, soughtBoard);
12040         CopyBoard(rotateBoard, reverseBoard);
12041         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12042             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
12043             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12044         }
12045     }
12046     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12047     if(appData.searchMode >= 5) {
12048         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12049         MakePieceList(soughtBoard, minSought);
12050         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12051     }
12052     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12053         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12054 }
12055
12056 GameInfo dummyInfo;
12057 static int creatingBook;
12058
12059 int
12060 GameContainsPosition (FILE *f, ListGame *lg)
12061 {
12062     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12063     int fromX, fromY, toX, toY;
12064     char promoChar;
12065     static int initDone=FALSE;
12066
12067     // weed out games based on numerical tag comparison
12068     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12069     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12070     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12071     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12072     if(!initDone) {
12073         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12074         initDone = TRUE;
12075     }
12076     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen);
12077     else CopyBoard(boards[scratch], initialPosition); // default start position
12078     if(lg->moves) {
12079         turn = btm + 1;
12080         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12081         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12082     }
12083     if(btm) plyNr++;
12084     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12085     fseek(f, lg->offset, 0);
12086     yynewfile(f);
12087     while(1) {
12088         yyboardindex = scratch;
12089         quickFlag = plyNr+1;
12090         next = Myylex();
12091         quickFlag = 0;
12092         switch(next) {
12093             case PGNTag:
12094                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12095             default:
12096                 continue;
12097
12098             case XBoardGame:
12099             case GNUChessGame:
12100                 if(plyNr) return -1; // after we have seen moves, this is for new game
12101               continue;
12102
12103             case AmbiguousMove: // we cannot reconstruct the game beyond these two
12104             case ImpossibleMove:
12105             case WhiteWins: // game ends here with these four
12106             case BlackWins:
12107             case GameIsDrawn:
12108             case GameUnfinished:
12109                 return -1;
12110
12111             case IllegalMove:
12112                 if(appData.testLegality) return -1;
12113             case WhiteCapturesEnPassant:
12114             case BlackCapturesEnPassant:
12115             case WhitePromotion:
12116             case BlackPromotion:
12117             case WhiteNonPromotion:
12118             case BlackNonPromotion:
12119             case NormalMove:
12120             case WhiteKingSideCastle:
12121             case WhiteQueenSideCastle:
12122             case BlackKingSideCastle:
12123             case BlackQueenSideCastle:
12124             case WhiteKingSideCastleWild:
12125             case WhiteQueenSideCastleWild:
12126             case BlackKingSideCastleWild:
12127             case BlackQueenSideCastleWild:
12128             case WhiteHSideCastleFR:
12129             case WhiteASideCastleFR:
12130             case BlackHSideCastleFR:
12131             case BlackASideCastleFR:
12132                 fromX = currentMoveString[0] - AAA;
12133                 fromY = currentMoveString[1] - ONE;
12134                 toX = currentMoveString[2] - AAA;
12135                 toY = currentMoveString[3] - ONE;
12136                 promoChar = currentMoveString[4];
12137                 break;
12138             case WhiteDrop:
12139             case BlackDrop:
12140                 fromX = next == WhiteDrop ?
12141                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
12142                   (int) CharToPiece(ToLower(currentMoveString[0]));
12143                 fromY = DROP_RANK;
12144                 toX = currentMoveString[2] - AAA;
12145                 toY = currentMoveString[3] - ONE;
12146                 promoChar = 0;
12147                 break;
12148         }
12149         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12150         plyNr++;
12151         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12152         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12153         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12154         if(appData.findMirror) {
12155             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12156             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12157         }
12158     }
12159 }
12160
12161 /* Load the nth game from open file f */
12162 int
12163 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12164 {
12165     ChessMove cm;
12166     char buf[MSG_SIZ];
12167     int gn = gameNumber;
12168     ListGame *lg = NULL;
12169     int numPGNTags = 0;
12170     int err, pos = -1;
12171     GameMode oldGameMode;
12172     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12173
12174     if (appData.debugMode)
12175         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12176
12177     if (gameMode == Training )
12178         SetTrainingModeOff();
12179
12180     oldGameMode = gameMode;
12181     if (gameMode != BeginningOfGame) {
12182       Reset(FALSE, TRUE);
12183     }
12184
12185     gameFileFP = f;
12186     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12187         fclose(lastLoadGameFP);
12188     }
12189
12190     if (useList) {
12191         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12192
12193         if (lg) {
12194             fseek(f, lg->offset, 0);
12195             GameListHighlight(gameNumber);
12196             pos = lg->position;
12197             gn = 1;
12198         }
12199         else {
12200             if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
12201               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12202             else
12203             DisplayError(_("Game number out of range"), 0);
12204             return FALSE;
12205         }
12206     } else {
12207         GameListDestroy();
12208         if (fseek(f, 0, 0) == -1) {
12209             if (f == lastLoadGameFP ?
12210                 gameNumber == lastLoadGameNumber + 1 :
12211                 gameNumber == 1) {
12212                 gn = 1;
12213             } else {
12214                 DisplayError(_("Can't seek on game file"), 0);
12215                 return FALSE;
12216             }
12217         }
12218     }
12219     lastLoadGameFP = f;
12220     lastLoadGameNumber = gameNumber;
12221     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12222     lastLoadGameUseList = useList;
12223
12224     yynewfile(f);
12225
12226     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12227       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12228                 lg->gameInfo.black);
12229             DisplayTitle(buf);
12230     } else if (*title != NULLCHAR) {
12231         if (gameNumber > 1) {
12232           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12233             DisplayTitle(buf);
12234         } else {
12235             DisplayTitle(title);
12236         }
12237     }
12238
12239     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12240         gameMode = PlayFromGameFile;
12241         ModeHighlight();
12242     }
12243
12244     currentMove = forwardMostMove = backwardMostMove = 0;
12245     CopyBoard(boards[0], initialPosition);
12246     StopClocks();
12247
12248     /*
12249      * Skip the first gn-1 games in the file.
12250      * Also skip over anything that precedes an identifiable
12251      * start of game marker, to avoid being confused by
12252      * garbage at the start of the file.  Currently
12253      * recognized start of game markers are the move number "1",
12254      * the pattern "gnuchess .* game", the pattern
12255      * "^[#;%] [^ ]* game file", and a PGN tag block.
12256      * A game that starts with one of the latter two patterns
12257      * will also have a move number 1, possibly
12258      * following a position diagram.
12259      * 5-4-02: Let's try being more lenient and allowing a game to
12260      * start with an unnumbered move.  Does that break anything?
12261      */
12262     cm = lastLoadGameStart = EndOfFile;
12263     while (gn > 0) {
12264         yyboardindex = forwardMostMove;
12265         cm = (ChessMove) Myylex();
12266         switch (cm) {
12267           case EndOfFile:
12268             if (cmailMsgLoaded) {
12269                 nCmailGames = CMAIL_MAX_GAMES - gn;
12270             } else {
12271                 Reset(TRUE, TRUE);
12272                 DisplayError(_("Game not found in file"), 0);
12273             }
12274             return FALSE;
12275
12276           case GNUChessGame:
12277           case XBoardGame:
12278             gn--;
12279             lastLoadGameStart = cm;
12280             break;
12281
12282           case MoveNumberOne:
12283             switch (lastLoadGameStart) {
12284               case GNUChessGame:
12285               case XBoardGame:
12286               case PGNTag:
12287                 break;
12288               case MoveNumberOne:
12289               case EndOfFile:
12290                 gn--;           /* count this game */
12291                 lastLoadGameStart = cm;
12292                 break;
12293               default:
12294                 /* impossible */
12295                 break;
12296             }
12297             break;
12298
12299           case PGNTag:
12300             switch (lastLoadGameStart) {
12301               case GNUChessGame:
12302               case PGNTag:
12303               case MoveNumberOne:
12304               case EndOfFile:
12305                 gn--;           /* count this game */
12306                 lastLoadGameStart = cm;
12307                 break;
12308               case XBoardGame:
12309                 lastLoadGameStart = cm; /* game counted already */
12310                 break;
12311               default:
12312                 /* impossible */
12313                 break;
12314             }
12315             if (gn > 0) {
12316                 do {
12317                     yyboardindex = forwardMostMove;
12318                     cm = (ChessMove) Myylex();
12319                 } while (cm == PGNTag || cm == Comment);
12320             }
12321             break;
12322
12323           case WhiteWins:
12324           case BlackWins:
12325           case GameIsDrawn:
12326             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
12327                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
12328                     != CMAIL_OLD_RESULT) {
12329                     nCmailResults ++ ;
12330                     cmailResult[  CMAIL_MAX_GAMES
12331                                 - gn - 1] = CMAIL_OLD_RESULT;
12332                 }
12333             }
12334             break;
12335
12336           case NormalMove:
12337             /* Only a NormalMove can be at the start of a game
12338              * without a position diagram. */
12339             if (lastLoadGameStart == EndOfFile ) {
12340               gn--;
12341               lastLoadGameStart = MoveNumberOne;
12342             }
12343             break;
12344
12345           default:
12346             break;
12347         }
12348     }
12349
12350     if (appData.debugMode)
12351       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
12352
12353     if (cm == XBoardGame) {
12354         /* Skip any header junk before position diagram and/or move 1 */
12355         for (;;) {
12356             yyboardindex = forwardMostMove;
12357             cm = (ChessMove) Myylex();
12358
12359             if (cm == EndOfFile ||
12360                 cm == GNUChessGame || cm == XBoardGame) {
12361                 /* Empty game; pretend end-of-file and handle later */
12362                 cm = EndOfFile;
12363                 break;
12364             }
12365
12366             if (cm == MoveNumberOne || cm == PositionDiagram ||
12367                 cm == PGNTag || cm == Comment)
12368               break;
12369         }
12370     } else if (cm == GNUChessGame) {
12371         if (gameInfo.event != NULL) {
12372             free(gameInfo.event);
12373         }
12374         gameInfo.event = StrSave(yy_text);
12375     }
12376
12377     startedFromSetupPosition = FALSE;
12378     while (cm == PGNTag) {
12379         if (appData.debugMode)
12380           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
12381         err = ParsePGNTag(yy_text, &gameInfo);
12382         if (!err) numPGNTags++;
12383
12384         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
12385         if(gameInfo.variant != oldVariant) {
12386             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
12387             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
12388             InitPosition(TRUE);
12389             oldVariant = gameInfo.variant;
12390             if (appData.debugMode)
12391               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
12392         }
12393
12394
12395         if (gameInfo.fen != NULL) {
12396           Board initial_position;
12397           startedFromSetupPosition = TRUE;
12398           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
12399             Reset(TRUE, TRUE);
12400             DisplayError(_("Bad FEN position in file"), 0);
12401             return FALSE;
12402           }
12403           CopyBoard(boards[0], initial_position);
12404           if (blackPlaysFirst) {
12405             currentMove = forwardMostMove = backwardMostMove = 1;
12406             CopyBoard(boards[1], initial_position);
12407             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12408             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12409             timeRemaining[0][1] = whiteTimeRemaining;
12410             timeRemaining[1][1] = blackTimeRemaining;
12411             if (commentList[0] != NULL) {
12412               commentList[1] = commentList[0];
12413               commentList[0] = NULL;
12414             }
12415           } else {
12416             currentMove = forwardMostMove = backwardMostMove = 0;
12417           }
12418           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12419           {   int i;
12420               initialRulePlies = FENrulePlies;
12421               for( i=0; i< nrCastlingRights; i++ )
12422                   initialRights[i] = initial_position[CASTLING][i];
12423           }
12424           yyboardindex = forwardMostMove;
12425           free(gameInfo.fen);
12426           gameInfo.fen = NULL;
12427         }
12428
12429         yyboardindex = forwardMostMove;
12430         cm = (ChessMove) Myylex();
12431
12432         /* Handle comments interspersed among the tags */
12433         while (cm == Comment) {
12434             char *p;
12435             if (appData.debugMode)
12436               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12437             p = yy_text;
12438             AppendComment(currentMove, p, FALSE);
12439             yyboardindex = forwardMostMove;
12440             cm = (ChessMove) Myylex();
12441         }
12442     }
12443
12444     /* don't rely on existence of Event tag since if game was
12445      * pasted from clipboard the Event tag may not exist
12446      */
12447     if (numPGNTags > 0){
12448         char *tags;
12449         if (gameInfo.variant == VariantNormal) {
12450           VariantClass v = StringToVariant(gameInfo.event);
12451           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12452           if(v < VariantShogi) gameInfo.variant = v;
12453         }
12454         if (!matchMode) {
12455           if( appData.autoDisplayTags ) {
12456             tags = PGNTags(&gameInfo);
12457             TagsPopUp(tags, CmailMsg());
12458             free(tags);
12459           }
12460         }
12461     } else {
12462         /* Make something up, but don't display it now */
12463         SetGameInfo();
12464         TagsPopDown();
12465     }
12466
12467     if (cm == PositionDiagram) {
12468         int i, j;
12469         char *p;
12470         Board initial_position;
12471
12472         if (appData.debugMode)
12473           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12474
12475         if (!startedFromSetupPosition) {
12476             p = yy_text;
12477             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12478               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12479                 switch (*p) {
12480                   case '{':
12481                   case '[':
12482                   case '-':
12483                   case ' ':
12484                   case '\t':
12485                   case '\n':
12486                   case '\r':
12487                     break;
12488                   default:
12489                     initial_position[i][j++] = CharToPiece(*p);
12490                     break;
12491                 }
12492             while (*p == ' ' || *p == '\t' ||
12493                    *p == '\n' || *p == '\r') p++;
12494
12495             if (strncmp(p, "black", strlen("black"))==0)
12496               blackPlaysFirst = TRUE;
12497             else
12498               blackPlaysFirst = FALSE;
12499             startedFromSetupPosition = TRUE;
12500
12501             CopyBoard(boards[0], initial_position);
12502             if (blackPlaysFirst) {
12503                 currentMove = forwardMostMove = backwardMostMove = 1;
12504                 CopyBoard(boards[1], initial_position);
12505                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12506                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12507                 timeRemaining[0][1] = whiteTimeRemaining;
12508                 timeRemaining[1][1] = blackTimeRemaining;
12509                 if (commentList[0] != NULL) {
12510                     commentList[1] = commentList[0];
12511                     commentList[0] = NULL;
12512                 }
12513             } else {
12514                 currentMove = forwardMostMove = backwardMostMove = 0;
12515             }
12516         }
12517         yyboardindex = forwardMostMove;
12518         cm = (ChessMove) Myylex();
12519     }
12520
12521   if(!creatingBook) {
12522     if (first.pr == NoProc) {
12523         StartChessProgram(&first);
12524     }
12525     InitChessProgram(&first, FALSE);
12526     SendToProgram("force\n", &first);
12527     if (startedFromSetupPosition) {
12528         SendBoard(&first, forwardMostMove);
12529     if (appData.debugMode) {
12530         fprintf(debugFP, "Load Game\n");
12531     }
12532         DisplayBothClocks();
12533     }
12534   }
12535
12536     /* [HGM] server: flag to write setup moves in broadcast file as one */
12537     loadFlag = appData.suppressLoadMoves;
12538
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     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12550         cm == WhiteWins || cm == BlackWins ||
12551         cm == GameIsDrawn || cm == GameUnfinished) {
12552         DisplayMessage("", _("No moves in game"));
12553         if (cmailMsgLoaded) {
12554             if (appData.debugMode)
12555               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12556             ClearHighlights();
12557             flipView = FALSE;
12558         }
12559         DrawPosition(FALSE, boards[currentMove]);
12560         DisplayBothClocks();
12561         gameMode = EditGame;
12562         ModeHighlight();
12563         gameFileFP = NULL;
12564         cmailOldMove = 0;
12565         return TRUE;
12566     }
12567
12568     // [HGM] PV info: routine tests if comment empty
12569     if (!matchMode && (pausing || appData.timeDelay != 0)) {
12570         DisplayComment(currentMove - 1, commentList[currentMove]);
12571     }
12572     if (!matchMode && appData.timeDelay != 0)
12573       DrawPosition(FALSE, boards[currentMove]);
12574
12575     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12576       programStats.ok_to_send = 1;
12577     }
12578
12579     /* if the first token after the PGN tags is a move
12580      * and not move number 1, retrieve it from the parser
12581      */
12582     if (cm != MoveNumberOne)
12583         LoadGameOneMove(cm);
12584
12585     /* load the remaining moves from the file */
12586     while (LoadGameOneMove(EndOfFile)) {
12587       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12588       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12589     }
12590
12591     /* rewind to the start of the game */
12592     currentMove = backwardMostMove;
12593
12594     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12595
12596     if (oldGameMode == AnalyzeFile) {
12597       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
12598       AnalyzeFileEvent();
12599     } else
12600     if (oldGameMode == AnalyzeMode) {
12601       AnalyzeFileEvent();
12602     }
12603
12604     if(creatingBook) return TRUE;
12605     if (!matchMode && pos > 0) {
12606         ToNrEvent(pos); // [HGM] no autoplay if selected on position
12607     } else
12608     if (matchMode || appData.timeDelay == 0) {
12609       ToEndEvent();
12610     } else if (appData.timeDelay > 0) {
12611       AutoPlayGameLoop();
12612     }
12613
12614     if (appData.debugMode)
12615         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
12616
12617     loadFlag = 0; /* [HGM] true game starts */
12618     return TRUE;
12619 }
12620
12621 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
12622 int
12623 ReloadPosition (int offset)
12624 {
12625     int positionNumber = lastLoadPositionNumber + offset;
12626     if (lastLoadPositionFP == NULL) {
12627         DisplayError(_("No position has been loaded yet"), 0);
12628         return FALSE;
12629     }
12630     if (positionNumber <= 0) {
12631         DisplayError(_("Can't back up any further"), 0);
12632         return FALSE;
12633     }
12634     return LoadPosition(lastLoadPositionFP, positionNumber,
12635                         lastLoadPositionTitle);
12636 }
12637
12638 /* Load the nth position from the given file */
12639 int
12640 LoadPositionFromFile (char *filename, int n, char *title)
12641 {
12642     FILE *f;
12643     char buf[MSG_SIZ];
12644
12645     if (strcmp(filename, "-") == 0) {
12646         return LoadPosition(stdin, n, "stdin");
12647     } else {
12648         f = fopen(filename, "rb");
12649         if (f == NULL) {
12650             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12651             DisplayError(buf, errno);
12652             return FALSE;
12653         } else {
12654             return LoadPosition(f, n, title);
12655         }
12656     }
12657 }
12658
12659 /* Load the nth position from the given open file, and close it */
12660 int
12661 LoadPosition (FILE *f, int positionNumber, char *title)
12662 {
12663     char *p, line[MSG_SIZ];
12664     Board initial_position;
12665     int i, j, fenMode, pn;
12666
12667     if (gameMode == Training )
12668         SetTrainingModeOff();
12669
12670     if (gameMode != BeginningOfGame) {
12671         Reset(FALSE, TRUE);
12672     }
12673     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12674         fclose(lastLoadPositionFP);
12675     }
12676     if (positionNumber == 0) positionNumber = 1;
12677     lastLoadPositionFP = f;
12678     lastLoadPositionNumber = positionNumber;
12679     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12680     if (first.pr == NoProc && !appData.noChessProgram) {
12681       StartChessProgram(&first);
12682       InitChessProgram(&first, FALSE);
12683     }
12684     pn = positionNumber;
12685     if (positionNumber < 0) {
12686         /* Negative position number means to seek to that byte offset */
12687         if (fseek(f, -positionNumber, 0) == -1) {
12688             DisplayError(_("Can't seek on position file"), 0);
12689             return FALSE;
12690         };
12691         pn = 1;
12692     } else {
12693         if (fseek(f, 0, 0) == -1) {
12694             if (f == lastLoadPositionFP ?
12695                 positionNumber == lastLoadPositionNumber + 1 :
12696                 positionNumber == 1) {
12697                 pn = 1;
12698             } else {
12699                 DisplayError(_("Can't seek on position file"), 0);
12700                 return FALSE;
12701             }
12702         }
12703     }
12704     /* See if this file is FEN or old-style xboard */
12705     if (fgets(line, MSG_SIZ, f) == NULL) {
12706         DisplayError(_("Position not found in file"), 0);
12707         return FALSE;
12708     }
12709     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12710     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12711
12712     if (pn >= 2) {
12713         if (fenMode || line[0] == '#') pn--;
12714         while (pn > 0) {
12715             /* skip positions before number pn */
12716             if (fgets(line, MSG_SIZ, f) == NULL) {
12717                 Reset(TRUE, TRUE);
12718                 DisplayError(_("Position not found in file"), 0);
12719                 return FALSE;
12720             }
12721             if (fenMode || line[0] == '#') pn--;
12722         }
12723     }
12724
12725     if (fenMode) {
12726         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
12727             DisplayError(_("Bad FEN position in file"), 0);
12728             return FALSE;
12729         }
12730     } else {
12731         (void) fgets(line, MSG_SIZ, f);
12732         (void) fgets(line, MSG_SIZ, f);
12733
12734         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12735             (void) fgets(line, MSG_SIZ, f);
12736             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12737                 if (*p == ' ')
12738                   continue;
12739                 initial_position[i][j++] = CharToPiece(*p);
12740             }
12741         }
12742
12743         blackPlaysFirst = FALSE;
12744         if (!feof(f)) {
12745             (void) fgets(line, MSG_SIZ, f);
12746             if (strncmp(line, "black", strlen("black"))==0)
12747               blackPlaysFirst = TRUE;
12748         }
12749     }
12750     startedFromSetupPosition = TRUE;
12751
12752     CopyBoard(boards[0], initial_position);
12753     if (blackPlaysFirst) {
12754         currentMove = forwardMostMove = backwardMostMove = 1;
12755         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12756         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12757         CopyBoard(boards[1], initial_position);
12758         DisplayMessage("", _("Black to play"));
12759     } else {
12760         currentMove = forwardMostMove = backwardMostMove = 0;
12761         DisplayMessage("", _("White to play"));
12762     }
12763     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12764     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12765         SendToProgram("force\n", &first);
12766         SendBoard(&first, forwardMostMove);
12767     }
12768     if (appData.debugMode) {
12769 int i, j;
12770   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12771   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12772         fprintf(debugFP, "Load Position\n");
12773     }
12774
12775     if (positionNumber > 1) {
12776       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12777         DisplayTitle(line);
12778     } else {
12779         DisplayTitle(title);
12780     }
12781     gameMode = EditGame;
12782     ModeHighlight();
12783     ResetClocks();
12784     timeRemaining[0][1] = whiteTimeRemaining;
12785     timeRemaining[1][1] = blackTimeRemaining;
12786     DrawPosition(FALSE, boards[currentMove]);
12787
12788     return TRUE;
12789 }
12790
12791
12792 void
12793 CopyPlayerNameIntoFileName (char **dest, char *src)
12794 {
12795     while (*src != NULLCHAR && *src != ',') {
12796         if (*src == ' ') {
12797             *(*dest)++ = '_';
12798             src++;
12799         } else {
12800             *(*dest)++ = *src++;
12801         }
12802     }
12803 }
12804
12805 char *
12806 DefaultFileName (char *ext)
12807 {
12808     static char def[MSG_SIZ];
12809     char *p;
12810
12811     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12812         p = def;
12813         CopyPlayerNameIntoFileName(&p, gameInfo.white);
12814         *p++ = '-';
12815         CopyPlayerNameIntoFileName(&p, gameInfo.black);
12816         *p++ = '.';
12817         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12818     } else {
12819         def[0] = NULLCHAR;
12820     }
12821     return def;
12822 }
12823
12824 /* Save the current game to the given file */
12825 int
12826 SaveGameToFile (char *filename, int append)
12827 {
12828     FILE *f;
12829     char buf[MSG_SIZ];
12830     int result, i, t,tot=0;
12831
12832     if (strcmp(filename, "-") == 0) {
12833         return SaveGame(stdout, 0, NULL);
12834     } else {
12835         for(i=0; i<10; i++) { // upto 10 tries
12836              f = fopen(filename, append ? "a" : "w");
12837              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12838              if(f || errno != 13) break;
12839              DoSleep(t = 5 + random()%11); // wait 5-15 msec
12840              tot += t;
12841         }
12842         if (f == NULL) {
12843             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12844             DisplayError(buf, errno);
12845             return FALSE;
12846         } else {
12847             safeStrCpy(buf, lastMsg, MSG_SIZ);
12848             DisplayMessage(_("Waiting for access to save file"), "");
12849             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
12850             DisplayMessage(_("Saving game"), "");
12851             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
12852             result = SaveGame(f, 0, NULL);
12853             DisplayMessage(buf, "");
12854             return result;
12855         }
12856     }
12857 }
12858
12859 char *
12860 SavePart (char *str)
12861 {
12862     static char buf[MSG_SIZ];
12863     char *p;
12864
12865     p = strchr(str, ' ');
12866     if (p == NULL) return str;
12867     strncpy(buf, str, p - str);
12868     buf[p - str] = NULLCHAR;
12869     return buf;
12870 }
12871
12872 #define PGN_MAX_LINE 75
12873
12874 #define PGN_SIDE_WHITE  0
12875 #define PGN_SIDE_BLACK  1
12876
12877 static int
12878 FindFirstMoveOutOfBook (int side)
12879 {
12880     int result = -1;
12881
12882     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12883         int index = backwardMostMove;
12884         int has_book_hit = 0;
12885
12886         if( (index % 2) != side ) {
12887             index++;
12888         }
12889
12890         while( index < forwardMostMove ) {
12891             /* Check to see if engine is in book */
12892             int depth = pvInfoList[index].depth;
12893             int score = pvInfoList[index].score;
12894             int in_book = 0;
12895
12896             if( depth <= 2 ) {
12897                 in_book = 1;
12898             }
12899             else if( score == 0 && depth == 63 ) {
12900                 in_book = 1; /* Zappa */
12901             }
12902             else if( score == 2 && depth == 99 ) {
12903                 in_book = 1; /* Abrok */
12904             }
12905
12906             has_book_hit += in_book;
12907
12908             if( ! in_book ) {
12909                 result = index;
12910
12911                 break;
12912             }
12913
12914             index += 2;
12915         }
12916     }
12917
12918     return result;
12919 }
12920
12921 void
12922 GetOutOfBookInfo (char * buf)
12923 {
12924     int oob[2];
12925     int i;
12926     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12927
12928     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12929     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12930
12931     *buf = '\0';
12932
12933     if( oob[0] >= 0 || oob[1] >= 0 ) {
12934         for( i=0; i<2; i++ ) {
12935             int idx = oob[i];
12936
12937             if( idx >= 0 ) {
12938                 if( i > 0 && oob[0] >= 0 ) {
12939                     strcat( buf, "   " );
12940                 }
12941
12942                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12943                 sprintf( buf+strlen(buf), "%s%.2f",
12944                     pvInfoList[idx].score >= 0 ? "+" : "",
12945                     pvInfoList[idx].score / 100.0 );
12946             }
12947         }
12948     }
12949 }
12950
12951 /* Save game in PGN style and close the file */
12952 int
12953 SaveGamePGN (FILE *f)
12954 {
12955     int i, offset, linelen, newblock;
12956 //    char *movetext;
12957     char numtext[32];
12958     int movelen, numlen, blank;
12959     char move_buffer[100]; /* [AS] Buffer for move+PV info */
12960
12961     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12962
12963     PrintPGNTags(f, &gameInfo);
12964
12965     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
12966
12967     if (backwardMostMove > 0 || startedFromSetupPosition) {
12968         char *fen = PositionToFEN(backwardMostMove, NULL, 1);
12969         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12970         fprintf(f, "\n{--------------\n");
12971         PrintPosition(f, backwardMostMove);
12972         fprintf(f, "--------------}\n");
12973         free(fen);
12974     }
12975     else {
12976         /* [AS] Out of book annotation */
12977         if( appData.saveOutOfBookInfo ) {
12978             char buf[64];
12979
12980             GetOutOfBookInfo( buf );
12981
12982             if( buf[0] != '\0' ) {
12983                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12984             }
12985         }
12986
12987         fprintf(f, "\n");
12988     }
12989
12990     i = backwardMostMove;
12991     linelen = 0;
12992     newblock = TRUE;
12993
12994     while (i < forwardMostMove) {
12995         /* Print comments preceding this move */
12996         if (commentList[i] != NULL) {
12997             if (linelen > 0) fprintf(f, "\n");
12998             fprintf(f, "%s", commentList[i]);
12999             linelen = 0;
13000             newblock = TRUE;
13001         }
13002
13003         /* Format move number */
13004         if ((i % 2) == 0)
13005           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13006         else
13007           if (newblock)
13008             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13009           else
13010             numtext[0] = NULLCHAR;
13011
13012         numlen = strlen(numtext);
13013         newblock = FALSE;
13014
13015         /* Print move number */
13016         blank = linelen > 0 && numlen > 0;
13017         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13018             fprintf(f, "\n");
13019             linelen = 0;
13020             blank = 0;
13021         }
13022         if (blank) {
13023             fprintf(f, " ");
13024             linelen++;
13025         }
13026         fprintf(f, "%s", numtext);
13027         linelen += numlen;
13028
13029         /* Get move */
13030         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13031         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13032
13033         /* Print move */
13034         blank = linelen > 0 && movelen > 0;
13035         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13036             fprintf(f, "\n");
13037             linelen = 0;
13038             blank = 0;
13039         }
13040         if (blank) {
13041             fprintf(f, " ");
13042             linelen++;
13043         }
13044         fprintf(f, "%s", move_buffer);
13045         linelen += movelen;
13046
13047         /* [AS] Add PV info if present */
13048         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13049             /* [HGM] add time */
13050             char buf[MSG_SIZ]; int seconds;
13051
13052             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13053
13054             if( seconds <= 0)
13055               buf[0] = 0;
13056             else
13057               if( seconds < 30 )
13058                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13059               else
13060                 {
13061                   seconds = (seconds + 4)/10; // round to full seconds
13062                   if( seconds < 60 )
13063                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
13064                   else
13065                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
13066                 }
13067
13068             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
13069                       pvInfoList[i].score >= 0 ? "+" : "",
13070                       pvInfoList[i].score / 100.0,
13071                       pvInfoList[i].depth,
13072                       buf );
13073
13074             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
13075
13076             /* Print score/depth */
13077             blank = linelen > 0 && movelen > 0;
13078             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13079                 fprintf(f, "\n");
13080                 linelen = 0;
13081                 blank = 0;
13082             }
13083             if (blank) {
13084                 fprintf(f, " ");
13085                 linelen++;
13086             }
13087             fprintf(f, "%s", move_buffer);
13088             linelen += movelen;
13089         }
13090
13091         i++;
13092     }
13093
13094     /* Start a new line */
13095     if (linelen > 0) fprintf(f, "\n");
13096
13097     /* Print comments after last move */
13098     if (commentList[i] != NULL) {
13099         fprintf(f, "%s\n", commentList[i]);
13100     }
13101
13102     /* Print result */
13103     if (gameInfo.resultDetails != NULL &&
13104         gameInfo.resultDetails[0] != NULLCHAR) {
13105         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
13106                 PGNResult(gameInfo.result));
13107     } else {
13108         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13109     }
13110
13111     fclose(f);
13112     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13113     return TRUE;
13114 }
13115
13116 /* Save game in old style and close the file */
13117 int
13118 SaveGameOldStyle (FILE *f)
13119 {
13120     int i, offset;
13121     time_t tm;
13122
13123     tm = time((time_t *) NULL);
13124
13125     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
13126     PrintOpponents(f);
13127
13128     if (backwardMostMove > 0 || startedFromSetupPosition) {
13129         fprintf(f, "\n[--------------\n");
13130         PrintPosition(f, backwardMostMove);
13131         fprintf(f, "--------------]\n");
13132     } else {
13133         fprintf(f, "\n");
13134     }
13135
13136     i = backwardMostMove;
13137     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13138
13139     while (i < forwardMostMove) {
13140         if (commentList[i] != NULL) {
13141             fprintf(f, "[%s]\n", commentList[i]);
13142         }
13143
13144         if ((i % 2) == 1) {
13145             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
13146             i++;
13147         } else {
13148             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
13149             i++;
13150             if (commentList[i] != NULL) {
13151                 fprintf(f, "\n");
13152                 continue;
13153             }
13154             if (i >= forwardMostMove) {
13155                 fprintf(f, "\n");
13156                 break;
13157             }
13158             fprintf(f, "%s\n", parseList[i]);
13159             i++;
13160         }
13161     }
13162
13163     if (commentList[i] != NULL) {
13164         fprintf(f, "[%s]\n", commentList[i]);
13165     }
13166
13167     /* This isn't really the old style, but it's close enough */
13168     if (gameInfo.resultDetails != NULL &&
13169         gameInfo.resultDetails[0] != NULLCHAR) {
13170         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13171                 gameInfo.resultDetails);
13172     } else {
13173         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13174     }
13175
13176     fclose(f);
13177     return TRUE;
13178 }
13179
13180 /* Save the current game to open file f and close the file */
13181 int
13182 SaveGame (FILE *f, int dummy, char *dummy2)
13183 {
13184     if (gameMode == EditPosition) EditPositionDone(TRUE);
13185     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13186     if (appData.oldSaveStyle)
13187       return SaveGameOldStyle(f);
13188     else
13189       return SaveGamePGN(f);
13190 }
13191
13192 /* Save the current position to the given file */
13193 int
13194 SavePositionToFile (char *filename)
13195 {
13196     FILE *f;
13197     char buf[MSG_SIZ];
13198
13199     if (strcmp(filename, "-") == 0) {
13200         return SavePosition(stdout, 0, NULL);
13201     } else {
13202         f = fopen(filename, "a");
13203         if (f == NULL) {
13204             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13205             DisplayError(buf, errno);
13206             return FALSE;
13207         } else {
13208             safeStrCpy(buf, lastMsg, MSG_SIZ);
13209             DisplayMessage(_("Waiting for access to save file"), "");
13210             flock(fileno(f), LOCK_EX); // [HGM] lock
13211             DisplayMessage(_("Saving position"), "");
13212             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
13213             SavePosition(f, 0, NULL);
13214             DisplayMessage(buf, "");
13215             return TRUE;
13216         }
13217     }
13218 }
13219
13220 /* Save the current position to the given open file and close the file */
13221 int
13222 SavePosition (FILE *f, int dummy, char *dummy2)
13223 {
13224     time_t tm;
13225     char *fen;
13226
13227     if (gameMode == EditPosition) EditPositionDone(TRUE);
13228     if (appData.oldSaveStyle) {
13229         tm = time((time_t *) NULL);
13230
13231         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
13232         PrintOpponents(f);
13233         fprintf(f, "[--------------\n");
13234         PrintPosition(f, currentMove);
13235         fprintf(f, "--------------]\n");
13236     } else {
13237         fen = PositionToFEN(currentMove, NULL, 1);
13238         fprintf(f, "%s\n", fen);
13239         free(fen);
13240     }
13241     fclose(f);
13242     return TRUE;
13243 }
13244
13245 void
13246 ReloadCmailMsgEvent (int unregister)
13247 {
13248 #if !WIN32
13249     static char *inFilename = NULL;
13250     static char *outFilename;
13251     int i;
13252     struct stat inbuf, outbuf;
13253     int status;
13254
13255     /* Any registered moves are unregistered if unregister is set, */
13256     /* i.e. invoked by the signal handler */
13257     if (unregister) {
13258         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13259             cmailMoveRegistered[i] = FALSE;
13260             if (cmailCommentList[i] != NULL) {
13261                 free(cmailCommentList[i]);
13262                 cmailCommentList[i] = NULL;
13263             }
13264         }
13265         nCmailMovesRegistered = 0;
13266     }
13267
13268     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13269         cmailResult[i] = CMAIL_NOT_RESULT;
13270     }
13271     nCmailResults = 0;
13272
13273     if (inFilename == NULL) {
13274         /* Because the filenames are static they only get malloced once  */
13275         /* and they never get freed                                      */
13276         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
13277         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
13278
13279         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
13280         sprintf(outFilename, "%s.out", appData.cmailGameName);
13281     }
13282
13283     status = stat(outFilename, &outbuf);
13284     if (status < 0) {
13285         cmailMailedMove = FALSE;
13286     } else {
13287         status = stat(inFilename, &inbuf);
13288         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
13289     }
13290
13291     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
13292        counts the games, notes how each one terminated, etc.
13293
13294        It would be nice to remove this kludge and instead gather all
13295        the information while building the game list.  (And to keep it
13296        in the game list nodes instead of having a bunch of fixed-size
13297        parallel arrays.)  Note this will require getting each game's
13298        termination from the PGN tags, as the game list builder does
13299        not process the game moves.  --mann
13300        */
13301     cmailMsgLoaded = TRUE;
13302     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
13303
13304     /* Load first game in the file or popup game menu */
13305     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
13306
13307 #endif /* !WIN32 */
13308     return;
13309 }
13310
13311 int
13312 RegisterMove ()
13313 {
13314     FILE *f;
13315     char string[MSG_SIZ];
13316
13317     if (   cmailMailedMove
13318         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
13319         return TRUE;            /* Allow free viewing  */
13320     }
13321
13322     /* Unregister move to ensure that we don't leave RegisterMove        */
13323     /* with the move registered when the conditions for registering no   */
13324     /* longer hold                                                       */
13325     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
13326         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
13327         nCmailMovesRegistered --;
13328
13329         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
13330           {
13331               free(cmailCommentList[lastLoadGameNumber - 1]);
13332               cmailCommentList[lastLoadGameNumber - 1] = NULL;
13333           }
13334     }
13335
13336     if (cmailOldMove == -1) {
13337         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
13338         return FALSE;
13339     }
13340
13341     if (currentMove > cmailOldMove + 1) {
13342         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
13343         return FALSE;
13344     }
13345
13346     if (currentMove < cmailOldMove) {
13347         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
13348         return FALSE;
13349     }
13350
13351     if (forwardMostMove > currentMove) {
13352         /* Silently truncate extra moves */
13353         TruncateGame();
13354     }
13355
13356     if (   (currentMove == cmailOldMove + 1)
13357         || (   (currentMove == cmailOldMove)
13358             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
13359                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
13360         if (gameInfo.result != GameUnfinished) {
13361             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
13362         }
13363
13364         if (commentList[currentMove] != NULL) {
13365             cmailCommentList[lastLoadGameNumber - 1]
13366               = StrSave(commentList[currentMove]);
13367         }
13368         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
13369
13370         if (appData.debugMode)
13371           fprintf(debugFP, "Saving %s for game %d\n",
13372                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
13373
13374         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
13375
13376         f = fopen(string, "w");
13377         if (appData.oldSaveStyle) {
13378             SaveGameOldStyle(f); /* also closes the file */
13379
13380             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
13381             f = fopen(string, "w");
13382             SavePosition(f, 0, NULL); /* also closes the file */
13383         } else {
13384             fprintf(f, "{--------------\n");
13385             PrintPosition(f, currentMove);
13386             fprintf(f, "--------------}\n\n");
13387
13388             SaveGame(f, 0, NULL); /* also closes the file*/
13389         }
13390
13391         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
13392         nCmailMovesRegistered ++;
13393     } else if (nCmailGames == 1) {
13394         DisplayError(_("You have not made a move yet"), 0);
13395         return FALSE;
13396     }
13397
13398     return TRUE;
13399 }
13400
13401 void
13402 MailMoveEvent ()
13403 {
13404 #if !WIN32
13405     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
13406     FILE *commandOutput;
13407     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
13408     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
13409     int nBuffers;
13410     int i;
13411     int archived;
13412     char *arcDir;
13413
13414     if (! cmailMsgLoaded) {
13415         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
13416         return;
13417     }
13418
13419     if (nCmailGames == nCmailResults) {
13420         DisplayError(_("No unfinished games"), 0);
13421         return;
13422     }
13423
13424 #if CMAIL_PROHIBIT_REMAIL
13425     if (cmailMailedMove) {
13426       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);
13427         DisplayError(msg, 0);
13428         return;
13429     }
13430 #endif
13431
13432     if (! (cmailMailedMove || RegisterMove())) return;
13433
13434     if (   cmailMailedMove
13435         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13436       snprintf(string, MSG_SIZ, partCommandString,
13437                appData.debugMode ? " -v" : "", appData.cmailGameName);
13438         commandOutput = popen(string, "r");
13439
13440         if (commandOutput == NULL) {
13441             DisplayError(_("Failed to invoke cmail"), 0);
13442         } else {
13443             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13444                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13445             }
13446             if (nBuffers > 1) {
13447                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13448                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13449                 nBytes = MSG_SIZ - 1;
13450             } else {
13451                 (void) memcpy(msg, buffer, nBytes);
13452             }
13453             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13454
13455             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13456                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
13457
13458                 archived = TRUE;
13459                 for (i = 0; i < nCmailGames; i ++) {
13460                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
13461                         archived = FALSE;
13462                     }
13463                 }
13464                 if (   archived
13465                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13466                         != NULL)) {
13467                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13468                            arcDir,
13469                            appData.cmailGameName,
13470                            gameInfo.date);
13471                     LoadGameFromFile(buffer, 1, buffer, FALSE);
13472                     cmailMsgLoaded = FALSE;
13473                 }
13474             }
13475
13476             DisplayInformation(msg);
13477             pclose(commandOutput);
13478         }
13479     } else {
13480         if ((*cmailMsg) != '\0') {
13481             DisplayInformation(cmailMsg);
13482         }
13483     }
13484
13485     return;
13486 #endif /* !WIN32 */
13487 }
13488
13489 char *
13490 CmailMsg ()
13491 {
13492 #if WIN32
13493     return NULL;
13494 #else
13495     int  prependComma = 0;
13496     char number[5];
13497     char string[MSG_SIZ];       /* Space for game-list */
13498     int  i;
13499
13500     if (!cmailMsgLoaded) return "";
13501
13502     if (cmailMailedMove) {
13503       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13504     } else {
13505         /* Create a list of games left */
13506       snprintf(string, MSG_SIZ, "[");
13507         for (i = 0; i < nCmailGames; i ++) {
13508             if (! (   cmailMoveRegistered[i]
13509                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13510                 if (prependComma) {
13511                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13512                 } else {
13513                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13514                     prependComma = 1;
13515                 }
13516
13517                 strcat(string, number);
13518             }
13519         }
13520         strcat(string, "]");
13521
13522         if (nCmailMovesRegistered + nCmailResults == 0) {
13523             switch (nCmailGames) {
13524               case 1:
13525                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13526                 break;
13527
13528               case 2:
13529                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13530                 break;
13531
13532               default:
13533                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13534                          nCmailGames);
13535                 break;
13536             }
13537         } else {
13538             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13539               case 1:
13540                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13541                          string);
13542                 break;
13543
13544               case 0:
13545                 if (nCmailResults == nCmailGames) {
13546                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13547                 } else {
13548                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13549                 }
13550                 break;
13551
13552               default:
13553                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13554                          string);
13555             }
13556         }
13557     }
13558     return cmailMsg;
13559 #endif /* WIN32 */
13560 }
13561
13562 void
13563 ResetGameEvent ()
13564 {
13565     if (gameMode == Training)
13566       SetTrainingModeOff();
13567
13568     Reset(TRUE, TRUE);
13569     cmailMsgLoaded = FALSE;
13570     if (appData.icsActive) {
13571       SendToICS(ics_prefix);
13572       SendToICS("refresh\n");
13573     }
13574 }
13575
13576 void
13577 ExitEvent (int status)
13578 {
13579     exiting++;
13580     if (exiting > 2) {
13581       /* Give up on clean exit */
13582       exit(status);
13583     }
13584     if (exiting > 1) {
13585       /* Keep trying for clean exit */
13586       return;
13587     }
13588
13589     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
13590
13591     if (telnetISR != NULL) {
13592       RemoveInputSource(telnetISR);
13593     }
13594     if (icsPR != NoProc) {
13595       DestroyChildProcess(icsPR, TRUE);
13596     }
13597
13598     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
13599     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
13600
13601     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
13602     /* make sure this other one finishes before killing it!                  */
13603     if(endingGame) { int count = 0;
13604         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
13605         while(endingGame && count++ < 10) DoSleep(1);
13606         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
13607     }
13608
13609     /* Kill off chess programs */
13610     if (first.pr != NoProc) {
13611         ExitAnalyzeMode();
13612
13613         DoSleep( appData.delayBeforeQuit );
13614         SendToProgram("quit\n", &first);
13615         DoSleep( appData.delayAfterQuit );
13616         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
13617     }
13618     if (second.pr != NoProc) {
13619         DoSleep( appData.delayBeforeQuit );
13620         SendToProgram("quit\n", &second);
13621         DoSleep( appData.delayAfterQuit );
13622         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
13623     }
13624     if (first.isr != NULL) {
13625         RemoveInputSource(first.isr);
13626     }
13627     if (second.isr != NULL) {
13628         RemoveInputSource(second.isr);
13629     }
13630
13631     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13632     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13633
13634     ShutDownFrontEnd();
13635     exit(status);
13636 }
13637
13638 void
13639 PauseEngine (ChessProgramState *cps)
13640 {
13641     SendToProgram("pause\n", cps);
13642     cps->pause = 2;
13643 }
13644
13645 void
13646 UnPauseEngine (ChessProgramState *cps)
13647 {
13648     SendToProgram("resume\n", cps);
13649     cps->pause = 1;
13650 }
13651
13652 void
13653 PauseEvent ()
13654 {
13655     if (appData.debugMode)
13656         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13657     if (pausing) {
13658         pausing = FALSE;
13659         ModeHighlight();
13660         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
13661             StartClocks();
13662             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
13663                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
13664                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
13665             }
13666             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
13667             HandleMachineMove(stashedInputMove, stalledEngine);
13668             stalledEngine = NULL;
13669             return;
13670         }
13671         if (gameMode == MachinePlaysWhite ||
13672             gameMode == TwoMachinesPlay   ||
13673             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
13674             if(first.pause)  UnPauseEngine(&first);
13675             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
13676             if(second.pause) UnPauseEngine(&second);
13677             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
13678             StartClocks();
13679         } else {
13680             DisplayBothClocks();
13681         }
13682         if (gameMode == PlayFromGameFile) {
13683             if (appData.timeDelay >= 0)
13684                 AutoPlayGameLoop();
13685         } else if (gameMode == IcsExamining && pauseExamInvalid) {
13686             Reset(FALSE, TRUE);
13687             SendToICS(ics_prefix);
13688             SendToICS("refresh\n");
13689         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
13690             ForwardInner(forwardMostMove);
13691         }
13692         pauseExamInvalid = FALSE;
13693     } else {
13694         switch (gameMode) {
13695           default:
13696             return;
13697           case IcsExamining:
13698             pauseExamForwardMostMove = forwardMostMove;
13699             pauseExamInvalid = FALSE;
13700             /* fall through */
13701           case IcsObserving:
13702           case IcsPlayingWhite:
13703           case IcsPlayingBlack:
13704             pausing = TRUE;
13705             ModeHighlight();
13706             return;
13707           case PlayFromGameFile:
13708             (void) StopLoadGameTimer();
13709             pausing = TRUE;
13710             ModeHighlight();
13711             break;
13712           case BeginningOfGame:
13713             if (appData.icsActive) return;
13714             /* else fall through */
13715           case MachinePlaysWhite:
13716           case MachinePlaysBlack:
13717           case TwoMachinesPlay:
13718             if (forwardMostMove == 0)
13719               return;           /* don't pause if no one has moved */
13720             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
13721                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
13722                 if(onMove->pause) {           // thinking engine can be paused
13723                     PauseEngine(onMove);      // do it
13724                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
13725                         PauseEngine(onMove->other);
13726                     else
13727                         SendToProgram("easy\n", onMove->other);
13728                     StopClocks();
13729                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
13730             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
13731                 if(first.pause) {
13732                     PauseEngine(&first);
13733                     StopClocks();
13734                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
13735             } else { // human on move, pause pondering by either method
13736                 if(first.pause)
13737                     PauseEngine(&first);
13738                 else if(appData.ponderNextMove)
13739                     SendToProgram("easy\n", &first);
13740                 StopClocks();
13741             }
13742             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
13743           case AnalyzeMode:
13744             pausing = TRUE;
13745             ModeHighlight();
13746             break;
13747         }
13748     }
13749 }
13750
13751 void
13752 EditCommentEvent ()
13753 {
13754     char title[MSG_SIZ];
13755
13756     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13757       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13758     } else {
13759       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13760                WhiteOnMove(currentMove - 1) ? " " : ".. ",
13761                parseList[currentMove - 1]);
13762     }
13763
13764     EditCommentPopUp(currentMove, title, commentList[currentMove]);
13765 }
13766
13767
13768 void
13769 EditTagsEvent ()
13770 {
13771     char *tags = PGNTags(&gameInfo);
13772     bookUp = FALSE;
13773     EditTagsPopUp(tags, NULL);
13774     free(tags);
13775 }
13776
13777 void
13778 ToggleSecond ()
13779 {
13780   if(second.analyzing) {
13781     SendToProgram("exit\n", &second);
13782     second.analyzing = FALSE;
13783   } else {
13784     if (second.pr == NoProc) StartChessProgram(&second);
13785     InitChessProgram(&second, FALSE);
13786     FeedMovesToProgram(&second, currentMove);
13787
13788     SendToProgram("analyze\n", &second);
13789     second.analyzing = TRUE;
13790   }
13791 }
13792
13793 /* Toggle ShowThinking */
13794 void
13795 ToggleShowThinking()
13796 {
13797   appData.showThinking = !appData.showThinking;
13798   ShowThinkingEvent();
13799 }
13800
13801 int
13802 AnalyzeModeEvent ()
13803 {
13804     char buf[MSG_SIZ];
13805
13806     if (!first.analysisSupport) {
13807       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
13808       DisplayError(buf, 0);
13809       return 0;
13810     }
13811     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
13812     if (appData.icsActive) {
13813         if (gameMode != IcsObserving) {
13814           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
13815             DisplayError(buf, 0);
13816             /* secure check */
13817             if (appData.icsEngineAnalyze) {
13818                 if (appData.debugMode)
13819                     fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
13820                 ExitAnalyzeMode();
13821                 ModeHighlight();
13822             }
13823             return 0;
13824         }
13825         /* if enable, user wants to disable icsEngineAnalyze */
13826         if (appData.icsEngineAnalyze) {
13827                 ExitAnalyzeMode();
13828                 ModeHighlight();
13829                 return 0;
13830         }
13831         appData.icsEngineAnalyze = TRUE;
13832         if (appData.debugMode)
13833             fprintf(debugFP, "ICS engine analyze starting... \n");
13834     }
13835
13836     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
13837     if (appData.noChessProgram || gameMode == AnalyzeMode)
13838       return 0;
13839
13840     if (gameMode != AnalyzeFile) {
13841         if (!appData.icsEngineAnalyze) {
13842                EditGameEvent();
13843                if (gameMode != EditGame) return 0;
13844         }
13845         if (!appData.showThinking) ToggleShowThinking();
13846         ResurrectChessProgram();
13847         SendToProgram("analyze\n", &first);
13848         first.analyzing = TRUE;
13849         /*first.maybeThinking = TRUE;*/
13850         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13851         EngineOutputPopUp();
13852     }
13853     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
13854     pausing = FALSE;
13855     ModeHighlight();
13856     SetGameInfo();
13857
13858     StartAnalysisClock();
13859     GetTimeMark(&lastNodeCountTime);
13860     lastNodeCount = 0;
13861     return 1;
13862 }
13863
13864 void
13865 AnalyzeFileEvent ()
13866 {
13867     if (appData.noChessProgram || gameMode == AnalyzeFile)
13868       return;
13869
13870     if (!first.analysisSupport) {
13871       char buf[MSG_SIZ];
13872       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
13873       DisplayError(buf, 0);
13874       return;
13875     }
13876
13877     if (gameMode != AnalyzeMode) {
13878         keepInfo = 1; // mere annotating should not alter PGN tags
13879         EditGameEvent();
13880         keepInfo = 0;
13881         if (gameMode != EditGame) return;
13882         if (!appData.showThinking) ToggleShowThinking();
13883         ResurrectChessProgram();
13884         SendToProgram("analyze\n", &first);
13885         first.analyzing = TRUE;
13886         /*first.maybeThinking = TRUE;*/
13887         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13888         EngineOutputPopUp();
13889     }
13890     gameMode = AnalyzeFile;
13891     pausing = FALSE;
13892     ModeHighlight();
13893
13894     StartAnalysisClock();
13895     GetTimeMark(&lastNodeCountTime);
13896     lastNodeCount = 0;
13897     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
13898     AnalysisPeriodicEvent(1);
13899 }
13900
13901 void
13902 MachineWhiteEvent ()
13903 {
13904     char buf[MSG_SIZ];
13905     char *bookHit = NULL;
13906
13907     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
13908       return;
13909
13910
13911     if (gameMode == PlayFromGameFile ||
13912         gameMode == TwoMachinesPlay  ||
13913         gameMode == Training         ||
13914         gameMode == AnalyzeMode      ||
13915         gameMode == EndOfGame)
13916         EditGameEvent();
13917
13918     if (gameMode == EditPosition)
13919         EditPositionDone(TRUE);
13920
13921     if (!WhiteOnMove(currentMove)) {
13922         DisplayError(_("It is not White's turn"), 0);
13923         return;
13924     }
13925
13926     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13927       ExitAnalyzeMode();
13928
13929     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13930         gameMode == AnalyzeFile)
13931         TruncateGame();
13932
13933     ResurrectChessProgram();    /* in case it isn't running */
13934     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
13935         gameMode = MachinePlaysWhite;
13936         ResetClocks();
13937     } else
13938     gameMode = MachinePlaysWhite;
13939     pausing = FALSE;
13940     ModeHighlight();
13941     SetGameInfo();
13942     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13943     DisplayTitle(buf);
13944     if (first.sendName) {
13945       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
13946       SendToProgram(buf, &first);
13947     }
13948     if (first.sendTime) {
13949       if (first.useColors) {
13950         SendToProgram("black\n", &first); /*gnu kludge*/
13951       }
13952       SendTimeRemaining(&first, TRUE);
13953     }
13954     if (first.useColors) {
13955       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
13956     }
13957     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13958     SetMachineThinkingEnables();
13959     first.maybeThinking = TRUE;
13960     StartClocks();
13961     firstMove = FALSE;
13962
13963     if (appData.autoFlipView && !flipView) {
13964       flipView = !flipView;
13965       DrawPosition(FALSE, NULL);
13966       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13967     }
13968
13969     if(bookHit) { // [HGM] book: simulate book reply
13970         static char bookMove[MSG_SIZ]; // a bit generous?
13971
13972         programStats.nodes = programStats.depth = programStats.time =
13973         programStats.score = programStats.got_only_move = 0;
13974         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13975
13976         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13977         strcat(bookMove, bookHit);
13978         HandleMachineMove(bookMove, &first);
13979     }
13980 }
13981
13982 void
13983 MachineBlackEvent ()
13984 {
13985   char buf[MSG_SIZ];
13986   char *bookHit = NULL;
13987
13988     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
13989         return;
13990
13991
13992     if (gameMode == PlayFromGameFile ||
13993         gameMode == TwoMachinesPlay  ||
13994         gameMode == Training         ||
13995         gameMode == AnalyzeMode      ||
13996         gameMode == EndOfGame)
13997         EditGameEvent();
13998
13999     if (gameMode == EditPosition)
14000         EditPositionDone(TRUE);
14001
14002     if (WhiteOnMove(currentMove)) {
14003         DisplayError(_("It is not Black's turn"), 0);
14004         return;
14005     }
14006
14007     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14008       ExitAnalyzeMode();
14009
14010     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14011         gameMode == AnalyzeFile)
14012         TruncateGame();
14013
14014     ResurrectChessProgram();    /* in case it isn't running */
14015     gameMode = MachinePlaysBlack;
14016     pausing = FALSE;
14017     ModeHighlight();
14018     SetGameInfo();
14019     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14020     DisplayTitle(buf);
14021     if (first.sendName) {
14022       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14023       SendToProgram(buf, &first);
14024     }
14025     if (first.sendTime) {
14026       if (first.useColors) {
14027         SendToProgram("white\n", &first); /*gnu kludge*/
14028       }
14029       SendTimeRemaining(&first, FALSE);
14030     }
14031     if (first.useColors) {
14032       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14033     }
14034     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14035     SetMachineThinkingEnables();
14036     first.maybeThinking = TRUE;
14037     StartClocks();
14038
14039     if (appData.autoFlipView && flipView) {
14040       flipView = !flipView;
14041       DrawPosition(FALSE, NULL);
14042       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14043     }
14044     if(bookHit) { // [HGM] book: simulate book reply
14045         static char bookMove[MSG_SIZ]; // a bit generous?
14046
14047         programStats.nodes = programStats.depth = programStats.time =
14048         programStats.score = programStats.got_only_move = 0;
14049         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14050
14051         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14052         strcat(bookMove, bookHit);
14053         HandleMachineMove(bookMove, &first);
14054     }
14055 }
14056
14057
14058 void
14059 DisplayTwoMachinesTitle ()
14060 {
14061     char buf[MSG_SIZ];
14062     if (appData.matchGames > 0) {
14063         if(appData.tourneyFile[0]) {
14064           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14065                    gameInfo.white, _("vs."), gameInfo.black,
14066                    nextGame+1, appData.matchGames+1,
14067                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14068         } else
14069         if (first.twoMachinesColor[0] == 'w') {
14070           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14071                    gameInfo.white, _("vs."),  gameInfo.black,
14072                    first.matchWins, second.matchWins,
14073                    matchGame - 1 - (first.matchWins + second.matchWins));
14074         } else {
14075           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14076                    gameInfo.white, _("vs."), gameInfo.black,
14077                    second.matchWins, first.matchWins,
14078                    matchGame - 1 - (first.matchWins + second.matchWins));
14079         }
14080     } else {
14081       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14082     }
14083     DisplayTitle(buf);
14084 }
14085
14086 void
14087 SettingsMenuIfReady ()
14088 {
14089   if (second.lastPing != second.lastPong) {
14090     DisplayMessage("", _("Waiting for second chess program"));
14091     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14092     return;
14093   }
14094   ThawUI();
14095   DisplayMessage("", "");
14096   SettingsPopUp(&second);
14097 }
14098
14099 int
14100 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
14101 {
14102     char buf[MSG_SIZ];
14103     if (cps->pr == NoProc) {
14104         StartChessProgram(cps);
14105         if (cps->protocolVersion == 1) {
14106           retry();
14107           ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
14108         } else {
14109           /* kludge: allow timeout for initial "feature" command */
14110           if(retry != TwoMachinesEventIfReady) FreezeUI();
14111           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
14112           DisplayMessage("", buf);
14113           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
14114         }
14115         return 1;
14116     }
14117     return 0;
14118 }
14119
14120 void
14121 TwoMachinesEvent P((void))
14122 {
14123     int i;
14124     char buf[MSG_SIZ];
14125     ChessProgramState *onmove;
14126     char *bookHit = NULL;
14127     static int stalling = 0;
14128     TimeMark now;
14129     long wait;
14130
14131     if (appData.noChessProgram) return;
14132
14133     switch (gameMode) {
14134       case TwoMachinesPlay:
14135         return;
14136       case MachinePlaysWhite:
14137       case MachinePlaysBlack:
14138         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14139             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
14140             return;
14141         }
14142         /* fall through */
14143       case BeginningOfGame:
14144       case PlayFromGameFile:
14145       case EndOfGame:
14146         EditGameEvent();
14147         if (gameMode != EditGame) return;
14148         break;
14149       case EditPosition:
14150         EditPositionDone(TRUE);
14151         break;
14152       case AnalyzeMode:
14153       case AnalyzeFile:
14154         ExitAnalyzeMode();
14155         break;
14156       case EditGame:
14157       default:
14158         break;
14159     }
14160
14161 //    forwardMostMove = currentMove;
14162     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
14163     startingEngine = TRUE;
14164
14165     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
14166
14167     if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14168     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14169       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14170       return;
14171     }
14172     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14173
14174     if(second.protocolVersion >= 2 && !strstr(second.variants, VariantName(gameInfo.variant))) {
14175         startingEngine = FALSE;
14176         DisplayError("second engine does not play this", 0);
14177         return;
14178     }
14179
14180     if(!stalling) {
14181       InitChessProgram(&second, FALSE); // unbalances ping of second engine
14182       SendToProgram("force\n", &second);
14183       stalling = 1;
14184       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14185       return;
14186     }
14187     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14188     if(appData.matchPause>10000 || appData.matchPause<10)
14189                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14190     wait = SubtractTimeMarks(&now, &pauseStart);
14191     if(wait < appData.matchPause) {
14192         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14193         return;
14194     }
14195     // we are now committed to starting the game
14196     stalling = 0;
14197     DisplayMessage("", "");
14198     if (startedFromSetupPosition) {
14199         SendBoard(&second, backwardMostMove);
14200     if (appData.debugMode) {
14201         fprintf(debugFP, "Two Machines\n");
14202     }
14203     }
14204     for (i = backwardMostMove; i < forwardMostMove; i++) {
14205         SendMoveToProgram(i, &second);
14206     }
14207
14208     gameMode = TwoMachinesPlay;
14209     pausing = startingEngine = FALSE;
14210     ModeHighlight(); // [HGM] logo: this triggers display update of logos
14211     SetGameInfo();
14212     DisplayTwoMachinesTitle();
14213     firstMove = TRUE;
14214     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14215         onmove = &first;
14216     } else {
14217         onmove = &second;
14218     }
14219     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14220     SendToProgram(first.computerString, &first);
14221     if (first.sendName) {
14222       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14223       SendToProgram(buf, &first);
14224     }
14225     SendToProgram(second.computerString, &second);
14226     if (second.sendName) {
14227       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
14228       SendToProgram(buf, &second);
14229     }
14230
14231     ResetClocks();
14232     if (!first.sendTime || !second.sendTime) {
14233         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14234         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14235     }
14236     if (onmove->sendTime) {
14237       if (onmove->useColors) {
14238         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
14239       }
14240       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
14241     }
14242     if (onmove->useColors) {
14243       SendToProgram(onmove->twoMachinesColor, onmove);
14244     }
14245     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
14246 //    SendToProgram("go\n", onmove);
14247     onmove->maybeThinking = TRUE;
14248     SetMachineThinkingEnables();
14249
14250     StartClocks();
14251
14252     if(bookHit) { // [HGM] book: simulate book reply
14253         static char bookMove[MSG_SIZ]; // a bit generous?
14254
14255         programStats.nodes = programStats.depth = programStats.time =
14256         programStats.score = programStats.got_only_move = 0;
14257         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14258
14259         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14260         strcat(bookMove, bookHit);
14261         savedMessage = bookMove; // args for deferred call
14262         savedState = onmove;
14263         ScheduleDelayedEvent(DeferredBookMove, 1);
14264     }
14265 }
14266
14267 void
14268 TrainingEvent ()
14269 {
14270     if (gameMode == Training) {
14271       SetTrainingModeOff();
14272       gameMode = PlayFromGameFile;
14273       DisplayMessage("", _("Training mode off"));
14274     } else {
14275       gameMode = Training;
14276       animateTraining = appData.animate;
14277
14278       /* make sure we are not already at the end of the game */
14279       if (currentMove < forwardMostMove) {
14280         SetTrainingModeOn();
14281         DisplayMessage("", _("Training mode on"));
14282       } else {
14283         gameMode = PlayFromGameFile;
14284         DisplayError(_("Already at end of game"), 0);
14285       }
14286     }
14287     ModeHighlight();
14288 }
14289
14290 void
14291 IcsClientEvent ()
14292 {
14293     if (!appData.icsActive) return;
14294     switch (gameMode) {
14295       case IcsPlayingWhite:
14296       case IcsPlayingBlack:
14297       case IcsObserving:
14298       case IcsIdle:
14299       case BeginningOfGame:
14300       case IcsExamining:
14301         return;
14302
14303       case EditGame:
14304         break;
14305
14306       case EditPosition:
14307         EditPositionDone(TRUE);
14308         break;
14309
14310       case AnalyzeMode:
14311       case AnalyzeFile:
14312         ExitAnalyzeMode();
14313         break;
14314
14315       default:
14316         EditGameEvent();
14317         break;
14318     }
14319
14320     gameMode = IcsIdle;
14321     ModeHighlight();
14322     return;
14323 }
14324
14325 void
14326 EditGameEvent ()
14327 {
14328     int i;
14329
14330     switch (gameMode) {
14331       case Training:
14332         SetTrainingModeOff();
14333         break;
14334       case MachinePlaysWhite:
14335       case MachinePlaysBlack:
14336       case BeginningOfGame:
14337         SendToProgram("force\n", &first);
14338         SetUserThinkingEnables();
14339         break;
14340       case PlayFromGameFile:
14341         (void) StopLoadGameTimer();
14342         if (gameFileFP != NULL) {
14343             gameFileFP = NULL;
14344         }
14345         break;
14346       case EditPosition:
14347         EditPositionDone(TRUE);
14348         break;
14349       case AnalyzeMode:
14350       case AnalyzeFile:
14351         ExitAnalyzeMode();
14352         SendToProgram("force\n", &first);
14353         break;
14354       case TwoMachinesPlay:
14355         GameEnds(EndOfFile, NULL, GE_PLAYER);
14356         ResurrectChessProgram();
14357         SetUserThinkingEnables();
14358         break;
14359       case EndOfGame:
14360         ResurrectChessProgram();
14361         break;
14362       case IcsPlayingBlack:
14363       case IcsPlayingWhite:
14364         DisplayError(_("Warning: You are still playing a game"), 0);
14365         break;
14366       case IcsObserving:
14367         DisplayError(_("Warning: You are still observing a game"), 0);
14368         break;
14369       case IcsExamining:
14370         DisplayError(_("Warning: You are still examining a game"), 0);
14371         break;
14372       case IcsIdle:
14373         break;
14374       case EditGame:
14375       default:
14376         return;
14377     }
14378
14379     pausing = FALSE;
14380     StopClocks();
14381     first.offeredDraw = second.offeredDraw = 0;
14382
14383     if (gameMode == PlayFromGameFile) {
14384         whiteTimeRemaining = timeRemaining[0][currentMove];
14385         blackTimeRemaining = timeRemaining[1][currentMove];
14386         DisplayTitle("");
14387     }
14388
14389     if (gameMode == MachinePlaysWhite ||
14390         gameMode == MachinePlaysBlack ||
14391         gameMode == TwoMachinesPlay ||
14392         gameMode == EndOfGame) {
14393         i = forwardMostMove;
14394         while (i > currentMove) {
14395             SendToProgram("undo\n", &first);
14396             i--;
14397         }
14398         if(!adjustedClock) {
14399         whiteTimeRemaining = timeRemaining[0][currentMove];
14400         blackTimeRemaining = timeRemaining[1][currentMove];
14401         DisplayBothClocks();
14402         }
14403         if (whiteFlag || blackFlag) {
14404             whiteFlag = blackFlag = 0;
14405         }
14406         DisplayTitle("");
14407     }
14408
14409     gameMode = EditGame;
14410     ModeHighlight();
14411     SetGameInfo();
14412 }
14413
14414
14415 void
14416 EditPositionEvent ()
14417 {
14418     if (gameMode == EditPosition) {
14419         EditGameEvent();
14420         return;
14421     }
14422
14423     EditGameEvent();
14424     if (gameMode != EditGame) return;
14425
14426     gameMode = EditPosition;
14427     ModeHighlight();
14428     SetGameInfo();
14429     if (currentMove > 0)
14430       CopyBoard(boards[0], boards[currentMove]);
14431
14432     blackPlaysFirst = !WhiteOnMove(currentMove);
14433     ResetClocks();
14434     currentMove = forwardMostMove = backwardMostMove = 0;
14435     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14436     DisplayMove(-1);
14437     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
14438 }
14439
14440 void
14441 ExitAnalyzeMode ()
14442 {
14443     /* [DM] icsEngineAnalyze - possible call from other functions */
14444     if (appData.icsEngineAnalyze) {
14445         appData.icsEngineAnalyze = FALSE;
14446
14447         DisplayMessage("",_("Close ICS engine analyze..."));
14448     }
14449     if (first.analysisSupport && first.analyzing) {
14450       SendToBoth("exit\n");
14451       first.analyzing = second.analyzing = FALSE;
14452     }
14453     thinkOutput[0] = NULLCHAR;
14454 }
14455
14456 void
14457 EditPositionDone (Boolean fakeRights)
14458 {
14459     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
14460
14461     startedFromSetupPosition = TRUE;
14462     InitChessProgram(&first, FALSE);
14463     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
14464       boards[0][EP_STATUS] = EP_NONE;
14465       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
14466       if(boards[0][0][BOARD_WIDTH>>1] == king) {
14467         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
14468         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
14469       } else boards[0][CASTLING][2] = NoRights;
14470       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
14471         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
14472         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
14473       } else boards[0][CASTLING][5] = NoRights;
14474       if(gameInfo.variant == VariantSChess) {
14475         int i;
14476         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
14477           boards[0][VIRGIN][i] = 0;
14478           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
14479           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
14480         }
14481       }
14482     }
14483     SendToProgram("force\n", &first);
14484     if (blackPlaysFirst) {
14485         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
14486         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
14487         currentMove = forwardMostMove = backwardMostMove = 1;
14488         CopyBoard(boards[1], boards[0]);
14489     } else {
14490         currentMove = forwardMostMove = backwardMostMove = 0;
14491     }
14492     SendBoard(&first, forwardMostMove);
14493     if (appData.debugMode) {
14494         fprintf(debugFP, "EditPosDone\n");
14495     }
14496     DisplayTitle("");
14497     DisplayMessage("", "");
14498     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14499     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14500     gameMode = EditGame;
14501     ModeHighlight();
14502     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14503     ClearHighlights(); /* [AS] */
14504 }
14505
14506 /* Pause for `ms' milliseconds */
14507 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14508 void
14509 TimeDelay (long ms)
14510 {
14511     TimeMark m1, m2;
14512
14513     GetTimeMark(&m1);
14514     do {
14515         GetTimeMark(&m2);
14516     } while (SubtractTimeMarks(&m2, &m1) < ms);
14517 }
14518
14519 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14520 void
14521 SendMultiLineToICS (char *buf)
14522 {
14523     char temp[MSG_SIZ+1], *p;
14524     int len;
14525
14526     len = strlen(buf);
14527     if (len > MSG_SIZ)
14528       len = MSG_SIZ;
14529
14530     strncpy(temp, buf, len);
14531     temp[len] = 0;
14532
14533     p = temp;
14534     while (*p) {
14535         if (*p == '\n' || *p == '\r')
14536           *p = ' ';
14537         ++p;
14538     }
14539
14540     strcat(temp, "\n");
14541     SendToICS(temp);
14542     SendToPlayer(temp, strlen(temp));
14543 }
14544
14545 void
14546 SetWhiteToPlayEvent ()
14547 {
14548     if (gameMode == EditPosition) {
14549         blackPlaysFirst = FALSE;
14550         DisplayBothClocks();    /* works because currentMove is 0 */
14551     } else if (gameMode == IcsExamining) {
14552         SendToICS(ics_prefix);
14553         SendToICS("tomove white\n");
14554     }
14555 }
14556
14557 void
14558 SetBlackToPlayEvent ()
14559 {
14560     if (gameMode == EditPosition) {
14561         blackPlaysFirst = TRUE;
14562         currentMove = 1;        /* kludge */
14563         DisplayBothClocks();
14564         currentMove = 0;
14565     } else if (gameMode == IcsExamining) {
14566         SendToICS(ics_prefix);
14567         SendToICS("tomove black\n");
14568     }
14569 }
14570
14571 void
14572 EditPositionMenuEvent (ChessSquare selection, int x, int y)
14573 {
14574     char buf[MSG_SIZ];
14575     ChessSquare piece = boards[0][y][x];
14576
14577     if (gameMode != EditPosition && gameMode != IcsExamining) return;
14578
14579     switch (selection) {
14580       case ClearBoard:
14581         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
14582             SendToICS(ics_prefix);
14583             SendToICS("bsetup clear\n");
14584         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
14585             SendToICS(ics_prefix);
14586             SendToICS("clearboard\n");
14587         } else {
14588             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
14589                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
14590                 for (y = 0; y < BOARD_HEIGHT; y++) {
14591                     if (gameMode == IcsExamining) {
14592                         if (boards[currentMove][y][x] != EmptySquare) {
14593                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
14594                                     AAA + x, ONE + y);
14595                             SendToICS(buf);
14596                         }
14597                     } else {
14598                         boards[0][y][x] = p;
14599                     }
14600                 }
14601             }
14602         }
14603         if (gameMode == EditPosition) {
14604             DrawPosition(FALSE, boards[0]);
14605         }
14606         break;
14607
14608       case WhitePlay:
14609         SetWhiteToPlayEvent();
14610         break;
14611
14612       case BlackPlay:
14613         SetBlackToPlayEvent();
14614         break;
14615
14616       case EmptySquare:
14617         if (gameMode == IcsExamining) {
14618             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14619             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
14620             SendToICS(buf);
14621         } else {
14622             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14623                 if(x == BOARD_LEFT-2) {
14624                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
14625                     boards[0][y][1] = 0;
14626                 } else
14627                 if(x == BOARD_RGHT+1) {
14628                     if(y >= gameInfo.holdingsSize) break;
14629                     boards[0][y][BOARD_WIDTH-2] = 0;
14630                 } else break;
14631             }
14632             boards[0][y][x] = EmptySquare;
14633             DrawPosition(FALSE, boards[0]);
14634         }
14635         break;
14636
14637       case PromotePiece:
14638         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
14639            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
14640             selection = (ChessSquare) (PROMOTED piece);
14641         } else if(piece == EmptySquare) selection = WhiteSilver;
14642         else selection = (ChessSquare)((int)piece - 1);
14643         goto defaultlabel;
14644
14645       case DemotePiece:
14646         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
14647            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
14648             selection = (ChessSquare) (DEMOTED piece);
14649         } else if(piece == EmptySquare) selection = BlackSilver;
14650         else selection = (ChessSquare)((int)piece + 1);
14651         goto defaultlabel;
14652
14653       case WhiteQueen:
14654       case BlackQueen:
14655         if(gameInfo.variant == VariantShatranj ||
14656            gameInfo.variant == VariantXiangqi  ||
14657            gameInfo.variant == VariantCourier  ||
14658            gameInfo.variant == VariantASEAN    ||
14659            gameInfo.variant == VariantMakruk     )
14660             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
14661         goto defaultlabel;
14662
14663       case WhiteKing:
14664       case BlackKing:
14665         if(gameInfo.variant == VariantXiangqi)
14666             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
14667         if(gameInfo.variant == VariantKnightmate)
14668             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
14669       default:
14670         defaultlabel:
14671         if (gameMode == IcsExamining) {
14672             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14673             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
14674                      PieceToChar(selection), AAA + x, ONE + y);
14675             SendToICS(buf);
14676         } else {
14677             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14678                 int n;
14679                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
14680                     n = PieceToNumber(selection - BlackPawn);
14681                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
14682                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
14683                     boards[0][BOARD_HEIGHT-1-n][1]++;
14684                 } else
14685                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
14686                     n = PieceToNumber(selection);
14687                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
14688                     boards[0][n][BOARD_WIDTH-1] = selection;
14689                     boards[0][n][BOARD_WIDTH-2]++;
14690                 }
14691             } else
14692             boards[0][y][x] = selection;
14693             DrawPosition(TRUE, boards[0]);
14694             ClearHighlights();
14695             fromX = fromY = -1;
14696         }
14697         break;
14698     }
14699 }
14700
14701
14702 void
14703 DropMenuEvent (ChessSquare selection, int x, int y)
14704 {
14705     ChessMove moveType;
14706
14707     switch (gameMode) {
14708       case IcsPlayingWhite:
14709       case MachinePlaysBlack:
14710         if (!WhiteOnMove(currentMove)) {
14711             DisplayMoveError(_("It is Black's turn"));
14712             return;
14713         }
14714         moveType = WhiteDrop;
14715         break;
14716       case IcsPlayingBlack:
14717       case MachinePlaysWhite:
14718         if (WhiteOnMove(currentMove)) {
14719             DisplayMoveError(_("It is White's turn"));
14720             return;
14721         }
14722         moveType = BlackDrop;
14723         break;
14724       case EditGame:
14725         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
14726         break;
14727       default:
14728         return;
14729     }
14730
14731     if (moveType == BlackDrop && selection < BlackPawn) {
14732       selection = (ChessSquare) ((int) selection
14733                                  + (int) BlackPawn - (int) WhitePawn);
14734     }
14735     if (boards[currentMove][y][x] != EmptySquare) {
14736         DisplayMoveError(_("That square is occupied"));
14737         return;
14738     }
14739
14740     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
14741 }
14742
14743 void
14744 AcceptEvent ()
14745 {
14746     /* Accept a pending offer of any kind from opponent */
14747
14748     if (appData.icsActive) {
14749         SendToICS(ics_prefix);
14750         SendToICS("accept\n");
14751     } else if (cmailMsgLoaded) {
14752         if (currentMove == cmailOldMove &&
14753             commentList[cmailOldMove] != NULL &&
14754             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14755                    "Black offers a draw" : "White offers a draw")) {
14756             TruncateGame();
14757             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14758             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14759         } else {
14760             DisplayError(_("There is no pending offer on this move"), 0);
14761             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14762         }
14763     } else {
14764         /* Not used for offers from chess program */
14765     }
14766 }
14767
14768 void
14769 DeclineEvent ()
14770 {
14771     /* Decline a pending offer of any kind from opponent */
14772
14773     if (appData.icsActive) {
14774         SendToICS(ics_prefix);
14775         SendToICS("decline\n");
14776     } else if (cmailMsgLoaded) {
14777         if (currentMove == cmailOldMove &&
14778             commentList[cmailOldMove] != NULL &&
14779             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14780                    "Black offers a draw" : "White offers a draw")) {
14781 #ifdef NOTDEF
14782             AppendComment(cmailOldMove, "Draw declined", TRUE);
14783             DisplayComment(cmailOldMove - 1, "Draw declined");
14784 #endif /*NOTDEF*/
14785         } else {
14786             DisplayError(_("There is no pending offer on this move"), 0);
14787         }
14788     } else {
14789         /* Not used for offers from chess program */
14790     }
14791 }
14792
14793 void
14794 RematchEvent ()
14795 {
14796     /* Issue ICS rematch command */
14797     if (appData.icsActive) {
14798         SendToICS(ics_prefix);
14799         SendToICS("rematch\n");
14800     }
14801 }
14802
14803 void
14804 CallFlagEvent ()
14805 {
14806     /* Call your opponent's flag (claim a win on time) */
14807     if (appData.icsActive) {
14808         SendToICS(ics_prefix);
14809         SendToICS("flag\n");
14810     } else {
14811         switch (gameMode) {
14812           default:
14813             return;
14814           case MachinePlaysWhite:
14815             if (whiteFlag) {
14816                 if (blackFlag)
14817                   GameEnds(GameIsDrawn, "Both players ran out of time",
14818                            GE_PLAYER);
14819                 else
14820                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
14821             } else {
14822                 DisplayError(_("Your opponent is not out of time"), 0);
14823             }
14824             break;
14825           case MachinePlaysBlack:
14826             if (blackFlag) {
14827                 if (whiteFlag)
14828                   GameEnds(GameIsDrawn, "Both players ran out of time",
14829                            GE_PLAYER);
14830                 else
14831                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
14832             } else {
14833                 DisplayError(_("Your opponent is not out of time"), 0);
14834             }
14835             break;
14836         }
14837     }
14838 }
14839
14840 void
14841 ClockClick (int which)
14842 {       // [HGM] code moved to back-end from winboard.c
14843         if(which) { // black clock
14844           if (gameMode == EditPosition || gameMode == IcsExamining) {
14845             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14846             SetBlackToPlayEvent();
14847           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
14848           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
14849           } else if (shiftKey) {
14850             AdjustClock(which, -1);
14851           } else if (gameMode == IcsPlayingWhite ||
14852                      gameMode == MachinePlaysBlack) {
14853             CallFlagEvent();
14854           }
14855         } else { // white clock
14856           if (gameMode == EditPosition || gameMode == IcsExamining) {
14857             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14858             SetWhiteToPlayEvent();
14859           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
14860           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
14861           } else if (shiftKey) {
14862             AdjustClock(which, -1);
14863           } else if (gameMode == IcsPlayingBlack ||
14864                    gameMode == MachinePlaysWhite) {
14865             CallFlagEvent();
14866           }
14867         }
14868 }
14869
14870 void
14871 DrawEvent ()
14872 {
14873     /* Offer draw or accept pending draw offer from opponent */
14874
14875     if (appData.icsActive) {
14876         /* Note: tournament rules require draw offers to be
14877            made after you make your move but before you punch
14878            your clock.  Currently ICS doesn't let you do that;
14879            instead, you immediately punch your clock after making
14880            a move, but you can offer a draw at any time. */
14881
14882         SendToICS(ics_prefix);
14883         SendToICS("draw\n");
14884         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
14885     } else if (cmailMsgLoaded) {
14886         if (currentMove == cmailOldMove &&
14887             commentList[cmailOldMove] != NULL &&
14888             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14889                    "Black offers a draw" : "White offers a draw")) {
14890             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14891             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14892         } else if (currentMove == cmailOldMove + 1) {
14893             char *offer = WhiteOnMove(cmailOldMove) ?
14894               "White offers a draw" : "Black offers a draw";
14895             AppendComment(currentMove, offer, TRUE);
14896             DisplayComment(currentMove - 1, offer);
14897             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
14898         } else {
14899             DisplayError(_("You must make your move before offering a draw"), 0);
14900             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14901         }
14902     } else if (first.offeredDraw) {
14903         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
14904     } else {
14905         if (first.sendDrawOffers) {
14906             SendToProgram("draw\n", &first);
14907             userOfferedDraw = TRUE;
14908         }
14909     }
14910 }
14911
14912 void
14913 AdjournEvent ()
14914 {
14915     /* Offer Adjourn or accept pending Adjourn offer from opponent */
14916
14917     if (appData.icsActive) {
14918         SendToICS(ics_prefix);
14919         SendToICS("adjourn\n");
14920     } else {
14921         /* Currently GNU Chess doesn't offer or accept Adjourns */
14922     }
14923 }
14924
14925
14926 void
14927 AbortEvent ()
14928 {
14929     /* Offer Abort or accept pending Abort offer from opponent */
14930
14931     if (appData.icsActive) {
14932         SendToICS(ics_prefix);
14933         SendToICS("abort\n");
14934     } else {
14935         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
14936     }
14937 }
14938
14939 void
14940 ResignEvent ()
14941 {
14942     /* Resign.  You can do this even if it's not your turn. */
14943
14944     if (appData.icsActive) {
14945         SendToICS(ics_prefix);
14946         SendToICS("resign\n");
14947     } else {
14948         switch (gameMode) {
14949           case MachinePlaysWhite:
14950             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14951             break;
14952           case MachinePlaysBlack:
14953             GameEnds(BlackWins, "White resigns", GE_PLAYER);
14954             break;
14955           case EditGame:
14956             if (cmailMsgLoaded) {
14957                 TruncateGame();
14958                 if (WhiteOnMove(cmailOldMove)) {
14959                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
14960                 } else {
14961                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14962                 }
14963                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
14964             }
14965             break;
14966           default:
14967             break;
14968         }
14969     }
14970 }
14971
14972
14973 void
14974 StopObservingEvent ()
14975 {
14976     /* Stop observing current games */
14977     SendToICS(ics_prefix);
14978     SendToICS("unobserve\n");
14979 }
14980
14981 void
14982 StopExaminingEvent ()
14983 {
14984     /* Stop observing current game */
14985     SendToICS(ics_prefix);
14986     SendToICS("unexamine\n");
14987 }
14988
14989 void
14990 ForwardInner (int target)
14991 {
14992     int limit; int oldSeekGraphUp = seekGraphUp;
14993
14994     if (appData.debugMode)
14995         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
14996                 target, currentMove, forwardMostMove);
14997
14998     if (gameMode == EditPosition)
14999       return;
15000
15001     seekGraphUp = FALSE;
15002     MarkTargetSquares(1);
15003
15004     if (gameMode == PlayFromGameFile && !pausing)
15005       PauseEvent();
15006
15007     if (gameMode == IcsExamining && pausing)
15008       limit = pauseExamForwardMostMove;
15009     else
15010       limit = forwardMostMove;
15011
15012     if (target > limit) target = limit;
15013
15014     if (target > 0 && moveList[target - 1][0]) {
15015         int fromX, fromY, toX, toY;
15016         toX = moveList[target - 1][2] - AAA;
15017         toY = moveList[target - 1][3] - ONE;
15018         if (moveList[target - 1][1] == '@') {
15019             if (appData.highlightLastMove) {
15020                 SetHighlights(-1, -1, toX, toY);
15021             }
15022         } else {
15023             fromX = moveList[target - 1][0] - AAA;
15024             fromY = moveList[target - 1][1] - ONE;
15025             if (target == currentMove + 1) {
15026                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
15027             }
15028             if (appData.highlightLastMove) {
15029                 SetHighlights(fromX, fromY, toX, toY);
15030             }
15031         }
15032     }
15033     if (gameMode == EditGame || gameMode == AnalyzeMode ||
15034         gameMode == Training || gameMode == PlayFromGameFile ||
15035         gameMode == AnalyzeFile) {
15036         while (currentMove < target) {
15037             if(second.analyzing) SendMoveToProgram(currentMove, &second);
15038             SendMoveToProgram(currentMove++, &first);
15039         }
15040     } else {
15041         currentMove = target;
15042     }
15043
15044     if (gameMode == EditGame || gameMode == EndOfGame) {
15045         whiteTimeRemaining = timeRemaining[0][currentMove];
15046         blackTimeRemaining = timeRemaining[1][currentMove];
15047     }
15048     DisplayBothClocks();
15049     DisplayMove(currentMove - 1);
15050     DrawPosition(oldSeekGraphUp, boards[currentMove]);
15051     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15052     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
15053         DisplayComment(currentMove - 1, commentList[currentMove]);
15054     }
15055     ClearMap(); // [HGM] exclude: invalidate map
15056 }
15057
15058
15059 void
15060 ForwardEvent ()
15061 {
15062     if (gameMode == IcsExamining && !pausing) {
15063         SendToICS(ics_prefix);
15064         SendToICS("forward\n");
15065     } else {
15066         ForwardInner(currentMove + 1);
15067     }
15068 }
15069
15070 void
15071 ToEndEvent ()
15072 {
15073     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15074         /* to optimze, we temporarily turn off analysis mode while we feed
15075          * the remaining moves to the engine. Otherwise we get analysis output
15076          * after each move.
15077          */
15078         if (first.analysisSupport) {
15079           SendToProgram("exit\nforce\n", &first);
15080           first.analyzing = FALSE;
15081         }
15082     }
15083
15084     if (gameMode == IcsExamining && !pausing) {
15085         SendToICS(ics_prefix);
15086         SendToICS("forward 999999\n");
15087     } else {
15088         ForwardInner(forwardMostMove);
15089     }
15090
15091     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15092         /* we have fed all the moves, so reactivate analysis mode */
15093         SendToProgram("analyze\n", &first);
15094         first.analyzing = TRUE;
15095         /*first.maybeThinking = TRUE;*/
15096         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15097     }
15098 }
15099
15100 void
15101 BackwardInner (int target)
15102 {
15103     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
15104
15105     if (appData.debugMode)
15106         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
15107                 target, currentMove, forwardMostMove);
15108
15109     if (gameMode == EditPosition) return;
15110     seekGraphUp = FALSE;
15111     MarkTargetSquares(1);
15112     if (currentMove <= backwardMostMove) {
15113         ClearHighlights();
15114         DrawPosition(full_redraw, boards[currentMove]);
15115         return;
15116     }
15117     if (gameMode == PlayFromGameFile && !pausing)
15118       PauseEvent();
15119
15120     if (moveList[target][0]) {
15121         int fromX, fromY, toX, toY;
15122         toX = moveList[target][2] - AAA;
15123         toY = moveList[target][3] - ONE;
15124         if (moveList[target][1] == '@') {
15125             if (appData.highlightLastMove) {
15126                 SetHighlights(-1, -1, toX, toY);
15127             }
15128         } else {
15129             fromX = moveList[target][0] - AAA;
15130             fromY = moveList[target][1] - ONE;
15131             if (target == currentMove - 1) {
15132                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
15133             }
15134             if (appData.highlightLastMove) {
15135                 SetHighlights(fromX, fromY, toX, toY);
15136             }
15137         }
15138     }
15139     if (gameMode == EditGame || gameMode==AnalyzeMode ||
15140         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
15141         while (currentMove > target) {
15142             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
15143                 // null move cannot be undone. Reload program with move history before it.
15144                 int i;
15145                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
15146                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
15147                 }
15148                 SendBoard(&first, i);
15149               if(second.analyzing) SendBoard(&second, i);
15150                 for(currentMove=i; currentMove<target; currentMove++) {
15151                     SendMoveToProgram(currentMove, &first);
15152                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
15153                 }
15154                 break;
15155             }
15156             SendToBoth("undo\n");
15157             currentMove--;
15158         }
15159     } else {
15160         currentMove = target;
15161     }
15162
15163     if (gameMode == EditGame || gameMode == EndOfGame) {
15164         whiteTimeRemaining = timeRemaining[0][currentMove];
15165         blackTimeRemaining = timeRemaining[1][currentMove];
15166     }
15167     DisplayBothClocks();
15168     DisplayMove(currentMove - 1);
15169     DrawPosition(full_redraw, boards[currentMove]);
15170     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15171     // [HGM] PV info: routine tests if comment empty
15172     DisplayComment(currentMove - 1, commentList[currentMove]);
15173     ClearMap(); // [HGM] exclude: invalidate map
15174 }
15175
15176 void
15177 BackwardEvent ()
15178 {
15179     if (gameMode == IcsExamining && !pausing) {
15180         SendToICS(ics_prefix);
15181         SendToICS("backward\n");
15182     } else {
15183         BackwardInner(currentMove - 1);
15184     }
15185 }
15186
15187 void
15188 ToStartEvent ()
15189 {
15190     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15191         /* to optimize, we temporarily turn off analysis mode while we undo
15192          * all the moves. Otherwise we get analysis output after each undo.
15193          */
15194         if (first.analysisSupport) {
15195           SendToProgram("exit\nforce\n", &first);
15196           first.analyzing = FALSE;
15197         }
15198     }
15199
15200     if (gameMode == IcsExamining && !pausing) {
15201         SendToICS(ics_prefix);
15202         SendToICS("backward 999999\n");
15203     } else {
15204         BackwardInner(backwardMostMove);
15205     }
15206
15207     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15208         /* we have fed all the moves, so reactivate analysis mode */
15209         SendToProgram("analyze\n", &first);
15210         first.analyzing = TRUE;
15211         /*first.maybeThinking = TRUE;*/
15212         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15213     }
15214 }
15215
15216 void
15217 ToNrEvent (int to)
15218 {
15219   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
15220   if (to >= forwardMostMove) to = forwardMostMove;
15221   if (to <= backwardMostMove) to = backwardMostMove;
15222   if (to < currentMove) {
15223     BackwardInner(to);
15224   } else {
15225     ForwardInner(to);
15226   }
15227 }
15228
15229 void
15230 RevertEvent (Boolean annotate)
15231 {
15232     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
15233         return;
15234     }
15235     if (gameMode != IcsExamining) {
15236         DisplayError(_("You are not examining a game"), 0);
15237         return;
15238     }
15239     if (pausing) {
15240         DisplayError(_("You can't revert while pausing"), 0);
15241         return;
15242     }
15243     SendToICS(ics_prefix);
15244     SendToICS("revert\n");
15245 }
15246
15247 void
15248 RetractMoveEvent ()
15249 {
15250     switch (gameMode) {
15251       case MachinePlaysWhite:
15252       case MachinePlaysBlack:
15253         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15254             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
15255             return;
15256         }
15257         if (forwardMostMove < 2) return;
15258         currentMove = forwardMostMove = forwardMostMove - 2;
15259         whiteTimeRemaining = timeRemaining[0][currentMove];
15260         blackTimeRemaining = timeRemaining[1][currentMove];
15261         DisplayBothClocks();
15262         DisplayMove(currentMove - 1);
15263         ClearHighlights();/*!! could figure this out*/
15264         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
15265         SendToProgram("remove\n", &first);
15266         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
15267         break;
15268
15269       case BeginningOfGame:
15270       default:
15271         break;
15272
15273       case IcsPlayingWhite:
15274       case IcsPlayingBlack:
15275         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
15276             SendToICS(ics_prefix);
15277             SendToICS("takeback 2\n");
15278         } else {
15279             SendToICS(ics_prefix);
15280             SendToICS("takeback 1\n");
15281         }
15282         break;
15283     }
15284 }
15285
15286 void
15287 MoveNowEvent ()
15288 {
15289     ChessProgramState *cps;
15290
15291     switch (gameMode) {
15292       case MachinePlaysWhite:
15293         if (!WhiteOnMove(forwardMostMove)) {
15294             DisplayError(_("It is your turn"), 0);
15295             return;
15296         }
15297         cps = &first;
15298         break;
15299       case MachinePlaysBlack:
15300         if (WhiteOnMove(forwardMostMove)) {
15301             DisplayError(_("It is your turn"), 0);
15302             return;
15303         }
15304         cps = &first;
15305         break;
15306       case TwoMachinesPlay:
15307         if (WhiteOnMove(forwardMostMove) ==
15308             (first.twoMachinesColor[0] == 'w')) {
15309             cps = &first;
15310         } else {
15311             cps = &second;
15312         }
15313         break;
15314       case BeginningOfGame:
15315       default:
15316         return;
15317     }
15318     SendToProgram("?\n", cps);
15319 }
15320
15321 void
15322 TruncateGameEvent ()
15323 {
15324     EditGameEvent();
15325     if (gameMode != EditGame) return;
15326     TruncateGame();
15327 }
15328
15329 void
15330 TruncateGame ()
15331 {
15332     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
15333     if (forwardMostMove > currentMove) {
15334         if (gameInfo.resultDetails != NULL) {
15335             free(gameInfo.resultDetails);
15336             gameInfo.resultDetails = NULL;
15337             gameInfo.result = GameUnfinished;
15338         }
15339         forwardMostMove = currentMove;
15340         HistorySet(parseList, backwardMostMove, forwardMostMove,
15341                    currentMove-1);
15342     }
15343 }
15344
15345 void
15346 HintEvent ()
15347 {
15348     if (appData.noChessProgram) return;
15349     switch (gameMode) {
15350       case MachinePlaysWhite:
15351         if (WhiteOnMove(forwardMostMove)) {
15352             DisplayError(_("Wait until your turn"), 0);
15353             return;
15354         }
15355         break;
15356       case BeginningOfGame:
15357       case MachinePlaysBlack:
15358         if (!WhiteOnMove(forwardMostMove)) {
15359             DisplayError(_("Wait until your turn"), 0);
15360             return;
15361         }
15362         break;
15363       default:
15364         DisplayError(_("No hint available"), 0);
15365         return;
15366     }
15367     SendToProgram("hint\n", &first);
15368     hintRequested = TRUE;
15369 }
15370
15371 void
15372 CreateBookEvent ()
15373 {
15374     ListGame * lg = (ListGame *) gameList.head;
15375     FILE *f, *g;
15376     int nItem;
15377     static int secondTime = FALSE;
15378
15379     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
15380         DisplayError(_("Game list not loaded or empty"), 0);
15381         return;
15382     }
15383
15384     if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
15385         fclose(g);
15386         secondTime++;
15387         DisplayNote(_("Book file exists! Try again for overwrite."));
15388         return;
15389     }
15390
15391     creatingBook = TRUE;
15392     secondTime = FALSE;
15393
15394     /* Get list size */
15395     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
15396         LoadGame(f, nItem, "", TRUE);
15397         AddGameToBook(TRUE);
15398         lg = (ListGame *) lg->node.succ;
15399     }
15400
15401     creatingBook = FALSE;
15402     FlushBook();
15403 }
15404
15405 void
15406 BookEvent ()
15407 {
15408     if (appData.noChessProgram) return;
15409     switch (gameMode) {
15410       case MachinePlaysWhite:
15411         if (WhiteOnMove(forwardMostMove)) {
15412             DisplayError(_("Wait until your turn"), 0);
15413             return;
15414         }
15415         break;
15416       case BeginningOfGame:
15417       case MachinePlaysBlack:
15418         if (!WhiteOnMove(forwardMostMove)) {
15419             DisplayError(_("Wait until your turn"), 0);
15420             return;
15421         }
15422         break;
15423       case EditPosition:
15424         EditPositionDone(TRUE);
15425         break;
15426       case TwoMachinesPlay:
15427         return;
15428       default:
15429         break;
15430     }
15431     SendToProgram("bk\n", &first);
15432     bookOutput[0] = NULLCHAR;
15433     bookRequested = TRUE;
15434 }
15435
15436 void
15437 AboutGameEvent ()
15438 {
15439     char *tags = PGNTags(&gameInfo);
15440     TagsPopUp(tags, CmailMsg());
15441     free(tags);
15442 }
15443
15444 /* end button procedures */
15445
15446 void
15447 PrintPosition (FILE *fp, int move)
15448 {
15449     int i, j;
15450
15451     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15452         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15453             char c = PieceToChar(boards[move][i][j]);
15454             fputc(c == 'x' ? '.' : c, fp);
15455             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
15456         }
15457     }
15458     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
15459       fprintf(fp, "white to play\n");
15460     else
15461       fprintf(fp, "black to play\n");
15462 }
15463
15464 void
15465 PrintOpponents (FILE *fp)
15466 {
15467     if (gameInfo.white != NULL) {
15468         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
15469     } else {
15470         fprintf(fp, "\n");
15471     }
15472 }
15473
15474 /* Find last component of program's own name, using some heuristics */
15475 void
15476 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
15477 {
15478     char *p, *q, c;
15479     int local = (strcmp(host, "localhost") == 0);
15480     while (!local && (p = strchr(prog, ';')) != NULL) {
15481         p++;
15482         while (*p == ' ') p++;
15483         prog = p;
15484     }
15485     if (*prog == '"' || *prog == '\'') {
15486         q = strchr(prog + 1, *prog);
15487     } else {
15488         q = strchr(prog, ' ');
15489     }
15490     if (q == NULL) q = prog + strlen(prog);
15491     p = q;
15492     while (p >= prog && *p != '/' && *p != '\\') p--;
15493     p++;
15494     if(p == prog && *p == '"') p++;
15495     c = *q; *q = 0;
15496     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
15497     memcpy(buf, p, q - p);
15498     buf[q - p] = NULLCHAR;
15499     if (!local) {
15500         strcat(buf, "@");
15501         strcat(buf, host);
15502     }
15503 }
15504
15505 char *
15506 TimeControlTagValue ()
15507 {
15508     char buf[MSG_SIZ];
15509     if (!appData.clockMode) {
15510       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
15511     } else if (movesPerSession > 0) {
15512       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
15513     } else if (timeIncrement == 0) {
15514       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
15515     } else {
15516       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
15517     }
15518     return StrSave(buf);
15519 }
15520
15521 void
15522 SetGameInfo ()
15523 {
15524     /* This routine is used only for certain modes */
15525     VariantClass v = gameInfo.variant;
15526     ChessMove r = GameUnfinished;
15527     char *p = NULL;
15528
15529     if(keepInfo) return;
15530
15531     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
15532         r = gameInfo.result;
15533         p = gameInfo.resultDetails;
15534         gameInfo.resultDetails = NULL;
15535     }
15536     ClearGameInfo(&gameInfo);
15537     gameInfo.variant = v;
15538
15539     switch (gameMode) {
15540       case MachinePlaysWhite:
15541         gameInfo.event = StrSave( appData.pgnEventHeader );
15542         gameInfo.site = StrSave(HostName());
15543         gameInfo.date = PGNDate();
15544         gameInfo.round = StrSave("-");
15545         gameInfo.white = StrSave(first.tidy);
15546         gameInfo.black = StrSave(UserName());
15547         gameInfo.timeControl = TimeControlTagValue();
15548         break;
15549
15550       case MachinePlaysBlack:
15551         gameInfo.event = StrSave( appData.pgnEventHeader );
15552         gameInfo.site = StrSave(HostName());
15553         gameInfo.date = PGNDate();
15554         gameInfo.round = StrSave("-");
15555         gameInfo.white = StrSave(UserName());
15556         gameInfo.black = StrSave(first.tidy);
15557         gameInfo.timeControl = TimeControlTagValue();
15558         break;
15559
15560       case TwoMachinesPlay:
15561         gameInfo.event = StrSave( appData.pgnEventHeader );
15562         gameInfo.site = StrSave(HostName());
15563         gameInfo.date = PGNDate();
15564         if (roundNr > 0) {
15565             char buf[MSG_SIZ];
15566             snprintf(buf, MSG_SIZ, "%d", roundNr);
15567             gameInfo.round = StrSave(buf);
15568         } else {
15569             gameInfo.round = StrSave("-");
15570         }
15571         if (first.twoMachinesColor[0] == 'w') {
15572             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15573             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15574         } else {
15575             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15576             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15577         }
15578         gameInfo.timeControl = TimeControlTagValue();
15579         break;
15580
15581       case EditGame:
15582         gameInfo.event = StrSave("Edited game");
15583         gameInfo.site = StrSave(HostName());
15584         gameInfo.date = PGNDate();
15585         gameInfo.round = StrSave("-");
15586         gameInfo.white = StrSave("-");
15587         gameInfo.black = StrSave("-");
15588         gameInfo.result = r;
15589         gameInfo.resultDetails = p;
15590         break;
15591
15592       case EditPosition:
15593         gameInfo.event = StrSave("Edited position");
15594         gameInfo.site = StrSave(HostName());
15595         gameInfo.date = PGNDate();
15596         gameInfo.round = StrSave("-");
15597         gameInfo.white = StrSave("-");
15598         gameInfo.black = StrSave("-");
15599         break;
15600
15601       case IcsPlayingWhite:
15602       case IcsPlayingBlack:
15603       case IcsObserving:
15604       case IcsExamining:
15605         break;
15606
15607       case PlayFromGameFile:
15608         gameInfo.event = StrSave("Game from non-PGN file");
15609         gameInfo.site = StrSave(HostName());
15610         gameInfo.date = PGNDate();
15611         gameInfo.round = StrSave("-");
15612         gameInfo.white = StrSave("?");
15613         gameInfo.black = StrSave("?");
15614         break;
15615
15616       default:
15617         break;
15618     }
15619 }
15620
15621 void
15622 ReplaceComment (int index, char *text)
15623 {
15624     int len;
15625     char *p;
15626     float score;
15627
15628     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
15629        pvInfoList[index-1].depth == len &&
15630        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
15631        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
15632     while (*text == '\n') text++;
15633     len = strlen(text);
15634     while (len > 0 && text[len - 1] == '\n') len--;
15635
15636     if (commentList[index] != NULL)
15637       free(commentList[index]);
15638
15639     if (len == 0) {
15640         commentList[index] = NULL;
15641         return;
15642     }
15643   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
15644       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
15645       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
15646     commentList[index] = (char *) malloc(len + 2);
15647     strncpy(commentList[index], text, len);
15648     commentList[index][len] = '\n';
15649     commentList[index][len + 1] = NULLCHAR;
15650   } else {
15651     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
15652     char *p;
15653     commentList[index] = (char *) malloc(len + 7);
15654     safeStrCpy(commentList[index], "{\n", 3);
15655     safeStrCpy(commentList[index]+2, text, len+1);
15656     commentList[index][len+2] = NULLCHAR;
15657     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
15658     strcat(commentList[index], "\n}\n");
15659   }
15660 }
15661
15662 void
15663 CrushCRs (char *text)
15664 {
15665   char *p = text;
15666   char *q = text;
15667   char ch;
15668
15669   do {
15670     ch = *p++;
15671     if (ch == '\r') continue;
15672     *q++ = ch;
15673   } while (ch != '\0');
15674 }
15675
15676 void
15677 AppendComment (int index, char *text, Boolean addBraces)
15678 /* addBraces  tells if we should add {} */
15679 {
15680     int oldlen, len;
15681     char *old;
15682
15683 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
15684     if(addBraces == 3) addBraces = 0; else // force appending literally
15685     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
15686
15687     CrushCRs(text);
15688     while (*text == '\n') text++;
15689     len = strlen(text);
15690     while (len > 0 && text[len - 1] == '\n') len--;
15691     text[len] = NULLCHAR;
15692
15693     if (len == 0) return;
15694
15695     if (commentList[index] != NULL) {
15696       Boolean addClosingBrace = addBraces;
15697         old = commentList[index];
15698         oldlen = strlen(old);
15699         while(commentList[index][oldlen-1] ==  '\n')
15700           commentList[index][--oldlen] = NULLCHAR;
15701         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
15702         safeStrCpy(commentList[index], old, oldlen + len + 6);
15703         free(old);
15704         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
15705         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
15706           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
15707           while (*text == '\n') { text++; len--; }
15708           commentList[index][--oldlen] = NULLCHAR;
15709       }
15710         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
15711         else          strcat(commentList[index], "\n");
15712         strcat(commentList[index], text);
15713         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
15714         else          strcat(commentList[index], "\n");
15715     } else {
15716         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
15717         if(addBraces)
15718           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
15719         else commentList[index][0] = NULLCHAR;
15720         strcat(commentList[index], text);
15721         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
15722         if(addBraces == TRUE) strcat(commentList[index], "}\n");
15723     }
15724 }
15725
15726 static char *
15727 FindStr (char * text, char * sub_text)
15728 {
15729     char * result = strstr( text, sub_text );
15730
15731     if( result != NULL ) {
15732         result += strlen( sub_text );
15733     }
15734
15735     return result;
15736 }
15737
15738 /* [AS] Try to extract PV info from PGN comment */
15739 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
15740 char *
15741 GetInfoFromComment (int index, char * text)
15742 {
15743     char * sep = text, *p;
15744
15745     if( text != NULL && index > 0 ) {
15746         int score = 0;
15747         int depth = 0;
15748         int time = -1, sec = 0, deci;
15749         char * s_eval = FindStr( text, "[%eval " );
15750         char * s_emt = FindStr( text, "[%emt " );
15751 #if 0
15752         if( s_eval != NULL || s_emt != NULL ) {
15753 #else
15754         if(0) { // [HGM] this code is not finished, and could actually be detrimental
15755 #endif
15756             /* New style */
15757             char delim;
15758
15759             if( s_eval != NULL ) {
15760                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
15761                     return text;
15762                 }
15763
15764                 if( delim != ']' ) {
15765                     return text;
15766                 }
15767             }
15768
15769             if( s_emt != NULL ) {
15770             }
15771                 return text;
15772         }
15773         else {
15774             /* We expect something like: [+|-]nnn.nn/dd */
15775             int score_lo = 0;
15776
15777             if(*text != '{') return text; // [HGM] braces: must be normal comment
15778
15779             sep = strchr( text, '/' );
15780             if( sep == NULL || sep < (text+4) ) {
15781                 return text;
15782             }
15783
15784             p = text;
15785             if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
15786             if(p[1] == '(') { // comment starts with PV
15787                p = strchr(p, ')'); // locate end of PV
15788                if(p == NULL || sep < p+5) return text;
15789                // at this point we have something like "{(.*) +0.23/6 ..."
15790                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
15791                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
15792                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
15793             }
15794             time = -1; sec = -1; deci = -1;
15795             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
15796                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
15797                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
15798                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
15799                 return text;
15800             }
15801
15802             if( score_lo < 0 || score_lo >= 100 ) {
15803                 return text;
15804             }
15805
15806             if(sec >= 0) time = 600*time + 10*sec; else
15807             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
15808
15809             score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
15810
15811             /* [HGM] PV time: now locate end of PV info */
15812             while( *++sep >= '0' && *sep <= '9'); // strip depth
15813             if(time >= 0)
15814             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
15815             if(sec >= 0)
15816             while( *++sep >= '0' && *sep <= '9'); // strip seconds
15817             if(deci >= 0)
15818             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
15819             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
15820         }
15821
15822         if( depth <= 0 ) {
15823             return text;
15824         }
15825
15826         if( time < 0 ) {
15827             time = -1;
15828         }
15829
15830         pvInfoList[index-1].depth = depth;
15831         pvInfoList[index-1].score = score;
15832         pvInfoList[index-1].time  = 10*time; // centi-sec
15833         if(*sep == '}') *sep = 0; else *--sep = '{';
15834         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
15835     }
15836     return sep;
15837 }
15838
15839 void
15840 SendToProgram (char *message, ChessProgramState *cps)
15841 {
15842     int count, outCount, error;
15843     char buf[MSG_SIZ];
15844
15845     if (cps->pr == NoProc) return;
15846     Attention(cps);
15847
15848     if (appData.debugMode) {
15849         TimeMark now;
15850         GetTimeMark(&now);
15851         fprintf(debugFP, "%ld >%-6s: %s",
15852                 SubtractTimeMarks(&now, &programStartTime),
15853                 cps->which, message);
15854         if(serverFP)
15855             fprintf(serverFP, "%ld >%-6s: %s",
15856                 SubtractTimeMarks(&now, &programStartTime),
15857                 cps->which, message), fflush(serverFP);
15858     }
15859
15860     count = strlen(message);
15861     outCount = OutputToProcess(cps->pr, message, count, &error);
15862     if (outCount < count && !exiting
15863                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
15864       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15865       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
15866         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15867             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15868                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15869                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15870                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15871             } else {
15872                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15873                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15874                 gameInfo.result = res;
15875             }
15876             gameInfo.resultDetails = StrSave(buf);
15877         }
15878         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15879         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15880     }
15881 }
15882
15883 void
15884 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
15885 {
15886     char *end_str;
15887     char buf[MSG_SIZ];
15888     ChessProgramState *cps = (ChessProgramState *)closure;
15889
15890     if (isr != cps->isr) return; /* Killed intentionally */
15891     if (count <= 0) {
15892         if (count == 0) {
15893             RemoveInputSource(cps->isr);
15894             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
15895                     _(cps->which), cps->program);
15896             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
15897             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15898                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15899                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15900                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15901                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15902                 } else {
15903                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15904                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15905                     gameInfo.result = res;
15906                 }
15907                 gameInfo.resultDetails = StrSave(buf);
15908             }
15909             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15910             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
15911         } else {
15912             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
15913                     _(cps->which), cps->program);
15914             RemoveInputSource(cps->isr);
15915
15916             /* [AS] Program is misbehaving badly... kill it */
15917             if( count == -2 ) {
15918                 DestroyChildProcess( cps->pr, 9 );
15919                 cps->pr = NoProc;
15920             }
15921
15922             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15923         }
15924         return;
15925     }
15926
15927     if ((end_str = strchr(message, '\r')) != NULL)
15928       *end_str = NULLCHAR;
15929     if ((end_str = strchr(message, '\n')) != NULL)
15930       *end_str = NULLCHAR;
15931
15932     if (appData.debugMode) {
15933         TimeMark now; int print = 1;
15934         char *quote = ""; char c; int i;
15935
15936         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
15937                 char start = message[0];
15938                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
15939                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
15940                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
15941                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
15942                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
15943                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
15944                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
15945                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
15946                    sscanf(message, "hint: %c", &c)!=1 &&
15947                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
15948                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
15949                     print = (appData.engineComments >= 2);
15950                 }
15951                 message[0] = start; // restore original message
15952         }
15953         if(print) {
15954                 GetTimeMark(&now);
15955                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
15956                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15957                         quote,
15958                         message);
15959                 if(serverFP)
15960                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
15961                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15962                         quote,
15963                         message), fflush(serverFP);
15964         }
15965     }
15966
15967     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
15968     if (appData.icsEngineAnalyze) {
15969         if (strstr(message, "whisper") != NULL ||
15970              strstr(message, "kibitz") != NULL ||
15971             strstr(message, "tellics") != NULL) return;
15972     }
15973
15974     HandleMachineMove(message, cps);
15975 }
15976
15977
15978 void
15979 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
15980 {
15981     char buf[MSG_SIZ];
15982     int seconds;
15983
15984     if( timeControl_2 > 0 ) {
15985         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
15986             tc = timeControl_2;
15987         }
15988     }
15989     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
15990     inc /= cps->timeOdds;
15991     st  /= cps->timeOdds;
15992
15993     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
15994
15995     if (st > 0) {
15996       /* Set exact time per move, normally using st command */
15997       if (cps->stKludge) {
15998         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
15999         seconds = st % 60;
16000         if (seconds == 0) {
16001           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
16002         } else {
16003           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
16004         }
16005       } else {
16006         snprintf(buf, MSG_SIZ, "st %d\n", st);
16007       }
16008     } else {
16009       /* Set conventional or incremental time control, using level command */
16010       if (seconds == 0) {
16011         /* Note old gnuchess bug -- minutes:seconds used to not work.
16012            Fixed in later versions, but still avoid :seconds
16013            when seconds is 0. */
16014         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
16015       } else {
16016         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
16017                  seconds, inc/1000.);
16018       }
16019     }
16020     SendToProgram(buf, cps);
16021
16022     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
16023     /* Orthogonally, limit search to given depth */
16024     if (sd > 0) {
16025       if (cps->sdKludge) {
16026         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
16027       } else {
16028         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
16029       }
16030       SendToProgram(buf, cps);
16031     }
16032
16033     if(cps->nps >= 0) { /* [HGM] nps */
16034         if(cps->supportsNPS == FALSE)
16035           cps->nps = -1; // don't use if engine explicitly says not supported!
16036         else {
16037           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
16038           SendToProgram(buf, cps);
16039         }
16040     }
16041 }
16042
16043 ChessProgramState *
16044 WhitePlayer ()
16045 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
16046 {
16047     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
16048        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
16049         return &second;
16050     return &first;
16051 }
16052
16053 void
16054 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
16055 {
16056     char message[MSG_SIZ];
16057     long time, otime;
16058
16059     /* Note: this routine must be called when the clocks are stopped
16060        or when they have *just* been set or switched; otherwise
16061        it will be off by the time since the current tick started.
16062     */
16063     if (machineWhite) {
16064         time = whiteTimeRemaining / 10;
16065         otime = blackTimeRemaining / 10;
16066     } else {
16067         time = blackTimeRemaining / 10;
16068         otime = whiteTimeRemaining / 10;
16069     }
16070     /* [HGM] translate opponent's time by time-odds factor */
16071     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
16072
16073     if (time <= 0) time = 1;
16074     if (otime <= 0) otime = 1;
16075
16076     snprintf(message, MSG_SIZ, "time %ld\n", time);
16077     SendToProgram(message, cps);
16078
16079     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
16080     SendToProgram(message, cps);
16081 }
16082
16083 char *
16084 EngineDefinedVariant (ChessProgramState *cps, int n)
16085 {   // return name of n-th unknown variant that engine supports
16086     static char buf[MSG_SIZ];
16087     char *p, *s = cps->variants;
16088     if(!s) return NULL;
16089     do { // parse string from variants feature
16090       VariantClass v;
16091         p = strchr(s, ',');
16092         if(p) *p = NULLCHAR;
16093       v = StringToVariant(s);
16094       if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
16095         if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
16096             if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
16097         }
16098         if(p) *p++ = ',';
16099         if(n < 0) return buf;
16100     } while(s = p);
16101     return NULL;
16102 }
16103
16104 int
16105 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16106 {
16107   char buf[MSG_SIZ];
16108   int len = strlen(name);
16109   int val;
16110
16111   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16112     (*p) += len + 1;
16113     sscanf(*p, "%d", &val);
16114     *loc = (val != 0);
16115     while (**p && **p != ' ')
16116       (*p)++;
16117     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16118     SendToProgram(buf, cps);
16119     return TRUE;
16120   }
16121   return FALSE;
16122 }
16123
16124 int
16125 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16126 {
16127   char buf[MSG_SIZ];
16128   int len = strlen(name);
16129   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16130     (*p) += len + 1;
16131     sscanf(*p, "%d", loc);
16132     while (**p && **p != ' ') (*p)++;
16133     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16134     SendToProgram(buf, cps);
16135     return TRUE;
16136   }
16137   return FALSE;
16138 }
16139
16140 int
16141 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
16142 {
16143   char buf[MSG_SIZ];
16144   int len = strlen(name);
16145   if (strncmp((*p), name, len) == 0
16146       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
16147     (*p) += len + 2;
16148     ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
16149     sscanf(*p, "%[^\"]", *loc);
16150     while (**p && **p != '\"') (*p)++;
16151     if (**p == '\"') (*p)++;
16152     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16153     SendToProgram(buf, cps);
16154     return TRUE;
16155   }
16156   return FALSE;
16157 }
16158
16159 int
16160 ParseOption (Option *opt, ChessProgramState *cps)
16161 // [HGM] options: process the string that defines an engine option, and determine
16162 // name, type, default value, and allowed value range
16163 {
16164         char *p, *q, buf[MSG_SIZ];
16165         int n, min = (-1)<<31, max = 1<<31, def;
16166
16167         if(p = strstr(opt->name, " -spin ")) {
16168             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16169             if(max < min) max = min; // enforce consistency
16170             if(def < min) def = min;
16171             if(def > max) def = max;
16172             opt->value = def;
16173             opt->min = min;
16174             opt->max = max;
16175             opt->type = Spin;
16176         } else if((p = strstr(opt->name, " -slider "))) {
16177             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
16178             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16179             if(max < min) max = min; // enforce consistency
16180             if(def < min) def = min;
16181             if(def > max) def = max;
16182             opt->value = def;
16183             opt->min = min;
16184             opt->max = max;
16185             opt->type = Spin; // Slider;
16186         } else if((p = strstr(opt->name, " -string "))) {
16187             opt->textValue = p+9;
16188             opt->type = TextBox;
16189         } else if((p = strstr(opt->name, " -file "))) {
16190             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16191             opt->textValue = p+7;
16192             opt->type = FileName; // FileName;
16193         } else if((p = strstr(opt->name, " -path "))) {
16194             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16195             opt->textValue = p+7;
16196             opt->type = PathName; // PathName;
16197         } else if(p = strstr(opt->name, " -check ")) {
16198             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
16199             opt->value = (def != 0);
16200             opt->type = CheckBox;
16201         } else if(p = strstr(opt->name, " -combo ")) {
16202             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
16203             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
16204             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
16205             opt->value = n = 0;
16206             while(q = StrStr(q, " /// ")) {
16207                 n++; *q = 0;    // count choices, and null-terminate each of them
16208                 q += 5;
16209                 if(*q == '*') { // remember default, which is marked with * prefix
16210                     q++;
16211                     opt->value = n;
16212                 }
16213                 cps->comboList[cps->comboCnt++] = q;
16214             }
16215             cps->comboList[cps->comboCnt++] = NULL;
16216             opt->max = n + 1;
16217             opt->type = ComboBox;
16218         } else if(p = strstr(opt->name, " -button")) {
16219             opt->type = Button;
16220         } else if(p = strstr(opt->name, " -save")) {
16221             opt->type = SaveButton;
16222         } else return FALSE;
16223         *p = 0; // terminate option name
16224         // now look if the command-line options define a setting for this engine option.
16225         if(cps->optionSettings && cps->optionSettings[0])
16226             p = strstr(cps->optionSettings, opt->name); else p = NULL;
16227         if(p && (p == cps->optionSettings || p[-1] == ',')) {
16228           snprintf(buf, MSG_SIZ, "option %s", p);
16229                 if(p = strstr(buf, ",")) *p = 0;
16230                 if(q = strchr(buf, '=')) switch(opt->type) {
16231                     case ComboBox:
16232                         for(n=0; n<opt->max; n++)
16233                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
16234                         break;
16235                     case TextBox:
16236                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
16237                         break;
16238                     case Spin:
16239                     case CheckBox:
16240                         opt->value = atoi(q+1);
16241                     default:
16242                         break;
16243                 }
16244                 strcat(buf, "\n");
16245                 SendToProgram(buf, cps);
16246         }
16247         return TRUE;
16248 }
16249
16250 void
16251 FeatureDone (ChessProgramState *cps, int val)
16252 {
16253   DelayedEventCallback cb = GetDelayedEvent();
16254   if ((cb == InitBackEnd3 && cps == &first) ||
16255       (cb == SettingsMenuIfReady && cps == &second) ||
16256       (cb == LoadEngine) ||
16257       (cb == TwoMachinesEventIfReady)) {
16258     CancelDelayedEvent();
16259     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
16260   }
16261   cps->initDone = val;
16262   if(val) cps->reload = FALSE;
16263 }
16264
16265 /* Parse feature command from engine */
16266 void
16267 ParseFeatures (char *args, ChessProgramState *cps)
16268 {
16269   char *p = args;
16270   char *q = NULL;
16271   int val;
16272   char buf[MSG_SIZ];
16273
16274   for (;;) {
16275     while (*p == ' ') p++;
16276     if (*p == NULLCHAR) return;
16277
16278     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
16279     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
16280     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
16281     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
16282     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
16283     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
16284     if (BoolFeature(&p, "reuse", &val, cps)) {
16285       /* Engine can disable reuse, but can't enable it if user said no */
16286       if (!val) cps->reuse = FALSE;
16287       continue;
16288     }
16289     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
16290     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
16291       if (gameMode == TwoMachinesPlay) {
16292         DisplayTwoMachinesTitle();
16293       } else {
16294         DisplayTitle("");
16295       }
16296       continue;
16297     }
16298     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
16299     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
16300     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
16301     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
16302     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
16303     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
16304     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
16305     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
16306     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
16307     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
16308     if (IntFeature(&p, "done", &val, cps)) {
16309       FeatureDone(cps, val);
16310       continue;
16311     }
16312     /* Added by Tord: */
16313     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
16314     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
16315     /* End of additions by Tord */
16316
16317     /* [HGM] added features: */
16318     if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
16319     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
16320     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
16321     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
16322     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
16323     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
16324     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
16325     if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
16326         if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
16327         FREE(cps->option[cps->nrOptions].name);
16328         cps->option[cps->nrOptions].name = q; q = NULL;
16329         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
16330           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
16331             SendToProgram(buf, cps);
16332             continue;
16333         }
16334         if(cps->nrOptions >= MAX_OPTIONS) {
16335             cps->nrOptions--;
16336             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
16337             DisplayError(buf, 0);
16338         }
16339         continue;
16340     }
16341     /* End of additions by HGM */
16342
16343     /* unknown feature: complain and skip */
16344     q = p;
16345     while (*q && *q != '=') q++;
16346     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
16347     SendToProgram(buf, cps);
16348     p = q;
16349     if (*p == '=') {
16350       p++;
16351       if (*p == '\"') {
16352         p++;
16353         while (*p && *p != '\"') p++;
16354         if (*p == '\"') p++;
16355       } else {
16356         while (*p && *p != ' ') p++;
16357       }
16358     }
16359   }
16360
16361 }
16362
16363 void
16364 PeriodicUpdatesEvent (int newState)
16365 {
16366     if (newState == appData.periodicUpdates)
16367       return;
16368
16369     appData.periodicUpdates=newState;
16370
16371     /* Display type changes, so update it now */
16372 //    DisplayAnalysis();
16373
16374     /* Get the ball rolling again... */
16375     if (newState) {
16376         AnalysisPeriodicEvent(1);
16377         StartAnalysisClock();
16378     }
16379 }
16380
16381 void
16382 PonderNextMoveEvent (int newState)
16383 {
16384     if (newState == appData.ponderNextMove) return;
16385     if (gameMode == EditPosition) EditPositionDone(TRUE);
16386     if (newState) {
16387         SendToProgram("hard\n", &first);
16388         if (gameMode == TwoMachinesPlay) {
16389             SendToProgram("hard\n", &second);
16390         }
16391     } else {
16392         SendToProgram("easy\n", &first);
16393         thinkOutput[0] = NULLCHAR;
16394         if (gameMode == TwoMachinesPlay) {
16395             SendToProgram("easy\n", &second);
16396         }
16397     }
16398     appData.ponderNextMove = newState;
16399 }
16400
16401 void
16402 NewSettingEvent (int option, int *feature, char *command, int value)
16403 {
16404     char buf[MSG_SIZ];
16405
16406     if (gameMode == EditPosition) EditPositionDone(TRUE);
16407     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
16408     if(feature == NULL || *feature) SendToProgram(buf, &first);
16409     if (gameMode == TwoMachinesPlay) {
16410         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
16411     }
16412 }
16413
16414 void
16415 ShowThinkingEvent ()
16416 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
16417 {
16418     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
16419     int newState = appData.showThinking
16420         // [HGM] thinking: other features now need thinking output as well
16421         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
16422
16423     if (oldState == newState) return;
16424     oldState = newState;
16425     if (gameMode == EditPosition) EditPositionDone(TRUE);
16426     if (oldState) {
16427         SendToProgram("post\n", &first);
16428         if (gameMode == TwoMachinesPlay) {
16429             SendToProgram("post\n", &second);
16430         }
16431     } else {
16432         SendToProgram("nopost\n", &first);
16433         thinkOutput[0] = NULLCHAR;
16434         if (gameMode == TwoMachinesPlay) {
16435             SendToProgram("nopost\n", &second);
16436         }
16437     }
16438 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
16439 }
16440
16441 void
16442 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
16443 {
16444   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
16445   if (pr == NoProc) return;
16446   AskQuestion(title, question, replyPrefix, pr);
16447 }
16448
16449 void
16450 TypeInEvent (char firstChar)
16451 {
16452     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
16453         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
16454         gameMode == AnalyzeMode || gameMode == EditGame ||
16455         gameMode == EditPosition || gameMode == IcsExamining ||
16456         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
16457         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
16458                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
16459                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
16460         gameMode == Training) PopUpMoveDialog(firstChar);
16461 }
16462
16463 void
16464 TypeInDoneEvent (char *move)
16465 {
16466         Board board;
16467         int n, fromX, fromY, toX, toY;
16468         char promoChar;
16469         ChessMove moveType;
16470
16471         // [HGM] FENedit
16472         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
16473                 EditPositionPasteFEN(move);
16474                 return;
16475         }
16476         // [HGM] movenum: allow move number to be typed in any mode
16477         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
16478           ToNrEvent(2*n-1);
16479           return;
16480         }
16481         // undocumented kludge: allow command-line option to be typed in!
16482         // (potentially fatal, and does not implement the effect of the option.)
16483         // should only be used for options that are values on which future decisions will be made,
16484         // and definitely not on options that would be used during initialization.
16485         if(strstr(move, "!!! -") == move) {
16486             ParseArgsFromString(move+4);
16487             return;
16488         }
16489
16490       if (gameMode != EditGame && currentMove != forwardMostMove &&
16491         gameMode != Training) {
16492         DisplayMoveError(_("Displayed move is not current"));
16493       } else {
16494         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16495           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
16496         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
16497         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16498           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
16499           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
16500         } else {
16501           DisplayMoveError(_("Could not parse move"));
16502         }
16503       }
16504 }
16505
16506 void
16507 DisplayMove (int moveNumber)
16508 {
16509     char message[MSG_SIZ];
16510     char res[MSG_SIZ];
16511     char cpThinkOutput[MSG_SIZ];
16512
16513     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
16514
16515     if (moveNumber == forwardMostMove - 1 ||
16516         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16517
16518         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
16519
16520         if (strchr(cpThinkOutput, '\n')) {
16521             *strchr(cpThinkOutput, '\n') = NULLCHAR;
16522         }
16523     } else {
16524         *cpThinkOutput = NULLCHAR;
16525     }
16526
16527     /* [AS] Hide thinking from human user */
16528     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
16529         *cpThinkOutput = NULLCHAR;
16530         if( thinkOutput[0] != NULLCHAR ) {
16531             int i;
16532
16533             for( i=0; i<=hiddenThinkOutputState; i++ ) {
16534                 cpThinkOutput[i] = '.';
16535             }
16536             cpThinkOutput[i] = NULLCHAR;
16537             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
16538         }
16539     }
16540
16541     if (moveNumber == forwardMostMove - 1 &&
16542         gameInfo.resultDetails != NULL) {
16543         if (gameInfo.resultDetails[0] == NULLCHAR) {
16544           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
16545         } else {
16546           snprintf(res, MSG_SIZ, " {%s} %s",
16547                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
16548         }
16549     } else {
16550         res[0] = NULLCHAR;
16551     }
16552
16553     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16554         DisplayMessage(res, cpThinkOutput);
16555     } else {
16556       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
16557                 WhiteOnMove(moveNumber) ? " " : ".. ",
16558                 parseList[moveNumber], res);
16559         DisplayMessage(message, cpThinkOutput);
16560     }
16561 }
16562
16563 void
16564 DisplayComment (int moveNumber, char *text)
16565 {
16566     char title[MSG_SIZ];
16567
16568     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16569       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
16570     } else {
16571       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
16572               WhiteOnMove(moveNumber) ? " " : ".. ",
16573               parseList[moveNumber]);
16574     }
16575     if (text != NULL && (appData.autoDisplayComment || commentUp))
16576         CommentPopUp(title, text);
16577 }
16578
16579 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
16580  * might be busy thinking or pondering.  It can be omitted if your
16581  * gnuchess is configured to stop thinking immediately on any user
16582  * input.  However, that gnuchess feature depends on the FIONREAD
16583  * ioctl, which does not work properly on some flavors of Unix.
16584  */
16585 void
16586 Attention (ChessProgramState *cps)
16587 {
16588 #if ATTENTION
16589     if (!cps->useSigint) return;
16590     if (appData.noChessProgram || (cps->pr == NoProc)) return;
16591     switch (gameMode) {
16592       case MachinePlaysWhite:
16593       case MachinePlaysBlack:
16594       case TwoMachinesPlay:
16595       case IcsPlayingWhite:
16596       case IcsPlayingBlack:
16597       case AnalyzeMode:
16598       case AnalyzeFile:
16599         /* Skip if we know it isn't thinking */
16600         if (!cps->maybeThinking) return;
16601         if (appData.debugMode)
16602           fprintf(debugFP, "Interrupting %s\n", cps->which);
16603         InterruptChildProcess(cps->pr);
16604         cps->maybeThinking = FALSE;
16605         break;
16606       default:
16607         break;
16608     }
16609 #endif /*ATTENTION*/
16610 }
16611
16612 int
16613 CheckFlags ()
16614 {
16615     if (whiteTimeRemaining <= 0) {
16616         if (!whiteFlag) {
16617             whiteFlag = TRUE;
16618             if (appData.icsActive) {
16619                 if (appData.autoCallFlag &&
16620                     gameMode == IcsPlayingBlack && !blackFlag) {
16621                   SendToICS(ics_prefix);
16622                   SendToICS("flag\n");
16623                 }
16624             } else {
16625                 if (blackFlag) {
16626                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16627                 } else {
16628                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
16629                     if (appData.autoCallFlag) {
16630                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
16631                         return TRUE;
16632                     }
16633                 }
16634             }
16635         }
16636     }
16637     if (blackTimeRemaining <= 0) {
16638         if (!blackFlag) {
16639             blackFlag = TRUE;
16640             if (appData.icsActive) {
16641                 if (appData.autoCallFlag &&
16642                     gameMode == IcsPlayingWhite && !whiteFlag) {
16643                   SendToICS(ics_prefix);
16644                   SendToICS("flag\n");
16645                 }
16646             } else {
16647                 if (whiteFlag) {
16648                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16649                 } else {
16650                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
16651                     if (appData.autoCallFlag) {
16652                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
16653                         return TRUE;
16654                     }
16655                 }
16656             }
16657         }
16658     }
16659     return FALSE;
16660 }
16661
16662 void
16663 CheckTimeControl ()
16664 {
16665     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
16666         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
16667
16668     /*
16669      * add time to clocks when time control is achieved ([HGM] now also used for increment)
16670      */
16671     if ( !WhiteOnMove(forwardMostMove) ) {
16672         /* White made time control */
16673         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
16674         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
16675         /* [HGM] time odds: correct new time quota for time odds! */
16676                                             / WhitePlayer()->timeOdds;
16677         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
16678     } else {
16679         lastBlack -= blackTimeRemaining;
16680         /* Black made time control */
16681         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
16682                                             / WhitePlayer()->other->timeOdds;
16683         lastWhite = whiteTimeRemaining;
16684     }
16685 }
16686
16687 void
16688 DisplayBothClocks ()
16689 {
16690     int wom = gameMode == EditPosition ?
16691       !blackPlaysFirst : WhiteOnMove(currentMove);
16692     DisplayWhiteClock(whiteTimeRemaining, wom);
16693     DisplayBlackClock(blackTimeRemaining, !wom);
16694 }
16695
16696
16697 /* Timekeeping seems to be a portability nightmare.  I think everyone
16698    has ftime(), but I'm really not sure, so I'm including some ifdefs
16699    to use other calls if you don't.  Clocks will be less accurate if
16700    you have neither ftime nor gettimeofday.
16701 */
16702
16703 /* VS 2008 requires the #include outside of the function */
16704 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
16705 #include <sys/timeb.h>
16706 #endif
16707
16708 /* Get the current time as a TimeMark */
16709 void
16710 GetTimeMark (TimeMark *tm)
16711 {
16712 #if HAVE_GETTIMEOFDAY
16713
16714     struct timeval timeVal;
16715     struct timezone timeZone;
16716
16717     gettimeofday(&timeVal, &timeZone);
16718     tm->sec = (long) timeVal.tv_sec;
16719     tm->ms = (int) (timeVal.tv_usec / 1000L);
16720
16721 #else /*!HAVE_GETTIMEOFDAY*/
16722 #if HAVE_FTIME
16723
16724 // include <sys/timeb.h> / moved to just above start of function
16725     struct timeb timeB;
16726
16727     ftime(&timeB);
16728     tm->sec = (long) timeB.time;
16729     tm->ms = (int) timeB.millitm;
16730
16731 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
16732     tm->sec = (long) time(NULL);
16733     tm->ms = 0;
16734 #endif
16735 #endif
16736 }
16737
16738 /* Return the difference in milliseconds between two
16739    time marks.  We assume the difference will fit in a long!
16740 */
16741 long
16742 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
16743 {
16744     return 1000L*(tm2->sec - tm1->sec) +
16745            (long) (tm2->ms - tm1->ms);
16746 }
16747
16748
16749 /*
16750  * Code to manage the game clocks.
16751  *
16752  * In tournament play, black starts the clock and then white makes a move.
16753  * We give the human user a slight advantage if he is playing white---the
16754  * clocks don't run until he makes his first move, so it takes zero time.
16755  * Also, we don't account for network lag, so we could get out of sync
16756  * with GNU Chess's clock -- but then, referees are always right.
16757  */
16758
16759 static TimeMark tickStartTM;
16760 static long intendedTickLength;
16761
16762 long
16763 NextTickLength (long timeRemaining)
16764 {
16765     long nominalTickLength, nextTickLength;
16766
16767     if (timeRemaining > 0L && timeRemaining <= 10000L)
16768       nominalTickLength = 100L;
16769     else
16770       nominalTickLength = 1000L;
16771     nextTickLength = timeRemaining % nominalTickLength;
16772     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
16773
16774     return nextTickLength;
16775 }
16776
16777 /* Adjust clock one minute up or down */
16778 void
16779 AdjustClock (Boolean which, int dir)
16780 {
16781     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
16782     if(which) blackTimeRemaining += 60000*dir;
16783     else      whiteTimeRemaining += 60000*dir;
16784     DisplayBothClocks();
16785     adjustedClock = TRUE;
16786 }
16787
16788 /* Stop clocks and reset to a fresh time control */
16789 void
16790 ResetClocks ()
16791 {
16792     (void) StopClockTimer();
16793     if (appData.icsActive) {
16794         whiteTimeRemaining = blackTimeRemaining = 0;
16795     } else if (searchTime) {
16796         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16797         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16798     } else { /* [HGM] correct new time quote for time odds */
16799         whiteTC = blackTC = fullTimeControlString;
16800         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
16801         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
16802     }
16803     if (whiteFlag || blackFlag) {
16804         DisplayTitle("");
16805         whiteFlag = blackFlag = FALSE;
16806     }
16807     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
16808     DisplayBothClocks();
16809     adjustedClock = FALSE;
16810 }
16811
16812 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
16813
16814 /* Decrement running clock by amount of time that has passed */
16815 void
16816 DecrementClocks ()
16817 {
16818     long timeRemaining;
16819     long lastTickLength, fudge;
16820     TimeMark now;
16821
16822     if (!appData.clockMode) return;
16823     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
16824
16825     GetTimeMark(&now);
16826
16827     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16828
16829     /* Fudge if we woke up a little too soon */
16830     fudge = intendedTickLength - lastTickLength;
16831     if (fudge < 0 || fudge > FUDGE) fudge = 0;
16832
16833     if (WhiteOnMove(forwardMostMove)) {
16834         if(whiteNPS >= 0) lastTickLength = 0;
16835         timeRemaining = whiteTimeRemaining -= lastTickLength;
16836         if(timeRemaining < 0 && !appData.icsActive) {
16837             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
16838             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
16839                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
16840                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
16841             }
16842         }
16843         DisplayWhiteClock(whiteTimeRemaining - fudge,
16844                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16845     } else {
16846         if(blackNPS >= 0) lastTickLength = 0;
16847         timeRemaining = blackTimeRemaining -= lastTickLength;
16848         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
16849             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
16850             if(suddenDeath) {
16851                 blackStartMove = forwardMostMove;
16852                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
16853             }
16854         }
16855         DisplayBlackClock(blackTimeRemaining - fudge,
16856                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16857     }
16858     if (CheckFlags()) return;
16859
16860     if(twoBoards) { // count down secondary board's clocks as well
16861         activePartnerTime -= lastTickLength;
16862         partnerUp = 1;
16863         if(activePartner == 'W')
16864             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
16865         else
16866             DisplayBlackClock(activePartnerTime, TRUE);
16867         partnerUp = 0;
16868     }
16869
16870     tickStartTM = now;
16871     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
16872     StartClockTimer(intendedTickLength);
16873
16874     /* if the time remaining has fallen below the alarm threshold, sound the
16875      * alarm. if the alarm has sounded and (due to a takeback or time control
16876      * with increment) the time remaining has increased to a level above the
16877      * threshold, reset the alarm so it can sound again.
16878      */
16879
16880     if (appData.icsActive && appData.icsAlarm) {
16881
16882         /* make sure we are dealing with the user's clock */
16883         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
16884                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
16885            )) return;
16886
16887         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
16888             alarmSounded = FALSE;
16889         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
16890             PlayAlarmSound();
16891             alarmSounded = TRUE;
16892         }
16893     }
16894 }
16895
16896
16897 /* A player has just moved, so stop the previously running
16898    clock and (if in clock mode) start the other one.
16899    We redisplay both clocks in case we're in ICS mode, because
16900    ICS gives us an update to both clocks after every move.
16901    Note that this routine is called *after* forwardMostMove
16902    is updated, so the last fractional tick must be subtracted
16903    from the color that is *not* on move now.
16904 */
16905 void
16906 SwitchClocks (int newMoveNr)
16907 {
16908     long lastTickLength;
16909     TimeMark now;
16910     int flagged = FALSE;
16911
16912     GetTimeMark(&now);
16913
16914     if (StopClockTimer() && appData.clockMode) {
16915         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16916         if (!WhiteOnMove(forwardMostMove)) {
16917             if(blackNPS >= 0) lastTickLength = 0;
16918             blackTimeRemaining -= lastTickLength;
16919            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16920 //         if(pvInfoList[forwardMostMove].time == -1)
16921                  pvInfoList[forwardMostMove].time =               // use GUI time
16922                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
16923         } else {
16924            if(whiteNPS >= 0) lastTickLength = 0;
16925            whiteTimeRemaining -= lastTickLength;
16926            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16927 //         if(pvInfoList[forwardMostMove].time == -1)
16928                  pvInfoList[forwardMostMove].time =
16929                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
16930         }
16931         flagged = CheckFlags();
16932     }
16933     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
16934     CheckTimeControl();
16935
16936     if (flagged || !appData.clockMode) return;
16937
16938     switch (gameMode) {
16939       case MachinePlaysBlack:
16940       case MachinePlaysWhite:
16941       case BeginningOfGame:
16942         if (pausing) return;
16943         break;
16944
16945       case EditGame:
16946       case PlayFromGameFile:
16947       case IcsExamining:
16948         return;
16949
16950       default:
16951         break;
16952     }
16953
16954     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
16955         if(WhiteOnMove(forwardMostMove))
16956              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16957         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16958     }
16959
16960     tickStartTM = now;
16961     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16962       whiteTimeRemaining : blackTimeRemaining);
16963     StartClockTimer(intendedTickLength);
16964 }
16965
16966
16967 /* Stop both clocks */
16968 void
16969 StopClocks ()
16970 {
16971     long lastTickLength;
16972     TimeMark now;
16973
16974     if (!StopClockTimer()) return;
16975     if (!appData.clockMode) return;
16976
16977     GetTimeMark(&now);
16978
16979     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16980     if (WhiteOnMove(forwardMostMove)) {
16981         if(whiteNPS >= 0) lastTickLength = 0;
16982         whiteTimeRemaining -= lastTickLength;
16983         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
16984     } else {
16985         if(blackNPS >= 0) lastTickLength = 0;
16986         blackTimeRemaining -= lastTickLength;
16987         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
16988     }
16989     CheckFlags();
16990 }
16991
16992 /* Start clock of player on move.  Time may have been reset, so
16993    if clock is already running, stop and restart it. */
16994 void
16995 StartClocks ()
16996 {
16997     (void) StopClockTimer(); /* in case it was running already */
16998     DisplayBothClocks();
16999     if (CheckFlags()) return;
17000
17001     if (!appData.clockMode) return;
17002     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
17003
17004     GetTimeMark(&tickStartTM);
17005     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17006       whiteTimeRemaining : blackTimeRemaining);
17007
17008    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
17009     whiteNPS = blackNPS = -1;
17010     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
17011        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
17012         whiteNPS = first.nps;
17013     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
17014        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
17015         blackNPS = first.nps;
17016     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
17017         whiteNPS = second.nps;
17018     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
17019         blackNPS = second.nps;
17020     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
17021
17022     StartClockTimer(intendedTickLength);
17023 }
17024
17025 char *
17026 TimeString (long ms)
17027 {
17028     long second, minute, hour, day;
17029     char *sign = "";
17030     static char buf[32];
17031
17032     if (ms > 0 && ms <= 9900) {
17033       /* convert milliseconds to tenths, rounding up */
17034       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
17035
17036       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
17037       return buf;
17038     }
17039
17040     /* convert milliseconds to seconds, rounding up */
17041     /* use floating point to avoid strangeness of integer division
17042        with negative dividends on many machines */
17043     second = (long) floor(((double) (ms + 999L)) / 1000.0);
17044
17045     if (second < 0) {
17046         sign = "-";
17047         second = -second;
17048     }
17049
17050     day = second / (60 * 60 * 24);
17051     second = second % (60 * 60 * 24);
17052     hour = second / (60 * 60);
17053     second = second % (60 * 60);
17054     minute = second / 60;
17055     second = second % 60;
17056
17057     if (day > 0)
17058       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
17059               sign, day, hour, minute, second);
17060     else if (hour > 0)
17061       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
17062     else
17063       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
17064
17065     return buf;
17066 }
17067
17068
17069 /*
17070  * This is necessary because some C libraries aren't ANSI C compliant yet.
17071  */
17072 char *
17073 StrStr (char *string, char *match)
17074 {
17075     int i, length;
17076
17077     length = strlen(match);
17078
17079     for (i = strlen(string) - length; i >= 0; i--, string++)
17080       if (!strncmp(match, string, length))
17081         return string;
17082
17083     return NULL;
17084 }
17085
17086 char *
17087 StrCaseStr (char *string, char *match)
17088 {
17089     int i, j, length;
17090
17091     length = strlen(match);
17092
17093     for (i = strlen(string) - length; i >= 0; i--, string++) {
17094         for (j = 0; j < length; j++) {
17095             if (ToLower(match[j]) != ToLower(string[j]))
17096               break;
17097         }
17098         if (j == length) return string;
17099     }
17100
17101     return NULL;
17102 }
17103
17104 #ifndef _amigados
17105 int
17106 StrCaseCmp (char *s1, char *s2)
17107 {
17108     char c1, c2;
17109
17110     for (;;) {
17111         c1 = ToLower(*s1++);
17112         c2 = ToLower(*s2++);
17113         if (c1 > c2) return 1;
17114         if (c1 < c2) return -1;
17115         if (c1 == NULLCHAR) return 0;
17116     }
17117 }
17118
17119
17120 int
17121 ToLower (int c)
17122 {
17123     return isupper(c) ? tolower(c) : c;
17124 }
17125
17126
17127 int
17128 ToUpper (int c)
17129 {
17130     return islower(c) ? toupper(c) : c;
17131 }
17132 #endif /* !_amigados    */
17133
17134 char *
17135 StrSave (char *s)
17136 {
17137   char *ret;
17138
17139   if ((ret = (char *) malloc(strlen(s) + 1)))
17140     {
17141       safeStrCpy(ret, s, strlen(s)+1);
17142     }
17143   return ret;
17144 }
17145
17146 char *
17147 StrSavePtr (char *s, char **savePtr)
17148 {
17149     if (*savePtr) {
17150         free(*savePtr);
17151     }
17152     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
17153       safeStrCpy(*savePtr, s, strlen(s)+1);
17154     }
17155     return(*savePtr);
17156 }
17157
17158 char *
17159 PGNDate ()
17160 {
17161     time_t clock;
17162     struct tm *tm;
17163     char buf[MSG_SIZ];
17164
17165     clock = time((time_t *)NULL);
17166     tm = localtime(&clock);
17167     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
17168             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
17169     return StrSave(buf);
17170 }
17171
17172
17173 char *
17174 PositionToFEN (int move, char *overrideCastling, int moveCounts)
17175 {
17176     int i, j, fromX, fromY, toX, toY;
17177     int whiteToPlay;
17178     char buf[MSG_SIZ];
17179     char *p, *q;
17180     int emptycount;
17181     ChessSquare piece;
17182
17183     whiteToPlay = (gameMode == EditPosition) ?
17184       !blackPlaysFirst : (move % 2 == 0);
17185     p = buf;
17186
17187     /* Piece placement data */
17188     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17189         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
17190         emptycount = 0;
17191         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
17192             if (boards[move][i][j] == EmptySquare) {
17193                 emptycount++;
17194             } else { ChessSquare piece = boards[move][i][j];
17195                 if (emptycount > 0) {
17196                     if(emptycount<10) /* [HGM] can be >= 10 */
17197                         *p++ = '0' + emptycount;
17198                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17199                     emptycount = 0;
17200                 }
17201                 if(PieceToChar(piece) == '+') {
17202                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
17203                     *p++ = '+';
17204                     piece = (ChessSquare)(DEMOTED piece);
17205                 }
17206                 *p++ = PieceToChar(piece);
17207                 if(p[-1] == '~') {
17208                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
17209                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
17210                     *p++ = '~';
17211                 }
17212             }
17213         }
17214         if (emptycount > 0) {
17215             if(emptycount<10) /* [HGM] can be >= 10 */
17216                 *p++ = '0' + emptycount;
17217             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17218             emptycount = 0;
17219         }
17220         *p++ = '/';
17221     }
17222     *(p - 1) = ' ';
17223
17224     /* [HGM] print Crazyhouse or Shogi holdings */
17225     if( gameInfo.holdingsWidth ) {
17226         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
17227         q = p;
17228         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
17229             piece = boards[move][i][BOARD_WIDTH-1];
17230             if( piece != EmptySquare )
17231               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
17232                   *p++ = PieceToChar(piece);
17233         }
17234         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
17235             piece = boards[move][BOARD_HEIGHT-i-1][0];
17236             if( piece != EmptySquare )
17237               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
17238                   *p++ = PieceToChar(piece);
17239         }
17240
17241         if( q == p ) *p++ = '-';
17242         *p++ = ']';
17243         *p++ = ' ';
17244     }
17245
17246     /* Active color */
17247     *p++ = whiteToPlay ? 'w' : 'b';
17248     *p++ = ' ';
17249
17250   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
17251     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
17252   } else {
17253   if(nrCastlingRights) {
17254      q = p;
17255      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
17256        /* [HGM] write directly from rights */
17257            if(boards[move][CASTLING][2] != NoRights &&
17258               boards[move][CASTLING][0] != NoRights   )
17259                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
17260            if(boards[move][CASTLING][2] != NoRights &&
17261               boards[move][CASTLING][1] != NoRights   )
17262                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
17263            if(boards[move][CASTLING][5] != NoRights &&
17264               boards[move][CASTLING][3] != NoRights   )
17265                 *p++ = boards[move][CASTLING][3] + AAA;
17266            if(boards[move][CASTLING][5] != NoRights &&
17267               boards[move][CASTLING][4] != NoRights   )
17268                 *p++ = boards[move][CASTLING][4] + AAA;
17269      } else {
17270
17271         /* [HGM] write true castling rights */
17272         if( nrCastlingRights == 6 ) {
17273             int q, k=0;
17274             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
17275                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
17276             q = (boards[move][CASTLING][1] == BOARD_LEFT &&
17277                  boards[move][CASTLING][2] != NoRights  );
17278             if(gameInfo.variant == VariantSChess) { // for S-Chess, indicate all vrgin backrank pieces
17279                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_RGHT]; // count white held pieces
17280                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17281                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
17282                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
17283             }
17284             if(q) *p++ = 'Q';
17285             k = 0;
17286             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
17287                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
17288             q = (boards[move][CASTLING][4] == BOARD_LEFT &&
17289                  boards[move][CASTLING][5] != NoRights  );
17290             if(gameInfo.variant == VariantSChess) {
17291                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_LEFT-1]; // count black held pieces
17292                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17293                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
17294                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17295             }
17296             if(q) *p++ = 'q';
17297         }
17298      }
17299      if (q == p) *p++ = '-'; /* No castling rights */
17300      *p++ = ' ';
17301   }
17302
17303   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17304      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17305      gameInfo.variant != VariantMakruk   && gameInfo.variant != VariantASEAN ) {
17306     /* En passant target square */
17307     if (move > backwardMostMove) {
17308         fromX = moveList[move - 1][0] - AAA;
17309         fromY = moveList[move - 1][1] - ONE;
17310         toX = moveList[move - 1][2] - AAA;
17311         toY = moveList[move - 1][3] - ONE;
17312         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
17313             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
17314             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
17315             fromX == toX) {
17316             /* 2-square pawn move just happened */
17317             *p++ = toX + AAA;
17318             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17319         } else {
17320             *p++ = '-';
17321         }
17322     } else if(move == backwardMostMove) {
17323         // [HGM] perhaps we should always do it like this, and forget the above?
17324         if((signed char)boards[move][EP_STATUS] >= 0) {
17325             *p++ = boards[move][EP_STATUS] + AAA;
17326             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17327         } else {
17328             *p++ = '-';
17329         }
17330     } else {
17331         *p++ = '-';
17332     }
17333     *p++ = ' ';
17334   }
17335   }
17336
17337     if(moveCounts)
17338     {   int i = 0, j=move;
17339
17340         /* [HGM] find reversible plies */
17341         if (appData.debugMode) { int k;
17342             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
17343             for(k=backwardMostMove; k<=forwardMostMove; k++)
17344                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
17345
17346         }
17347
17348         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
17349         if( j == backwardMostMove ) i += initialRulePlies;
17350         sprintf(p, "%d ", i);
17351         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
17352
17353         /* Fullmove number */
17354         sprintf(p, "%d", (move / 2) + 1);
17355     } else *--p = NULLCHAR;
17356
17357     return StrSave(buf);
17358 }
17359
17360 Boolean
17361 ParseFEN (Board board, int *blackPlaysFirst, char *fen)
17362 {
17363     int i, j;
17364     char *p, c;
17365     int emptycount, virgin[BOARD_FILES];
17366     ChessSquare piece;
17367
17368     p = fen;
17369
17370     /* [HGM] by default clear Crazyhouse holdings, if present */
17371     if(gameInfo.holdingsWidth) {
17372        for(i=0; i<BOARD_HEIGHT; i++) {
17373            board[i][0]             = EmptySquare; /* black holdings */
17374            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
17375            board[i][1]             = (ChessSquare) 0; /* black counts */
17376            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
17377        }
17378     }
17379
17380     /* Piece placement data */
17381     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17382         j = 0;
17383         for (;;) {
17384             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
17385                 if (*p == '/') p++;
17386                 emptycount = gameInfo.boardWidth - j;
17387                 while (emptycount--)
17388                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17389                 break;
17390 #if(BOARD_FILES >= 10)
17391             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
17392                 p++; emptycount=10;
17393                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17394                 while (emptycount--)
17395                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17396 #endif
17397             } else if (isdigit(*p)) {
17398                 emptycount = *p++ - '0';
17399                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
17400                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17401                 while (emptycount--)
17402                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17403             } else if (*p == '+' || isalpha(*p)) {
17404                 if (j >= gameInfo.boardWidth) return FALSE;
17405                 if(*p=='+') {
17406                     piece = CharToPiece(*++p);
17407                     if(piece == EmptySquare) return FALSE; /* unknown piece */
17408                     piece = (ChessSquare) (PROMOTED piece ); p++;
17409                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
17410                 } else piece = CharToPiece(*p++);
17411
17412                 if(piece==EmptySquare) return FALSE; /* unknown piece */
17413                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
17414                     piece = (ChessSquare) (PROMOTED piece);
17415                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
17416                     p++;
17417                 }
17418                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
17419             } else {
17420                 return FALSE;
17421             }
17422         }
17423     }
17424     while (*p == '/' || *p == ' ') p++;
17425
17426     /* [HGM] look for Crazyhouse holdings here */
17427     while(*p==' ') p++;
17428     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
17429         if(*p == '[') p++;
17430         if(*p == '-' ) p++; /* empty holdings */ else {
17431             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
17432             /* if we would allow FEN reading to set board size, we would   */
17433             /* have to add holdings and shift the board read so far here   */
17434             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
17435                 p++;
17436                 if((int) piece >= (int) BlackPawn ) {
17437                     i = (int)piece - (int)BlackPawn;
17438                     i = PieceToNumber((ChessSquare)i);
17439                     if( i >= gameInfo.holdingsSize ) return FALSE;
17440                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
17441                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
17442                 } else {
17443                     i = (int)piece - (int)WhitePawn;
17444                     i = PieceToNumber((ChessSquare)i);
17445                     if( i >= gameInfo.holdingsSize ) return FALSE;
17446                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
17447                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
17448                 }
17449             }
17450         }
17451         if(*p == ']') p++;
17452     }
17453
17454     while(*p == ' ') p++;
17455
17456     /* Active color */
17457     c = *p++;
17458     if(appData.colorNickNames) {
17459       if( c == appData.colorNickNames[0] ) c = 'w'; else
17460       if( c == appData.colorNickNames[1] ) c = 'b';
17461     }
17462     switch (c) {
17463       case 'w':
17464         *blackPlaysFirst = FALSE;
17465         break;
17466       case 'b':
17467         *blackPlaysFirst = TRUE;
17468         break;
17469       default:
17470         return FALSE;
17471     }
17472
17473     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
17474     /* return the extra info in global variiables             */
17475
17476     /* set defaults in case FEN is incomplete */
17477     board[EP_STATUS] = EP_UNKNOWN;
17478     for(i=0; i<nrCastlingRights; i++ ) {
17479         board[CASTLING][i] =
17480             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
17481     }   /* assume possible unless obviously impossible */
17482     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
17483     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
17484     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
17485                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
17486     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
17487     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
17488     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
17489                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
17490     FENrulePlies = 0;
17491
17492     while(*p==' ') p++;
17493     if(nrCastlingRights) {
17494       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
17495       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
17496           /* castling indicator present, so default becomes no castlings */
17497           for(i=0; i<nrCastlingRights; i++ ) {
17498                  board[CASTLING][i] = NoRights;
17499           }
17500       }
17501       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
17502              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess) &&
17503              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
17504              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
17505         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
17506
17507         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
17508             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
17509             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
17510         }
17511         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
17512             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
17513         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
17514                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
17515         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
17516                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
17517         switch(c) {
17518           case'K':
17519               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
17520               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
17521               board[CASTLING][2] = whiteKingFile;
17522               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
17523               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17524               break;
17525           case'Q':
17526               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
17527               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
17528               board[CASTLING][2] = whiteKingFile;
17529               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
17530               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17531               break;
17532           case'k':
17533               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
17534               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
17535               board[CASTLING][5] = blackKingFile;
17536               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
17537               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17538               break;
17539           case'q':
17540               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
17541               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
17542               board[CASTLING][5] = blackKingFile;
17543               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
17544               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17545           case '-':
17546               break;
17547           default: /* FRC castlings */
17548               if(c >= 'a') { /* black rights */
17549                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
17550                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17551                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
17552                   if(i == BOARD_RGHT) break;
17553                   board[CASTLING][5] = i;
17554                   c -= AAA;
17555                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
17556                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
17557                   if(c > i)
17558                       board[CASTLING][3] = c;
17559                   else
17560                       board[CASTLING][4] = c;
17561               } else { /* white rights */
17562                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
17563                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17564                     if(board[0][i] == WhiteKing) break;
17565                   if(i == BOARD_RGHT) break;
17566                   board[CASTLING][2] = i;
17567                   c -= AAA - 'a' + 'A';
17568                   if(board[0][c] >= WhiteKing) break;
17569                   if(c > i)
17570                       board[CASTLING][0] = c;
17571                   else
17572                       board[CASTLING][1] = c;
17573               }
17574         }
17575       }
17576       for(i=0; i<nrCastlingRights; i++)
17577         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
17578       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = virgin[i];
17579     if (appData.debugMode) {
17580         fprintf(debugFP, "FEN castling rights:");
17581         for(i=0; i<nrCastlingRights; i++)
17582         fprintf(debugFP, " %d", board[CASTLING][i]);
17583         fprintf(debugFP, "\n");
17584     }
17585
17586       while(*p==' ') p++;
17587     }
17588
17589     /* read e.p. field in games that know e.p. capture */
17590     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17591        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17592        gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
17593       if(*p=='-') {
17594         p++; board[EP_STATUS] = EP_NONE;
17595       } else {
17596          char c = *p++ - AAA;
17597
17598          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
17599          if(*p >= '0' && *p <='9') p++;
17600          board[EP_STATUS] = c;
17601       }
17602     }
17603
17604
17605     if(sscanf(p, "%d", &i) == 1) {
17606         FENrulePlies = i; /* 50-move ply counter */
17607         /* (The move number is still ignored)    */
17608     }
17609
17610     return TRUE;
17611 }
17612
17613 void
17614 EditPositionPasteFEN (char *fen)
17615 {
17616   if (fen != NULL) {
17617     Board initial_position;
17618
17619     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
17620       DisplayError(_("Bad FEN position in clipboard"), 0);
17621       return ;
17622     } else {
17623       int savedBlackPlaysFirst = blackPlaysFirst;
17624       EditPositionEvent();
17625       blackPlaysFirst = savedBlackPlaysFirst;
17626       CopyBoard(boards[0], initial_position);
17627       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
17628       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
17629       DisplayBothClocks();
17630       DrawPosition(FALSE, boards[currentMove]);
17631     }
17632   }
17633 }
17634
17635 static char cseq[12] = "\\   ";
17636
17637 Boolean
17638 set_cont_sequence (char *new_seq)
17639 {
17640     int len;
17641     Boolean ret;
17642
17643     // handle bad attempts to set the sequence
17644         if (!new_seq)
17645                 return 0; // acceptable error - no debug
17646
17647     len = strlen(new_seq);
17648     ret = (len > 0) && (len < sizeof(cseq));
17649     if (ret)
17650       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
17651     else if (appData.debugMode)
17652       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
17653     return ret;
17654 }
17655
17656 /*
17657     reformat a source message so words don't cross the width boundary.  internal
17658     newlines are not removed.  returns the wrapped size (no null character unless
17659     included in source message).  If dest is NULL, only calculate the size required
17660     for the dest buffer.  lp argument indicats line position upon entry, and it's
17661     passed back upon exit.
17662 */
17663 int
17664 wrap (char *dest, char *src, int count, int width, int *lp)
17665 {
17666     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
17667
17668     cseq_len = strlen(cseq);
17669     old_line = line = *lp;
17670     ansi = len = clen = 0;
17671
17672     for (i=0; i < count; i++)
17673     {
17674         if (src[i] == '\033')
17675             ansi = 1;
17676
17677         // if we hit the width, back up
17678         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
17679         {
17680             // store i & len in case the word is too long
17681             old_i = i, old_len = len;
17682
17683             // find the end of the last word
17684             while (i && src[i] != ' ' && src[i] != '\n')
17685             {
17686                 i--;
17687                 len--;
17688             }
17689
17690             // word too long?  restore i & len before splitting it
17691             if ((old_i-i+clen) >= width)
17692             {
17693                 i = old_i;
17694                 len = old_len;
17695             }
17696
17697             // extra space?
17698             if (i && src[i-1] == ' ')
17699                 len--;
17700
17701             if (src[i] != ' ' && src[i] != '\n')
17702             {
17703                 i--;
17704                 if (len)
17705                     len--;
17706             }
17707
17708             // now append the newline and continuation sequence
17709             if (dest)
17710                 dest[len] = '\n';
17711             len++;
17712             if (dest)
17713                 strncpy(dest+len, cseq, cseq_len);
17714             len += cseq_len;
17715             line = cseq_len;
17716             clen = cseq_len;
17717             continue;
17718         }
17719
17720         if (dest)
17721             dest[len] = src[i];
17722         len++;
17723         if (!ansi)
17724             line++;
17725         if (src[i] == '\n')
17726             line = 0;
17727         if (src[i] == 'm')
17728             ansi = 0;
17729     }
17730     if (dest && appData.debugMode)
17731     {
17732         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
17733             count, width, line, len, *lp);
17734         show_bytes(debugFP, src, count);
17735         fprintf(debugFP, "\ndest: ");
17736         show_bytes(debugFP, dest, len);
17737         fprintf(debugFP, "\n");
17738     }
17739     *lp = dest ? line : old_line;
17740
17741     return len;
17742 }
17743
17744 // [HGM] vari: routines for shelving variations
17745 Boolean modeRestore = FALSE;
17746
17747 void
17748 PushInner (int firstMove, int lastMove)
17749 {
17750         int i, j, nrMoves = lastMove - firstMove;
17751
17752         // push current tail of game on stack
17753         savedResult[storedGames] = gameInfo.result;
17754         savedDetails[storedGames] = gameInfo.resultDetails;
17755         gameInfo.resultDetails = NULL;
17756         savedFirst[storedGames] = firstMove;
17757         savedLast [storedGames] = lastMove;
17758         savedFramePtr[storedGames] = framePtr;
17759         framePtr -= nrMoves; // reserve space for the boards
17760         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
17761             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
17762             for(j=0; j<MOVE_LEN; j++)
17763                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
17764             for(j=0; j<2*MOVE_LEN; j++)
17765                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
17766             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
17767             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
17768             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
17769             pvInfoList[firstMove+i-1].depth = 0;
17770             commentList[framePtr+i] = commentList[firstMove+i];
17771             commentList[firstMove+i] = NULL;
17772         }
17773
17774         storedGames++;
17775         forwardMostMove = firstMove; // truncate game so we can start variation
17776 }
17777
17778 void
17779 PushTail (int firstMove, int lastMove)
17780 {
17781         if(appData.icsActive) { // only in local mode
17782                 forwardMostMove = currentMove; // mimic old ICS behavior
17783                 return;
17784         }
17785         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
17786
17787         PushInner(firstMove, lastMove);
17788         if(storedGames == 1) GreyRevert(FALSE);
17789         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
17790 }
17791
17792 void
17793 PopInner (Boolean annotate)
17794 {
17795         int i, j, nrMoves;
17796         char buf[8000], moveBuf[20];
17797
17798         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
17799         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
17800         nrMoves = savedLast[storedGames] - currentMove;
17801         if(annotate) {
17802                 int cnt = 10;
17803                 if(!WhiteOnMove(currentMove))
17804                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
17805                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
17806                 for(i=currentMove; i<forwardMostMove; i++) {
17807                         if(WhiteOnMove(i))
17808                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
17809                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
17810                         strcat(buf, moveBuf);
17811                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
17812                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
17813                 }
17814                 strcat(buf, ")");
17815         }
17816         for(i=1; i<=nrMoves; i++) { // copy last variation back
17817             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
17818             for(j=0; j<MOVE_LEN; j++)
17819                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
17820             for(j=0; j<2*MOVE_LEN; j++)
17821                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
17822             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
17823             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
17824             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
17825             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
17826             commentList[currentMove+i] = commentList[framePtr+i];
17827             commentList[framePtr+i] = NULL;
17828         }
17829         if(annotate) AppendComment(currentMove+1, buf, FALSE);
17830         framePtr = savedFramePtr[storedGames];
17831         gameInfo.result = savedResult[storedGames];
17832         if(gameInfo.resultDetails != NULL) {
17833             free(gameInfo.resultDetails);
17834       }
17835         gameInfo.resultDetails = savedDetails[storedGames];
17836         forwardMostMove = currentMove + nrMoves;
17837 }
17838
17839 Boolean
17840 PopTail (Boolean annotate)
17841 {
17842         if(appData.icsActive) return FALSE; // only in local mode
17843         if(!storedGames) return FALSE; // sanity
17844         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
17845
17846         PopInner(annotate);
17847         if(currentMove < forwardMostMove) ForwardEvent(); else
17848         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
17849
17850         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
17851         return TRUE;
17852 }
17853
17854 void
17855 CleanupTail ()
17856 {       // remove all shelved variations
17857         int i;
17858         for(i=0; i<storedGames; i++) {
17859             if(savedDetails[i])
17860                 free(savedDetails[i]);
17861             savedDetails[i] = NULL;
17862         }
17863         for(i=framePtr; i<MAX_MOVES; i++) {
17864                 if(commentList[i]) free(commentList[i]);
17865                 commentList[i] = NULL;
17866         }
17867         framePtr = MAX_MOVES-1;
17868         storedGames = 0;
17869 }
17870
17871 void
17872 LoadVariation (int index, char *text)
17873 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
17874         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
17875         int level = 0, move;
17876
17877         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
17878         // first find outermost bracketing variation
17879         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
17880             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
17881                 if(*p == '{') wait = '}'; else
17882                 if(*p == '[') wait = ']'; else
17883                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
17884                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
17885             }
17886             if(*p == wait) wait = NULLCHAR; // closing ]} found
17887             p++;
17888         }
17889         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
17890         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
17891         end[1] = NULLCHAR; // clip off comment beyond variation
17892         ToNrEvent(currentMove-1);
17893         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
17894         // kludge: use ParsePV() to append variation to game
17895         move = currentMove;
17896         ParsePV(start, TRUE, TRUE);
17897         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
17898         ClearPremoveHighlights();
17899         CommentPopDown();
17900         ToNrEvent(currentMove+1);
17901 }
17902
17903 void
17904 LoadTheme ()
17905 {
17906     char *p, *q, buf[MSG_SIZ];
17907     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
17908         snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
17909         ParseArgsFromString(buf);
17910         ActivateTheme(TRUE); // also redo colors
17911         return;
17912     }
17913     p = nickName;
17914     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
17915     {
17916         int len;
17917         q = appData.themeNames;
17918         snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
17919       if(appData.useBitmaps) {
17920         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
17921                 appData.liteBackTextureFile, appData.darkBackTextureFile,
17922                 appData.liteBackTextureMode,
17923                 appData.darkBackTextureMode );
17924       } else {
17925         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
17926                 Col2Text(2),   // lightSquareColor
17927                 Col2Text(3) ); // darkSquareColor
17928       }
17929       if(appData.useBorder) {
17930         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
17931                 appData.border);
17932       } else {
17933         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
17934       }
17935       if(appData.useFont) {
17936         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
17937                 appData.renderPiecesWithFont,
17938                 appData.fontToPieceTable,
17939                 Col2Text(9),    // appData.fontBackColorWhite
17940                 Col2Text(10) ); // appData.fontForeColorBlack
17941       } else {
17942         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
17943                 appData.pieceDirectory);
17944         if(!appData.pieceDirectory[0])
17945           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
17946                 Col2Text(0),   // whitePieceColor
17947                 Col2Text(1) ); // blackPieceColor
17948       }
17949       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
17950                 Col2Text(4),   // highlightSquareColor
17951                 Col2Text(5) ); // premoveHighlightColor
17952         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
17953         if(insert != q) insert[-1] = NULLCHAR;
17954         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
17955         if(q)   free(q);
17956     }
17957     ActivateTheme(FALSE);
17958 }