94d715d06b6c41cac3ad6254ca95f7d7b01ac358
[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 *variantNames[] = VARIANT_NAMES;
2010 char *
2011 VariantName (VariantClass v)
2012 {
2013     return variantNames[v];
2014 }
2015
2016
2017 /* Identify a variant from the strings the chess servers use or the
2018    PGN Variant tag names we use. */
2019 VariantClass
2020 StringToVariant (char *e)
2021 {
2022     char *p;
2023     int wnum = -1;
2024     VariantClass v = VariantNormal;
2025     int i, found = FALSE;
2026     char buf[MSG_SIZ];
2027     int len;
2028
2029     if (!e) return v;
2030
2031     /* [HGM] skip over optional board-size prefixes */
2032     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
2033         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
2034         while( *e++ != '_');
2035     }
2036
2037     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2038         v = VariantNormal;
2039         found = TRUE;
2040     } else
2041     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2042       if (StrCaseStr(e, variantNames[i])) {
2043         v = (VariantClass) i;
2044         found = TRUE;
2045         break;
2046       }
2047     }
2048
2049     if (!found) {
2050       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2051           || StrCaseStr(e, "wild/fr")
2052           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2053         v = VariantFischeRandom;
2054       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2055                  (i = 1, p = StrCaseStr(e, "w"))) {
2056         p += i;
2057         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2058         if (isdigit(*p)) {
2059           wnum = atoi(p);
2060         } else {
2061           wnum = -1;
2062         }
2063         switch (wnum) {
2064         case 0: /* FICS only, actually */
2065         case 1:
2066           /* Castling legal even if K starts on d-file */
2067           v = VariantWildCastle;
2068           break;
2069         case 2:
2070         case 3:
2071         case 4:
2072           /* Castling illegal even if K & R happen to start in
2073              normal positions. */
2074           v = VariantNoCastle;
2075           break;
2076         case 5:
2077         case 7:
2078         case 8:
2079         case 10:
2080         case 11:
2081         case 12:
2082         case 13:
2083         case 14:
2084         case 15:
2085         case 18:
2086         case 19:
2087           /* Castling legal iff K & R start in normal positions */
2088           v = VariantNormal;
2089           break;
2090         case 6:
2091         case 20:
2092         case 21:
2093           /* Special wilds for position setup; unclear what to do here */
2094           v = VariantLoadable;
2095           break;
2096         case 9:
2097           /* Bizarre ICC game */
2098           v = VariantTwoKings;
2099           break;
2100         case 16:
2101           v = VariantKriegspiel;
2102           break;
2103         case 17:
2104           v = VariantLosers;
2105           break;
2106         case 22:
2107           v = VariantFischeRandom;
2108           break;
2109         case 23:
2110           v = VariantCrazyhouse;
2111           break;
2112         case 24:
2113           v = VariantBughouse;
2114           break;
2115         case 25:
2116           v = Variant3Check;
2117           break;
2118         case 26:
2119           /* Not quite the same as FICS suicide! */
2120           v = VariantGiveaway;
2121           break;
2122         case 27:
2123           v = VariantAtomic;
2124           break;
2125         case 28:
2126           v = VariantShatranj;
2127           break;
2128
2129         /* Temporary names for future ICC types.  The name *will* change in
2130            the next xboard/WinBoard release after ICC defines it. */
2131         case 29:
2132           v = Variant29;
2133           break;
2134         case 30:
2135           v = Variant30;
2136           break;
2137         case 31:
2138           v = Variant31;
2139           break;
2140         case 32:
2141           v = Variant32;
2142           break;
2143         case 33:
2144           v = Variant33;
2145           break;
2146         case 34:
2147           v = Variant34;
2148           break;
2149         case 35:
2150           v = Variant35;
2151           break;
2152         case 36:
2153           v = Variant36;
2154           break;
2155         case 37:
2156           v = VariantShogi;
2157           break;
2158         case 38:
2159           v = VariantXiangqi;
2160           break;
2161         case 39:
2162           v = VariantCourier;
2163           break;
2164         case 40:
2165           v = VariantGothic;
2166           break;
2167         case 41:
2168           v = VariantCapablanca;
2169           break;
2170         case 42:
2171           v = VariantKnightmate;
2172           break;
2173         case 43:
2174           v = VariantFairy;
2175           break;
2176         case 44:
2177           v = VariantCylinder;
2178           break;
2179         case 45:
2180           v = VariantFalcon;
2181           break;
2182         case 46:
2183           v = VariantCapaRandom;
2184           break;
2185         case 47:
2186           v = VariantBerolina;
2187           break;
2188         case 48:
2189           v = VariantJanus;
2190           break;
2191         case 49:
2192           v = VariantSuper;
2193           break;
2194         case 50:
2195           v = VariantGreat;
2196           break;
2197         case -1:
2198           /* Found "wild" or "w" in the string but no number;
2199              must assume it's normal chess. */
2200           v = VariantNormal;
2201           break;
2202         default:
2203           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2204           if( (len >= MSG_SIZ) && appData.debugMode )
2205             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2206
2207           DisplayError(buf, 0);
2208           v = VariantUnknown;
2209           break;
2210         }
2211       }
2212     }
2213     if (appData.debugMode) {
2214       fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
2215               e, wnum, VariantName(v));
2216     }
2217     return v;
2218 }
2219
2220 static int leftover_start = 0, leftover_len = 0;
2221 char star_match[STAR_MATCH_N][MSG_SIZ];
2222
2223 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2224    advance *index beyond it, and set leftover_start to the new value of
2225    *index; else return FALSE.  If pattern contains the character '*', it
2226    matches any sequence of characters not containing '\r', '\n', or the
2227    character following the '*' (if any), and the matched sequence(s) are
2228    copied into star_match.
2229    */
2230 int
2231 looking_at ( char *buf, int *index, char *pattern)
2232 {
2233     char *bufp = &buf[*index], *patternp = pattern;
2234     int star_count = 0;
2235     char *matchp = star_match[0];
2236
2237     for (;;) {
2238         if (*patternp == NULLCHAR) {
2239             *index = leftover_start = bufp - buf;
2240             *matchp = NULLCHAR;
2241             return TRUE;
2242         }
2243         if (*bufp == NULLCHAR) return FALSE;
2244         if (*patternp == '*') {
2245             if (*bufp == *(patternp + 1)) {
2246                 *matchp = NULLCHAR;
2247                 matchp = star_match[++star_count];
2248                 patternp += 2;
2249                 bufp++;
2250                 continue;
2251             } else if (*bufp == '\n' || *bufp == '\r') {
2252                 patternp++;
2253                 if (*patternp == NULLCHAR)
2254                   continue;
2255                 else
2256                   return FALSE;
2257             } else {
2258                 *matchp++ = *bufp++;
2259                 continue;
2260             }
2261         }
2262         if (*patternp != *bufp) return FALSE;
2263         patternp++;
2264         bufp++;
2265     }
2266 }
2267
2268 void
2269 SendToPlayer (char *data, int length)
2270 {
2271     int error, outCount;
2272     outCount = OutputToProcess(NoProc, data, length, &error);
2273     if (outCount < length) {
2274         DisplayFatalError(_("Error writing to display"), error, 1);
2275     }
2276 }
2277
2278 void
2279 PackHolding (char packed[], char *holding)
2280 {
2281     char *p = holding;
2282     char *q = packed;
2283     int runlength = 0;
2284     int curr = 9999;
2285     do {
2286         if (*p == curr) {
2287             runlength++;
2288         } else {
2289             switch (runlength) {
2290               case 0:
2291                 break;
2292               case 1:
2293                 *q++ = curr;
2294                 break;
2295               case 2:
2296                 *q++ = curr;
2297                 *q++ = curr;
2298                 break;
2299               default:
2300                 sprintf(q, "%d", runlength);
2301                 while (*q) q++;
2302                 *q++ = curr;
2303                 break;
2304             }
2305             runlength = 1;
2306             curr = *p;
2307         }
2308     } while (*p++);
2309     *q = NULLCHAR;
2310 }
2311
2312 /* Telnet protocol requests from the front end */
2313 void
2314 TelnetRequest (unsigned char ddww, unsigned char option)
2315 {
2316     unsigned char msg[3];
2317     int outCount, outError;
2318
2319     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2320
2321     if (appData.debugMode) {
2322         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2323         switch (ddww) {
2324           case TN_DO:
2325             ddwwStr = "DO";
2326             break;
2327           case TN_DONT:
2328             ddwwStr = "DONT";
2329             break;
2330           case TN_WILL:
2331             ddwwStr = "WILL";
2332             break;
2333           case TN_WONT:
2334             ddwwStr = "WONT";
2335             break;
2336           default:
2337             ddwwStr = buf1;
2338             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2339             break;
2340         }
2341         switch (option) {
2342           case TN_ECHO:
2343             optionStr = "ECHO";
2344             break;
2345           default:
2346             optionStr = buf2;
2347             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2348             break;
2349         }
2350         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2351     }
2352     msg[0] = TN_IAC;
2353     msg[1] = ddww;
2354     msg[2] = option;
2355     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2356     if (outCount < 3) {
2357         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2358     }
2359 }
2360
2361 void
2362 DoEcho ()
2363 {
2364     if (!appData.icsActive) return;
2365     TelnetRequest(TN_DO, TN_ECHO);
2366 }
2367
2368 void
2369 DontEcho ()
2370 {
2371     if (!appData.icsActive) return;
2372     TelnetRequest(TN_DONT, TN_ECHO);
2373 }
2374
2375 void
2376 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2377 {
2378     /* put the holdings sent to us by the server on the board holdings area */
2379     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2380     char p;
2381     ChessSquare piece;
2382
2383     if(gameInfo.holdingsWidth < 2)  return;
2384     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2385         return; // prevent overwriting by pre-board holdings
2386
2387     if( (int)lowestPiece >= BlackPawn ) {
2388         holdingsColumn = 0;
2389         countsColumn = 1;
2390         holdingsStartRow = BOARD_HEIGHT-1;
2391         direction = -1;
2392     } else {
2393         holdingsColumn = BOARD_WIDTH-1;
2394         countsColumn = BOARD_WIDTH-2;
2395         holdingsStartRow = 0;
2396         direction = 1;
2397     }
2398
2399     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2400         board[i][holdingsColumn] = EmptySquare;
2401         board[i][countsColumn]   = (ChessSquare) 0;
2402     }
2403     while( (p=*holdings++) != NULLCHAR ) {
2404         piece = CharToPiece( ToUpper(p) );
2405         if(piece == EmptySquare) continue;
2406         /*j = (int) piece - (int) WhitePawn;*/
2407         j = PieceToNumber(piece);
2408         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2409         if(j < 0) continue;               /* should not happen */
2410         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2411         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2412         board[holdingsStartRow+j*direction][countsColumn]++;
2413     }
2414 }
2415
2416
2417 void
2418 VariantSwitch (Board board, VariantClass newVariant)
2419 {
2420    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2421    static Board oldBoard;
2422
2423    startedFromPositionFile = FALSE;
2424    if(gameInfo.variant == newVariant) return;
2425
2426    /* [HGM] This routine is called each time an assignment is made to
2427     * gameInfo.variant during a game, to make sure the board sizes
2428     * are set to match the new variant. If that means adding or deleting
2429     * holdings, we shift the playing board accordingly
2430     * This kludge is needed because in ICS observe mode, we get boards
2431     * of an ongoing game without knowing the variant, and learn about the
2432     * latter only later. This can be because of the move list we requested,
2433     * in which case the game history is refilled from the beginning anyway,
2434     * but also when receiving holdings of a crazyhouse game. In the latter
2435     * case we want to add those holdings to the already received position.
2436     */
2437
2438
2439    if (appData.debugMode) {
2440      fprintf(debugFP, "Switch board from %s to %s\n",
2441              VariantName(gameInfo.variant), VariantName(newVariant));
2442      setbuf(debugFP, NULL);
2443    }
2444    shuffleOpenings = 0;       /* [HGM] shuffle */
2445    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2446    switch(newVariant)
2447      {
2448      case VariantShogi:
2449        newWidth = 9;  newHeight = 9;
2450        gameInfo.holdingsSize = 7;
2451      case VariantBughouse:
2452      case VariantCrazyhouse:
2453        newHoldingsWidth = 2; break;
2454      case VariantGreat:
2455        newWidth = 10;
2456      case VariantSuper:
2457        newHoldingsWidth = 2;
2458        gameInfo.holdingsSize = 8;
2459        break;
2460      case VariantGothic:
2461      case VariantCapablanca:
2462      case VariantCapaRandom:
2463        newWidth = 10;
2464      default:
2465        newHoldingsWidth = gameInfo.holdingsSize = 0;
2466      };
2467
2468    if(newWidth  != gameInfo.boardWidth  ||
2469       newHeight != gameInfo.boardHeight ||
2470       newHoldingsWidth != gameInfo.holdingsWidth ) {
2471
2472      /* shift position to new playing area, if needed */
2473      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2474        for(i=0; i<BOARD_HEIGHT; i++)
2475          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2476            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2477              board[i][j];
2478        for(i=0; i<newHeight; i++) {
2479          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2480          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2481        }
2482      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2483        for(i=0; i<BOARD_HEIGHT; i++)
2484          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2485            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2486              board[i][j];
2487      }
2488      board[HOLDINGS_SET] = 0;
2489      gameInfo.boardWidth  = newWidth;
2490      gameInfo.boardHeight = newHeight;
2491      gameInfo.holdingsWidth = newHoldingsWidth;
2492      gameInfo.variant = newVariant;
2493      InitDrawingSizes(-2, 0);
2494    } else gameInfo.variant = newVariant;
2495    CopyBoard(oldBoard, board);   // remember correctly formatted board
2496      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2497    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2498 }
2499
2500 static int loggedOn = FALSE;
2501
2502 /*-- Game start info cache: --*/
2503 int gs_gamenum;
2504 char gs_kind[MSG_SIZ];
2505 static char player1Name[128] = "";
2506 static char player2Name[128] = "";
2507 static char cont_seq[] = "\n\\   ";
2508 static int player1Rating = -1;
2509 static int player2Rating = -1;
2510 /*----------------------------*/
2511
2512 ColorClass curColor = ColorNormal;
2513 int suppressKibitz = 0;
2514
2515 // [HGM] seekgraph
2516 Boolean soughtPending = FALSE;
2517 Boolean seekGraphUp;
2518 #define MAX_SEEK_ADS 200
2519 #define SQUARE 0x80
2520 char *seekAdList[MAX_SEEK_ADS];
2521 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2522 float tcList[MAX_SEEK_ADS];
2523 char colorList[MAX_SEEK_ADS];
2524 int nrOfSeekAds = 0;
2525 int minRating = 1010, maxRating = 2800;
2526 int hMargin = 10, vMargin = 20, h, w;
2527 extern int squareSize, lineGap;
2528
2529 void
2530 PlotSeekAd (int i)
2531 {
2532         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2533         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2534         if(r < minRating+100 && r >=0 ) r = minRating+100;
2535         if(r > maxRating) r = maxRating;
2536         if(tc < 1.f) tc = 1.f;
2537         if(tc > 95.f) tc = 95.f;
2538         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2539         y = ((double)r - minRating)/(maxRating - minRating)
2540             * (h-vMargin-squareSize/8-1) + vMargin;
2541         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2542         if(strstr(seekAdList[i], " u ")) color = 1;
2543         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2544            !strstr(seekAdList[i], "bullet") &&
2545            !strstr(seekAdList[i], "blitz") &&
2546            !strstr(seekAdList[i], "standard") ) color = 2;
2547         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2548         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2549 }
2550
2551 void
2552 PlotSingleSeekAd (int i)
2553 {
2554         PlotSeekAd(i);
2555 }
2556
2557 void
2558 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2559 {
2560         char buf[MSG_SIZ], *ext = "";
2561         VariantClass v = StringToVariant(type);
2562         if(strstr(type, "wild")) {
2563             ext = type + 4; // append wild number
2564             if(v == VariantFischeRandom) type = "chess960"; else
2565             if(v == VariantLoadable) type = "setup"; else
2566             type = VariantName(v);
2567         }
2568         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2569         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2570             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2571             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2572             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2573             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2574             seekNrList[nrOfSeekAds] = nr;
2575             zList[nrOfSeekAds] = 0;
2576             seekAdList[nrOfSeekAds++] = StrSave(buf);
2577             if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2578         }
2579 }
2580
2581 void
2582 EraseSeekDot (int i)
2583 {
2584     int x = xList[i], y = yList[i], d=squareSize/4, k;
2585     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2586     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2587     // now replot every dot that overlapped
2588     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2589         int xx = xList[k], yy = yList[k];
2590         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2591             DrawSeekDot(xx, yy, colorList[k]);
2592     }
2593 }
2594
2595 void
2596 RemoveSeekAd (int nr)
2597 {
2598         int i;
2599         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2600             EraseSeekDot(i);
2601             if(seekAdList[i]) free(seekAdList[i]);
2602             seekAdList[i] = seekAdList[--nrOfSeekAds];
2603             seekNrList[i] = seekNrList[nrOfSeekAds];
2604             ratingList[i] = ratingList[nrOfSeekAds];
2605             colorList[i]  = colorList[nrOfSeekAds];
2606             tcList[i] = tcList[nrOfSeekAds];
2607             xList[i]  = xList[nrOfSeekAds];
2608             yList[i]  = yList[nrOfSeekAds];
2609             zList[i]  = zList[nrOfSeekAds];
2610             seekAdList[nrOfSeekAds] = NULL;
2611             break;
2612         }
2613 }
2614
2615 Boolean
2616 MatchSoughtLine (char *line)
2617 {
2618     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2619     int nr, base, inc, u=0; char dummy;
2620
2621     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2622        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2623        (u=1) &&
2624        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2625         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2626         // match: compact and save the line
2627         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2628         return TRUE;
2629     }
2630     return FALSE;
2631 }
2632
2633 int
2634 DrawSeekGraph ()
2635 {
2636     int i;
2637     if(!seekGraphUp) return FALSE;
2638     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2639     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2640
2641     DrawSeekBackground(0, 0, w, h);
2642     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2643     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2644     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2645         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2646         yy = h-1-yy;
2647         DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2648         if(i%500 == 0) {
2649             char buf[MSG_SIZ];
2650             snprintf(buf, MSG_SIZ, "%d", i);
2651             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2652         }
2653     }
2654     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2655     for(i=1; i<100; i+=(i<10?1:5)) {
2656         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2657         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2658         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2659             char buf[MSG_SIZ];
2660             snprintf(buf, MSG_SIZ, "%d", i);
2661             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2662         }
2663     }
2664     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2665     return TRUE;
2666 }
2667
2668 int
2669 SeekGraphClick (ClickType click, int x, int y, int moving)
2670 {
2671     static int lastDown = 0, displayed = 0, lastSecond;
2672     if(y < 0) return FALSE;
2673     if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2674         (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2675         if(!seekGraphUp) return FALSE;
2676         seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2677         DrawPosition(TRUE, NULL);
2678         return TRUE;
2679     }
2680     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2681         if(click == Release || moving) return FALSE;
2682         nrOfSeekAds = 0;
2683         soughtPending = TRUE;
2684         SendToICS(ics_prefix);
2685         SendToICS("sought\n"); // should this be "sought all"?
2686     } else { // issue challenge based on clicked ad
2687         int dist = 10000; int i, closest = 0, second = 0;
2688         for(i=0; i<nrOfSeekAds; i++) {
2689             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2690             if(d < dist) { dist = d; closest = i; }
2691             second += (d - zList[i] < 120); // count in-range ads
2692             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2693         }
2694         if(dist < 120) {
2695             char buf[MSG_SIZ];
2696             second = (second > 1);
2697             if(displayed != closest || second != lastSecond) {
2698                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2699                 lastSecond = second; displayed = closest;
2700             }
2701             if(click == Press) {
2702                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2703                 lastDown = closest;
2704                 return TRUE;
2705             } // on press 'hit', only show info
2706             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2707             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2708             SendToICS(ics_prefix);
2709             SendToICS(buf);
2710             return TRUE; // let incoming board of started game pop down the graph
2711         } else if(click == Release) { // release 'miss' is ignored
2712             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2713             if(moving == 2) { // right up-click
2714                 nrOfSeekAds = 0; // refresh graph
2715                 soughtPending = TRUE;
2716                 SendToICS(ics_prefix);
2717                 SendToICS("sought\n"); // should this be "sought all"?
2718             }
2719             return TRUE;
2720         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2721         // press miss or release hit 'pop down' seek graph
2722         seekGraphUp = FALSE;
2723         DrawPosition(TRUE, NULL);
2724     }
2725     return TRUE;
2726 }
2727
2728 void
2729 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2730 {
2731 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2732 #define STARTED_NONE 0
2733 #define STARTED_MOVES 1
2734 #define STARTED_BOARD 2
2735 #define STARTED_OBSERVE 3
2736 #define STARTED_HOLDINGS 4
2737 #define STARTED_CHATTER 5
2738 #define STARTED_COMMENT 6
2739 #define STARTED_MOVES_NOHIDE 7
2740
2741     static int started = STARTED_NONE;
2742     static char parse[20000];
2743     static int parse_pos = 0;
2744     static char buf[BUF_SIZE + 1];
2745     static int firstTime = TRUE, intfSet = FALSE;
2746     static ColorClass prevColor = ColorNormal;
2747     static int savingComment = FALSE;
2748     static int cmatch = 0; // continuation sequence match
2749     char *bp;
2750     char str[MSG_SIZ];
2751     int i, oldi;
2752     int buf_len;
2753     int next_out;
2754     int tkind;
2755     int backup;    /* [DM] For zippy color lines */
2756     char *p;
2757     char talker[MSG_SIZ]; // [HGM] chat
2758     int channel;
2759
2760     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2761
2762     if (appData.debugMode) {
2763       if (!error) {
2764         fprintf(debugFP, "<ICS: ");
2765         show_bytes(debugFP, data, count);
2766         fprintf(debugFP, "\n");
2767       }
2768     }
2769
2770     if (appData.debugMode) { int f = forwardMostMove;
2771         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2772                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2773                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2774     }
2775     if (count > 0) {
2776         /* If last read ended with a partial line that we couldn't parse,
2777            prepend it to the new read and try again. */
2778         if (leftover_len > 0) {
2779             for (i=0; i<leftover_len; i++)
2780               buf[i] = buf[leftover_start + i];
2781         }
2782
2783     /* copy new characters into the buffer */
2784     bp = buf + leftover_len;
2785     buf_len=leftover_len;
2786     for (i=0; i<count; i++)
2787     {
2788         // ignore these
2789         if (data[i] == '\r')
2790             continue;
2791
2792         // join lines split by ICS?
2793         if (!appData.noJoin)
2794         {
2795             /*
2796                 Joining just consists of finding matches against the
2797                 continuation sequence, and discarding that sequence
2798                 if found instead of copying it.  So, until a match
2799                 fails, there's nothing to do since it might be the
2800                 complete sequence, and thus, something we don't want
2801                 copied.
2802             */
2803             if (data[i] == cont_seq[cmatch])
2804             {
2805                 cmatch++;
2806                 if (cmatch == strlen(cont_seq))
2807                 {
2808                     cmatch = 0; // complete match.  just reset the counter
2809
2810                     /*
2811                         it's possible for the ICS to not include the space
2812                         at the end of the last word, making our [correct]
2813                         join operation fuse two separate words.  the server
2814                         does this when the space occurs at the width setting.
2815                     */
2816                     if (!buf_len || buf[buf_len-1] != ' ')
2817                     {
2818                         *bp++ = ' ';
2819                         buf_len++;
2820                     }
2821                 }
2822                 continue;
2823             }
2824             else if (cmatch)
2825             {
2826                 /*
2827                     match failed, so we have to copy what matched before
2828                     falling through and copying this character.  In reality,
2829                     this will only ever be just the newline character, but
2830                     it doesn't hurt to be precise.
2831                 */
2832                 strncpy(bp, cont_seq, cmatch);
2833                 bp += cmatch;
2834                 buf_len += cmatch;
2835                 cmatch = 0;
2836             }
2837         }
2838
2839         // copy this char
2840         *bp++ = data[i];
2841         buf_len++;
2842     }
2843
2844         buf[buf_len] = NULLCHAR;
2845 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2846         next_out = 0;
2847         leftover_start = 0;
2848
2849         i = 0;
2850         while (i < buf_len) {
2851             /* Deal with part of the TELNET option negotiation
2852                protocol.  We refuse to do anything beyond the
2853                defaults, except that we allow the WILL ECHO option,
2854                which ICS uses to turn off password echoing when we are
2855                directly connected to it.  We reject this option
2856                if localLineEditing mode is on (always on in xboard)
2857                and we are talking to port 23, which might be a real
2858                telnet server that will try to keep WILL ECHO on permanently.
2859              */
2860             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2861                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2862                 unsigned char option;
2863                 oldi = i;
2864                 switch ((unsigned char) buf[++i]) {
2865                   case TN_WILL:
2866                     if (appData.debugMode)
2867                       fprintf(debugFP, "\n<WILL ");
2868                     switch (option = (unsigned char) buf[++i]) {
2869                       case TN_ECHO:
2870                         if (appData.debugMode)
2871                           fprintf(debugFP, "ECHO ");
2872                         /* Reply only if this is a change, according
2873                            to the protocol rules. */
2874                         if (remoteEchoOption) break;
2875                         if (appData.localLineEditing &&
2876                             atoi(appData.icsPort) == TN_PORT) {
2877                             TelnetRequest(TN_DONT, TN_ECHO);
2878                         } else {
2879                             EchoOff();
2880                             TelnetRequest(TN_DO, TN_ECHO);
2881                             remoteEchoOption = TRUE;
2882                         }
2883                         break;
2884                       default:
2885                         if (appData.debugMode)
2886                           fprintf(debugFP, "%d ", option);
2887                         /* Whatever this is, we don't want it. */
2888                         TelnetRequest(TN_DONT, option);
2889                         break;
2890                     }
2891                     break;
2892                   case TN_WONT:
2893                     if (appData.debugMode)
2894                       fprintf(debugFP, "\n<WONT ");
2895                     switch (option = (unsigned char) buf[++i]) {
2896                       case TN_ECHO:
2897                         if (appData.debugMode)
2898                           fprintf(debugFP, "ECHO ");
2899                         /* Reply only if this is a change, according
2900                            to the protocol rules. */
2901                         if (!remoteEchoOption) break;
2902                         EchoOn();
2903                         TelnetRequest(TN_DONT, TN_ECHO);
2904                         remoteEchoOption = FALSE;
2905                         break;
2906                       default:
2907                         if (appData.debugMode)
2908                           fprintf(debugFP, "%d ", (unsigned char) option);
2909                         /* Whatever this is, it must already be turned
2910                            off, because we never agree to turn on
2911                            anything non-default, so according to the
2912                            protocol rules, we don't reply. */
2913                         break;
2914                     }
2915                     break;
2916                   case TN_DO:
2917                     if (appData.debugMode)
2918                       fprintf(debugFP, "\n<DO ");
2919                     switch (option = (unsigned char) buf[++i]) {
2920                       default:
2921                         /* Whatever this is, we refuse to do it. */
2922                         if (appData.debugMode)
2923                           fprintf(debugFP, "%d ", option);
2924                         TelnetRequest(TN_WONT, option);
2925                         break;
2926                     }
2927                     break;
2928                   case TN_DONT:
2929                     if (appData.debugMode)
2930                       fprintf(debugFP, "\n<DONT ");
2931                     switch (option = (unsigned char) buf[++i]) {
2932                       default:
2933                         if (appData.debugMode)
2934                           fprintf(debugFP, "%d ", option);
2935                         /* Whatever this is, we are already not doing
2936                            it, because we never agree to do anything
2937                            non-default, so according to the protocol
2938                            rules, we don't reply. */
2939                         break;
2940                     }
2941                     break;
2942                   case TN_IAC:
2943                     if (appData.debugMode)
2944                       fprintf(debugFP, "\n<IAC ");
2945                     /* Doubled IAC; pass it through */
2946                     i--;
2947                     break;
2948                   default:
2949                     if (appData.debugMode)
2950                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2951                     /* Drop all other telnet commands on the floor */
2952                     break;
2953                 }
2954                 if (oldi > next_out)
2955                   SendToPlayer(&buf[next_out], oldi - next_out);
2956                 if (++i > next_out)
2957                   next_out = i;
2958                 continue;
2959             }
2960
2961             /* OK, this at least will *usually* work */
2962             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2963                 loggedOn = TRUE;
2964             }
2965
2966             if (loggedOn && !intfSet) {
2967                 if (ics_type == ICS_ICC) {
2968                   snprintf(str, MSG_SIZ,
2969                           "/set-quietly interface %s\n/set-quietly style 12\n",
2970                           programVersion);
2971                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2972                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2973                 } else if (ics_type == ICS_CHESSNET) {
2974                   snprintf(str, MSG_SIZ, "/style 12\n");
2975                 } else {
2976                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2977                   strcat(str, programVersion);
2978                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2979                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2980                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2981 #ifdef WIN32
2982                   strcat(str, "$iset nohighlight 1\n");
2983 #endif
2984                   strcat(str, "$iset lock 1\n$style 12\n");
2985                 }
2986                 SendToICS(str);
2987                 NotifyFrontendLogin();
2988                 intfSet = TRUE;
2989             }
2990
2991             if (started == STARTED_COMMENT) {
2992                 /* Accumulate characters in comment */
2993                 parse[parse_pos++] = buf[i];
2994                 if (buf[i] == '\n') {
2995                     parse[parse_pos] = NULLCHAR;
2996                     if(chattingPartner>=0) {
2997                         char mess[MSG_SIZ];
2998                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2999                         OutputChatMessage(chattingPartner, mess);
3000                         chattingPartner = -1;
3001                         next_out = i+1; // [HGM] suppress printing in ICS window
3002                     } else
3003                     if(!suppressKibitz) // [HGM] kibitz
3004                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
3005                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
3006                         int nrDigit = 0, nrAlph = 0, j;
3007                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
3008                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
3009                         parse[parse_pos] = NULLCHAR;
3010                         // try to be smart: if it does not look like search info, it should go to
3011                         // ICS interaction window after all, not to engine-output window.
3012                         for(j=0; j<parse_pos; j++) { // count letters and digits
3013                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
3014                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
3015                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
3016                         }
3017                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
3018                             int depth=0; float score;
3019                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
3020                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
3021                                 pvInfoList[forwardMostMove-1].depth = depth;
3022                                 pvInfoList[forwardMostMove-1].score = 100*score;
3023                             }
3024                             OutputKibitz(suppressKibitz, parse);
3025                         } else {
3026                             char tmp[MSG_SIZ];
3027                             if(gameMode == IcsObserving) // restore original ICS messages
3028                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3029                             else
3030                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3031                             SendToPlayer(tmp, strlen(tmp));
3032                         }
3033                         next_out = i+1; // [HGM] suppress printing in ICS window
3034                     }
3035                     started = STARTED_NONE;
3036                 } else {
3037                     /* Don't match patterns against characters in comment */
3038                     i++;
3039                     continue;
3040                 }
3041             }
3042             if (started == STARTED_CHATTER) {
3043                 if (buf[i] != '\n') {
3044                     /* Don't match patterns against characters in chatter */
3045                     i++;
3046                     continue;
3047                 }
3048                 started = STARTED_NONE;
3049                 if(suppressKibitz) next_out = i+1;
3050             }
3051
3052             /* Kludge to deal with rcmd protocol */
3053             if (firstTime && looking_at(buf, &i, "\001*")) {
3054                 DisplayFatalError(&buf[1], 0, 1);
3055                 continue;
3056             } else {
3057                 firstTime = FALSE;
3058             }
3059
3060             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3061                 ics_type = ICS_ICC;
3062                 ics_prefix = "/";
3063                 if (appData.debugMode)
3064                   fprintf(debugFP, "ics_type %d\n", ics_type);
3065                 continue;
3066             }
3067             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3068                 ics_type = ICS_FICS;
3069                 ics_prefix = "$";
3070                 if (appData.debugMode)
3071                   fprintf(debugFP, "ics_type %d\n", ics_type);
3072                 continue;
3073             }
3074             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3075                 ics_type = ICS_CHESSNET;
3076                 ics_prefix = "/";
3077                 if (appData.debugMode)
3078                   fprintf(debugFP, "ics_type %d\n", ics_type);
3079                 continue;
3080             }
3081
3082             if (!loggedOn &&
3083                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3084                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3085                  looking_at(buf, &i, "will be \"*\""))) {
3086               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3087               continue;
3088             }
3089
3090             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3091               char buf[MSG_SIZ];
3092               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3093               DisplayIcsInteractionTitle(buf);
3094               have_set_title = TRUE;
3095             }
3096
3097             /* skip finger notes */
3098             if (started == STARTED_NONE &&
3099                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3100                  (buf[i] == '1' && buf[i+1] == '0')) &&
3101                 buf[i+2] == ':' && buf[i+3] == ' ') {
3102               started = STARTED_CHATTER;
3103               i += 3;
3104               continue;
3105             }
3106
3107             oldi = i;
3108             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3109             if(appData.seekGraph) {
3110                 if(soughtPending && MatchSoughtLine(buf+i)) {
3111                     i = strstr(buf+i, "rated") - buf;
3112                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3113                     next_out = leftover_start = i;
3114                     started = STARTED_CHATTER;
3115                     suppressKibitz = TRUE;
3116                     continue;
3117                 }
3118                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3119                         && looking_at(buf, &i, "* ads displayed")) {
3120                     soughtPending = FALSE;
3121                     seekGraphUp = TRUE;
3122                     DrawSeekGraph();
3123                     continue;
3124                 }
3125                 if(appData.autoRefresh) {
3126                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3127                         int s = (ics_type == ICS_ICC); // ICC format differs
3128                         if(seekGraphUp)
3129                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3130                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3131                         looking_at(buf, &i, "*% "); // eat prompt
3132                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3133                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3134                         next_out = i; // suppress
3135                         continue;
3136                     }
3137                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3138                         char *p = star_match[0];
3139                         while(*p) {
3140                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3141                             while(*p && *p++ != ' '); // next
3142                         }
3143                         looking_at(buf, &i, "*% "); // eat prompt
3144                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3145                         next_out = i;
3146                         continue;
3147                     }
3148                 }
3149             }
3150
3151             /* skip formula vars */
3152             if (started == STARTED_NONE &&
3153                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3154               started = STARTED_CHATTER;
3155               i += 3;
3156               continue;
3157             }
3158
3159             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3160             if (appData.autoKibitz && started == STARTED_NONE &&
3161                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3162                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3163                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3164                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3165                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3166                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3167                         suppressKibitz = TRUE;
3168                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3169                         next_out = i;
3170                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3171                                 && (gameMode == IcsPlayingWhite)) ||
3172                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3173                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3174                             started = STARTED_CHATTER; // own kibitz we simply discard
3175                         else {
3176                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3177                             parse_pos = 0; parse[0] = NULLCHAR;
3178                             savingComment = TRUE;
3179                             suppressKibitz = gameMode != IcsObserving ? 2 :
3180                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3181                         }
3182                         continue;
3183                 } else
3184                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3185                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3186                          && atoi(star_match[0])) {
3187                     // suppress the acknowledgements of our own autoKibitz
3188                     char *p;
3189                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3190                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3191                     SendToPlayer(star_match[0], strlen(star_match[0]));
3192                     if(looking_at(buf, &i, "*% ")) // eat prompt
3193                         suppressKibitz = FALSE;
3194                     next_out = i;
3195                     continue;
3196                 }
3197             } // [HGM] kibitz: end of patch
3198
3199             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3200
3201             // [HGM] chat: intercept tells by users for which we have an open chat window
3202             channel = -1;
3203             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3204                                            looking_at(buf, &i, "* whispers:") ||
3205                                            looking_at(buf, &i, "* kibitzes:") ||
3206                                            looking_at(buf, &i, "* shouts:") ||
3207                                            looking_at(buf, &i, "* c-shouts:") ||
3208                                            looking_at(buf, &i, "--> * ") ||
3209                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3210                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3211                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3212                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3213                 int p;
3214                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3215                 chattingPartner = -1;
3216
3217                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3218                 for(p=0; p<MAX_CHAT; p++) {
3219                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3220                     talker[0] = '['; strcat(talker, "] ");
3221                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3222                     chattingPartner = p; break;
3223                     }
3224                 } else
3225                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3226                 for(p=0; p<MAX_CHAT; p++) {
3227                     if(!strcmp("kibitzes", chatPartner[p])) {
3228                         talker[0] = '['; strcat(talker, "] ");
3229                         chattingPartner = p; break;
3230                     }
3231                 } else
3232                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3233                 for(p=0; p<MAX_CHAT; p++) {
3234                     if(!strcmp("whispers", chatPartner[p])) {
3235                         talker[0] = '['; strcat(talker, "] ");
3236                         chattingPartner = p; break;
3237                     }
3238                 } else
3239                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3240                   if(buf[i-8] == '-' && buf[i-3] == 't')
3241                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3242                     if(!strcmp("c-shouts", chatPartner[p])) {
3243                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3244                         chattingPartner = p; break;
3245                     }
3246                   }
3247                   if(chattingPartner < 0)
3248                   for(p=0; p<MAX_CHAT; p++) {
3249                     if(!strcmp("shouts", chatPartner[p])) {
3250                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3251                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3252                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3253                         chattingPartner = p; break;
3254                     }
3255                   }
3256                 }
3257                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3258                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3259                     talker[0] = 0; Colorize(ColorTell, FALSE);
3260                     chattingPartner = p; break;
3261                 }
3262                 if(chattingPartner<0) i = oldi; else {
3263                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3264                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3265                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3266                     started = STARTED_COMMENT;
3267                     parse_pos = 0; parse[0] = NULLCHAR;
3268                     savingComment = 3 + chattingPartner; // counts as TRUE
3269                     suppressKibitz = TRUE;
3270                     continue;
3271                 }
3272             } // [HGM] chat: end of patch
3273
3274           backup = i;
3275             if (appData.zippyTalk || appData.zippyPlay) {
3276                 /* [DM] Backup address for color zippy lines */
3277 #if ZIPPY
3278                if (loggedOn == TRUE)
3279                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3280                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3281 #endif
3282             } // [DM] 'else { ' deleted
3283                 if (
3284                     /* Regular tells and says */
3285                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3286                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3287                     looking_at(buf, &i, "* says: ") ||
3288                     /* Don't color "message" or "messages" output */
3289                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3290                     looking_at(buf, &i, "*. * at *:*: ") ||
3291                     looking_at(buf, &i, "--* (*:*): ") ||
3292                     /* Message notifications (same color as tells) */
3293                     looking_at(buf, &i, "* has left a message ") ||
3294                     looking_at(buf, &i, "* just sent you a message:\n") ||
3295                     /* Whispers and kibitzes */
3296                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3297                     looking_at(buf, &i, "* kibitzes: ") ||
3298                     /* Channel tells */
3299                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3300
3301                   if (tkind == 1 && strchr(star_match[0], ':')) {
3302                       /* Avoid "tells you:" spoofs in channels */
3303                      tkind = 3;
3304                   }
3305                   if (star_match[0][0] == NULLCHAR ||
3306                       strchr(star_match[0], ' ') ||
3307                       (tkind == 3 && strchr(star_match[1], ' '))) {
3308                     /* Reject bogus matches */
3309                     i = oldi;
3310                   } else {
3311                     if (appData.colorize) {
3312                       if (oldi > next_out) {
3313                         SendToPlayer(&buf[next_out], oldi - next_out);
3314                         next_out = oldi;
3315                       }
3316                       switch (tkind) {
3317                       case 1:
3318                         Colorize(ColorTell, FALSE);
3319                         curColor = ColorTell;
3320                         break;
3321                       case 2:
3322                         Colorize(ColorKibitz, FALSE);
3323                         curColor = ColorKibitz;
3324                         break;
3325                       case 3:
3326                         p = strrchr(star_match[1], '(');
3327                         if (p == NULL) {
3328                           p = star_match[1];
3329                         } else {
3330                           p++;
3331                         }
3332                         if (atoi(p) == 1) {
3333                           Colorize(ColorChannel1, FALSE);
3334                           curColor = ColorChannel1;
3335                         } else {
3336                           Colorize(ColorChannel, FALSE);
3337                           curColor = ColorChannel;
3338                         }
3339                         break;
3340                       case 5:
3341                         curColor = ColorNormal;
3342                         break;
3343                       }
3344                     }
3345                     if (started == STARTED_NONE && appData.autoComment &&
3346                         (gameMode == IcsObserving ||
3347                          gameMode == IcsPlayingWhite ||
3348                          gameMode == IcsPlayingBlack)) {
3349                       parse_pos = i - oldi;
3350                       memcpy(parse, &buf[oldi], parse_pos);
3351                       parse[parse_pos] = NULLCHAR;
3352                       started = STARTED_COMMENT;
3353                       savingComment = TRUE;
3354                     } else {
3355                       started = STARTED_CHATTER;
3356                       savingComment = FALSE;
3357                     }
3358                     loggedOn = TRUE;
3359                     continue;
3360                   }
3361                 }
3362
3363                 if (looking_at(buf, &i, "* s-shouts: ") ||
3364                     looking_at(buf, &i, "* c-shouts: ")) {
3365                     if (appData.colorize) {
3366                         if (oldi > next_out) {
3367                             SendToPlayer(&buf[next_out], oldi - next_out);
3368                             next_out = oldi;
3369                         }
3370                         Colorize(ColorSShout, FALSE);
3371                         curColor = ColorSShout;
3372                     }
3373                     loggedOn = TRUE;
3374                     started = STARTED_CHATTER;
3375                     continue;
3376                 }
3377
3378                 if (looking_at(buf, &i, "--->")) {
3379                     loggedOn = TRUE;
3380                     continue;
3381                 }
3382
3383                 if (looking_at(buf, &i, "* shouts: ") ||
3384                     looking_at(buf, &i, "--> ")) {
3385                     if (appData.colorize) {
3386                         if (oldi > next_out) {
3387                             SendToPlayer(&buf[next_out], oldi - next_out);
3388                             next_out = oldi;
3389                         }
3390                         Colorize(ColorShout, FALSE);
3391                         curColor = ColorShout;
3392                     }
3393                     loggedOn = TRUE;
3394                     started = STARTED_CHATTER;
3395                     continue;
3396                 }
3397
3398                 if (looking_at( buf, &i, "Challenge:")) {
3399                     if (appData.colorize) {
3400                         if (oldi > next_out) {
3401                             SendToPlayer(&buf[next_out], oldi - next_out);
3402                             next_out = oldi;
3403                         }
3404                         Colorize(ColorChallenge, FALSE);
3405                         curColor = ColorChallenge;
3406                     }
3407                     loggedOn = TRUE;
3408                     continue;
3409                 }
3410
3411                 if (looking_at(buf, &i, "* offers you") ||
3412                     looking_at(buf, &i, "* offers to be") ||
3413                     looking_at(buf, &i, "* would like to") ||
3414                     looking_at(buf, &i, "* requests to") ||
3415                     looking_at(buf, &i, "Your opponent offers") ||
3416                     looking_at(buf, &i, "Your opponent requests")) {
3417
3418                     if (appData.colorize) {
3419                         if (oldi > next_out) {
3420                             SendToPlayer(&buf[next_out], oldi - next_out);
3421                             next_out = oldi;
3422                         }
3423                         Colorize(ColorRequest, FALSE);
3424                         curColor = ColorRequest;
3425                     }
3426                     continue;
3427                 }
3428
3429                 if (looking_at(buf, &i, "* (*) seeking")) {
3430                     if (appData.colorize) {
3431                         if (oldi > next_out) {
3432                             SendToPlayer(&buf[next_out], oldi - next_out);
3433                             next_out = oldi;
3434                         }
3435                         Colorize(ColorSeek, FALSE);
3436                         curColor = ColorSeek;
3437                     }
3438                     continue;
3439             }
3440
3441           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3442
3443             if (looking_at(buf, &i, "\\   ")) {
3444                 if (prevColor != ColorNormal) {
3445                     if (oldi > next_out) {
3446                         SendToPlayer(&buf[next_out], oldi - next_out);
3447                         next_out = oldi;
3448                     }
3449                     Colorize(prevColor, TRUE);
3450                     curColor = prevColor;
3451                 }
3452                 if (savingComment) {
3453                     parse_pos = i - oldi;
3454                     memcpy(parse, &buf[oldi], parse_pos);
3455                     parse[parse_pos] = NULLCHAR;
3456                     started = STARTED_COMMENT;
3457                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3458                         chattingPartner = savingComment - 3; // kludge to remember the box
3459                 } else {
3460                     started = STARTED_CHATTER;
3461                 }
3462                 continue;
3463             }
3464
3465             if (looking_at(buf, &i, "Black Strength :") ||
3466                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3467                 looking_at(buf, &i, "<10>") ||
3468                 looking_at(buf, &i, "#@#")) {
3469                 /* Wrong board style */
3470                 loggedOn = TRUE;
3471                 SendToICS(ics_prefix);
3472                 SendToICS("set style 12\n");
3473                 SendToICS(ics_prefix);
3474                 SendToICS("refresh\n");
3475                 continue;
3476             }
3477
3478             if (looking_at(buf, &i, "login:")) {
3479               if (!have_sent_ICS_logon) {
3480                 if(ICSInitScript())
3481                   have_sent_ICS_logon = 1;
3482                 else // no init script was found
3483                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3484               } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3485                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3486               }
3487                 continue;
3488             }
3489
3490             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3491                 (looking_at(buf, &i, "\n<12> ") ||
3492                  looking_at(buf, &i, "<12> "))) {
3493                 loggedOn = TRUE;
3494                 if (oldi > next_out) {
3495                     SendToPlayer(&buf[next_out], oldi - next_out);
3496                 }
3497                 next_out = i;
3498                 started = STARTED_BOARD;
3499                 parse_pos = 0;
3500                 continue;
3501             }
3502
3503             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3504                 looking_at(buf, &i, "<b1> ")) {
3505                 if (oldi > next_out) {
3506                     SendToPlayer(&buf[next_out], oldi - next_out);
3507                 }
3508                 next_out = i;
3509                 started = STARTED_HOLDINGS;
3510                 parse_pos = 0;
3511                 continue;
3512             }
3513
3514             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3515                 loggedOn = TRUE;
3516                 /* Header for a move list -- first line */
3517
3518                 switch (ics_getting_history) {
3519                   case H_FALSE:
3520                     switch (gameMode) {
3521                       case IcsIdle:
3522                       case BeginningOfGame:
3523                         /* User typed "moves" or "oldmoves" while we
3524                            were idle.  Pretend we asked for these
3525                            moves and soak them up so user can step
3526                            through them and/or save them.
3527                            */
3528                         Reset(FALSE, TRUE);
3529                         gameMode = IcsObserving;
3530                         ModeHighlight();
3531                         ics_gamenum = -1;
3532                         ics_getting_history = H_GOT_UNREQ_HEADER;
3533                         break;
3534                       case EditGame: /*?*/
3535                       case EditPosition: /*?*/
3536                         /* Should above feature work in these modes too? */
3537                         /* For now it doesn't */
3538                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3539                         break;
3540                       default:
3541                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3542                         break;
3543                     }
3544                     break;
3545                   case H_REQUESTED:
3546                     /* Is this the right one? */
3547                     if (gameInfo.white && gameInfo.black &&
3548                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3549                         strcmp(gameInfo.black, star_match[2]) == 0) {
3550                         /* All is well */
3551                         ics_getting_history = H_GOT_REQ_HEADER;
3552                     }
3553                     break;
3554                   case H_GOT_REQ_HEADER:
3555                   case H_GOT_UNREQ_HEADER:
3556                   case H_GOT_UNWANTED_HEADER:
3557                   case H_GETTING_MOVES:
3558                     /* Should not happen */
3559                     DisplayError(_("Error gathering move list: two headers"), 0);
3560                     ics_getting_history = H_FALSE;
3561                     break;
3562                 }
3563
3564                 /* Save player ratings into gameInfo if needed */
3565                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3566                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3567                     (gameInfo.whiteRating == -1 ||
3568                      gameInfo.blackRating == -1)) {
3569
3570                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3571                     gameInfo.blackRating = string_to_rating(star_match[3]);
3572                     if (appData.debugMode)
3573                       fprintf(debugFP, "Ratings from header: W %d, B %d\n",
3574                               gameInfo.whiteRating, gameInfo.blackRating);
3575                 }
3576                 continue;
3577             }
3578
3579             if (looking_at(buf, &i,
3580               "* * match, initial time: * minute*, increment: * second")) {
3581                 /* Header for a move list -- second line */
3582                 /* Initial board will follow if this is a wild game */
3583                 if (gameInfo.event != NULL) free(gameInfo.event);
3584                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3585                 gameInfo.event = StrSave(str);
3586                 /* [HGM] we switched variant. Translate boards if needed. */
3587                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3588                 continue;
3589             }
3590
3591             if (looking_at(buf, &i, "Move  ")) {
3592                 /* Beginning of a move list */
3593                 switch (ics_getting_history) {
3594                   case H_FALSE:
3595                     /* Normally should not happen */
3596                     /* Maybe user hit reset while we were parsing */
3597                     break;
3598                   case H_REQUESTED:
3599                     /* Happens if we are ignoring a move list that is not
3600                      * the one we just requested.  Common if the user
3601                      * tries to observe two games without turning off
3602                      * getMoveList */
3603                     break;
3604                   case H_GETTING_MOVES:
3605                     /* Should not happen */
3606                     DisplayError(_("Error gathering move list: nested"), 0);
3607                     ics_getting_history = H_FALSE;
3608                     break;
3609                   case H_GOT_REQ_HEADER:
3610                     ics_getting_history = H_GETTING_MOVES;
3611                     started = STARTED_MOVES;
3612                     parse_pos = 0;
3613                     if (oldi > next_out) {
3614                         SendToPlayer(&buf[next_out], oldi - next_out);
3615                     }
3616                     break;
3617                   case H_GOT_UNREQ_HEADER:
3618                     ics_getting_history = H_GETTING_MOVES;
3619                     started = STARTED_MOVES_NOHIDE;
3620                     parse_pos = 0;
3621                     break;
3622                   case H_GOT_UNWANTED_HEADER:
3623                     ics_getting_history = H_FALSE;
3624                     break;
3625                 }
3626                 continue;
3627             }
3628
3629             if (looking_at(buf, &i, "% ") ||
3630                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3631                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3632                 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3633                     soughtPending = FALSE;
3634                     seekGraphUp = TRUE;
3635                     DrawSeekGraph();
3636                 }
3637                 if(suppressKibitz) next_out = i;
3638                 savingComment = FALSE;
3639                 suppressKibitz = 0;
3640                 switch (started) {
3641                   case STARTED_MOVES:
3642                   case STARTED_MOVES_NOHIDE:
3643                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3644                     parse[parse_pos + i - oldi] = NULLCHAR;
3645                     ParseGameHistory(parse);
3646 #if ZIPPY
3647                     if (appData.zippyPlay && first.initDone) {
3648                         FeedMovesToProgram(&first, forwardMostMove);
3649                         if (gameMode == IcsPlayingWhite) {
3650                             if (WhiteOnMove(forwardMostMove)) {
3651                                 if (first.sendTime) {
3652                                   if (first.useColors) {
3653                                     SendToProgram("black\n", &first);
3654                                   }
3655                                   SendTimeRemaining(&first, TRUE);
3656                                 }
3657                                 if (first.useColors) {
3658                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3659                                 }
3660                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3661                                 first.maybeThinking = TRUE;
3662                             } else {
3663                                 if (first.usePlayother) {
3664                                   if (first.sendTime) {
3665                                     SendTimeRemaining(&first, TRUE);
3666                                   }
3667                                   SendToProgram("playother\n", &first);
3668                                   firstMove = FALSE;
3669                                 } else {
3670                                   firstMove = TRUE;
3671                                 }
3672                             }
3673                         } else if (gameMode == IcsPlayingBlack) {
3674                             if (!WhiteOnMove(forwardMostMove)) {
3675                                 if (first.sendTime) {
3676                                   if (first.useColors) {
3677                                     SendToProgram("white\n", &first);
3678                                   }
3679                                   SendTimeRemaining(&first, FALSE);
3680                                 }
3681                                 if (first.useColors) {
3682                                   SendToProgram("black\n", &first);
3683                                 }
3684                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3685                                 first.maybeThinking = TRUE;
3686                             } else {
3687                                 if (first.usePlayother) {
3688                                   if (first.sendTime) {
3689                                     SendTimeRemaining(&first, FALSE);
3690                                   }
3691                                   SendToProgram("playother\n", &first);
3692                                   firstMove = FALSE;
3693                                 } else {
3694                                   firstMove = TRUE;
3695                                 }
3696                             }
3697                         }
3698                     }
3699 #endif
3700                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3701                         /* Moves came from oldmoves or moves command
3702                            while we weren't doing anything else.
3703                            */
3704                         currentMove = forwardMostMove;
3705                         ClearHighlights();/*!!could figure this out*/
3706                         flipView = appData.flipView;
3707                         DrawPosition(TRUE, boards[currentMove]);
3708                         DisplayBothClocks();
3709                         snprintf(str, MSG_SIZ, "%s %s %s",
3710                                 gameInfo.white, _("vs."),  gameInfo.black);
3711                         DisplayTitle(str);
3712                         gameMode = IcsIdle;
3713                     } else {
3714                         /* Moves were history of an active game */
3715                         if (gameInfo.resultDetails != NULL) {
3716                             free(gameInfo.resultDetails);
3717                             gameInfo.resultDetails = NULL;
3718                         }
3719                     }
3720                     HistorySet(parseList, backwardMostMove,
3721                                forwardMostMove, currentMove-1);
3722                     DisplayMove(currentMove - 1);
3723                     if (started == STARTED_MOVES) next_out = i;
3724                     started = STARTED_NONE;
3725                     ics_getting_history = H_FALSE;
3726                     break;
3727
3728                   case STARTED_OBSERVE:
3729                     started = STARTED_NONE;
3730                     SendToICS(ics_prefix);
3731                     SendToICS("refresh\n");
3732                     break;
3733
3734                   default:
3735                     break;
3736                 }
3737                 if(bookHit) { // [HGM] book: simulate book reply
3738                     static char bookMove[MSG_SIZ]; // a bit generous?
3739
3740                     programStats.nodes = programStats.depth = programStats.time =
3741                     programStats.score = programStats.got_only_move = 0;
3742                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3743
3744                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3745                     strcat(bookMove, bookHit);
3746                     HandleMachineMove(bookMove, &first);
3747                 }
3748                 continue;
3749             }
3750
3751             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3752                  started == STARTED_HOLDINGS ||
3753                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3754                 /* Accumulate characters in move list or board */
3755                 parse[parse_pos++] = buf[i];
3756             }
3757
3758             /* Start of game messages.  Mostly we detect start of game
3759                when the first board image arrives.  On some versions
3760                of the ICS, though, we need to do a "refresh" after starting
3761                to observe in order to get the current board right away. */
3762             if (looking_at(buf, &i, "Adding game * to observation list")) {
3763                 started = STARTED_OBSERVE;
3764                 continue;
3765             }
3766
3767             /* Handle auto-observe */
3768             if (appData.autoObserve &&
3769                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3770                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3771                 char *player;
3772                 /* Choose the player that was highlighted, if any. */
3773                 if (star_match[0][0] == '\033' ||
3774                     star_match[1][0] != '\033') {
3775                     player = star_match[0];
3776                 } else {
3777                     player = star_match[2];
3778                 }
3779                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3780                         ics_prefix, StripHighlightAndTitle(player));
3781                 SendToICS(str);
3782
3783                 /* Save ratings from notify string */
3784                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3785                 player1Rating = string_to_rating(star_match[1]);
3786                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3787                 player2Rating = string_to_rating(star_match[3]);
3788
3789                 if (appData.debugMode)
3790                   fprintf(debugFP,
3791                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3792                           player1Name, player1Rating,
3793                           player2Name, player2Rating);
3794
3795                 continue;
3796             }
3797
3798             /* Deal with automatic examine mode after a game,
3799                and with IcsObserving -> IcsExamining transition */
3800             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3801                 looking_at(buf, &i, "has made you an examiner of game *")) {
3802
3803                 int gamenum = atoi(star_match[0]);
3804                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3805                     gamenum == ics_gamenum) {
3806                     /* We were already playing or observing this game;
3807                        no need to refetch history */
3808                     gameMode = IcsExamining;
3809                     if (pausing) {
3810                         pauseExamForwardMostMove = forwardMostMove;
3811                     } else if (currentMove < forwardMostMove) {
3812                         ForwardInner(forwardMostMove);
3813                     }
3814                 } else {
3815                     /* I don't think this case really can happen */
3816                     SendToICS(ics_prefix);
3817                     SendToICS("refresh\n");
3818                 }
3819                 continue;
3820             }
3821
3822             /* Error messages */
3823 //          if (ics_user_moved) {
3824             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3825                 if (looking_at(buf, &i, "Illegal move") ||
3826                     looking_at(buf, &i, "Not a legal move") ||
3827                     looking_at(buf, &i, "Your king is in check") ||
3828                     looking_at(buf, &i, "It isn't your turn") ||
3829                     looking_at(buf, &i, "It is not your move")) {
3830                     /* Illegal move */
3831                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3832                         currentMove = forwardMostMove-1;
3833                         DisplayMove(currentMove - 1); /* before DMError */
3834                         DrawPosition(FALSE, boards[currentMove]);
3835                         SwitchClocks(forwardMostMove-1); // [HGM] race
3836                         DisplayBothClocks();
3837                     }
3838                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3839                     ics_user_moved = 0;
3840                     continue;
3841                 }
3842             }
3843
3844             if (looking_at(buf, &i, "still have time") ||
3845                 looking_at(buf, &i, "not out of time") ||
3846                 looking_at(buf, &i, "either player is out of time") ||
3847                 looking_at(buf, &i, "has timeseal; checking")) {
3848                 /* We must have called his flag a little too soon */
3849                 whiteFlag = blackFlag = FALSE;
3850                 continue;
3851             }
3852
3853             if (looking_at(buf, &i, "added * seconds to") ||
3854                 looking_at(buf, &i, "seconds were added to")) {
3855                 /* Update the clocks */
3856                 SendToICS(ics_prefix);
3857                 SendToICS("refresh\n");
3858                 continue;
3859             }
3860
3861             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3862                 ics_clock_paused = TRUE;
3863                 StopClocks();
3864                 continue;
3865             }
3866
3867             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3868                 ics_clock_paused = FALSE;
3869                 StartClocks();
3870                 continue;
3871             }
3872
3873             /* Grab player ratings from the Creating: message.
3874                Note we have to check for the special case when
3875                the ICS inserts things like [white] or [black]. */
3876             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3877                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3878                 /* star_matches:
3879                    0    player 1 name (not necessarily white)
3880                    1    player 1 rating
3881                    2    empty, white, or black (IGNORED)
3882                    3    player 2 name (not necessarily black)
3883                    4    player 2 rating
3884
3885                    The names/ratings are sorted out when the game
3886                    actually starts (below).
3887                 */
3888                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3889                 player1Rating = string_to_rating(star_match[1]);
3890                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3891                 player2Rating = string_to_rating(star_match[4]);
3892
3893                 if (appData.debugMode)
3894                   fprintf(debugFP,
3895                           "Ratings from 'Creating:' %s %d, %s %d\n",
3896                           player1Name, player1Rating,
3897                           player2Name, player2Rating);
3898
3899                 continue;
3900             }
3901
3902             /* Improved generic start/end-of-game messages */
3903             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3904                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3905                 /* If tkind == 0: */
3906                 /* star_match[0] is the game number */
3907                 /*           [1] is the white player's name */
3908                 /*           [2] is the black player's name */
3909                 /* For end-of-game: */
3910                 /*           [3] is the reason for the game end */
3911                 /*           [4] is a PGN end game-token, preceded by " " */
3912                 /* For start-of-game: */
3913                 /*           [3] begins with "Creating" or "Continuing" */
3914                 /*           [4] is " *" or empty (don't care). */
3915                 int gamenum = atoi(star_match[0]);
3916                 char *whitename, *blackname, *why, *endtoken;
3917                 ChessMove endtype = EndOfFile;
3918
3919                 if (tkind == 0) {
3920                   whitename = star_match[1];
3921                   blackname = star_match[2];
3922                   why = star_match[3];
3923                   endtoken = star_match[4];
3924                 } else {
3925                   whitename = star_match[1];
3926                   blackname = star_match[3];
3927                   why = star_match[5];
3928                   endtoken = star_match[6];
3929                 }
3930
3931                 /* Game start messages */
3932                 if (strncmp(why, "Creating ", 9) == 0 ||
3933                     strncmp(why, "Continuing ", 11) == 0) {
3934                     gs_gamenum = gamenum;
3935                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3936                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
3937                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3938 #if ZIPPY
3939                     if (appData.zippyPlay) {
3940                         ZippyGameStart(whitename, blackname);
3941                     }
3942 #endif /*ZIPPY*/
3943                     partnerBoardValid = FALSE; // [HGM] bughouse
3944                     continue;
3945                 }
3946
3947                 /* Game end messages */
3948                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3949                     ics_gamenum != gamenum) {
3950                     continue;
3951                 }
3952                 while (endtoken[0] == ' ') endtoken++;
3953                 switch (endtoken[0]) {
3954                   case '*':
3955                   default:
3956                     endtype = GameUnfinished;
3957                     break;
3958                   case '0':
3959                     endtype = BlackWins;
3960                     break;
3961                   case '1':
3962                     if (endtoken[1] == '/')
3963                       endtype = GameIsDrawn;
3964                     else
3965                       endtype = WhiteWins;
3966                     break;
3967                 }
3968                 GameEnds(endtype, why, GE_ICS);
3969 #if ZIPPY
3970                 if (appData.zippyPlay && first.initDone) {
3971                     ZippyGameEnd(endtype, why);
3972                     if (first.pr == NoProc) {
3973                       /* Start the next process early so that we'll
3974                          be ready for the next challenge */
3975                       StartChessProgram(&first);
3976                     }
3977                     /* Send "new" early, in case this command takes
3978                        a long time to finish, so that we'll be ready
3979                        for the next challenge. */
3980                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3981                     Reset(TRUE, TRUE);
3982                 }
3983 #endif /*ZIPPY*/
3984                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3985                 continue;
3986             }
3987
3988             if (looking_at(buf, &i, "Removing game * from observation") ||
3989                 looking_at(buf, &i, "no longer observing game *") ||
3990                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3991                 if (gameMode == IcsObserving &&
3992                     atoi(star_match[0]) == ics_gamenum)
3993                   {
3994                       /* icsEngineAnalyze */
3995                       if (appData.icsEngineAnalyze) {
3996                             ExitAnalyzeMode();
3997                             ModeHighlight();
3998                       }
3999                       StopClocks();
4000                       gameMode = IcsIdle;
4001                       ics_gamenum = -1;
4002                       ics_user_moved = FALSE;
4003                   }
4004                 continue;
4005             }
4006
4007             if (looking_at(buf, &i, "no longer examining game *")) {
4008                 if (gameMode == IcsExamining &&
4009                     atoi(star_match[0]) == ics_gamenum)
4010                   {
4011                       gameMode = IcsIdle;
4012                       ics_gamenum = -1;
4013                       ics_user_moved = FALSE;
4014                   }
4015                 continue;
4016             }
4017
4018             /* Advance leftover_start past any newlines we find,
4019                so only partial lines can get reparsed */
4020             if (looking_at(buf, &i, "\n")) {
4021                 prevColor = curColor;
4022                 if (curColor != ColorNormal) {
4023                     if (oldi > next_out) {
4024                         SendToPlayer(&buf[next_out], oldi - next_out);
4025                         next_out = oldi;
4026                     }
4027                     Colorize(ColorNormal, FALSE);
4028                     curColor = ColorNormal;
4029                 }
4030                 if (started == STARTED_BOARD) {
4031                     started = STARTED_NONE;
4032                     parse[parse_pos] = NULLCHAR;
4033                     ParseBoard12(parse);
4034                     ics_user_moved = 0;
4035
4036                     /* Send premove here */
4037                     if (appData.premove) {
4038                       char str[MSG_SIZ];
4039                       if (currentMove == 0 &&
4040                           gameMode == IcsPlayingWhite &&
4041                           appData.premoveWhite) {
4042                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
4043                         if (appData.debugMode)
4044                           fprintf(debugFP, "Sending premove:\n");
4045                         SendToICS(str);
4046                       } else if (currentMove == 1 &&
4047                                  gameMode == IcsPlayingBlack &&
4048                                  appData.premoveBlack) {
4049                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
4050                         if (appData.debugMode)
4051                           fprintf(debugFP, "Sending premove:\n");
4052                         SendToICS(str);
4053                       } else if (gotPremove) {
4054                         gotPremove = 0;
4055                         ClearPremoveHighlights();
4056                         if (appData.debugMode)
4057                           fprintf(debugFP, "Sending premove:\n");
4058                           UserMoveEvent(premoveFromX, premoveFromY,
4059                                         premoveToX, premoveToY,
4060                                         premovePromoChar);
4061                       }
4062                     }
4063
4064                     /* Usually suppress following prompt */
4065                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4066                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4067                         if (looking_at(buf, &i, "*% ")) {
4068                             savingComment = FALSE;
4069                             suppressKibitz = 0;
4070                         }
4071                     }
4072                     next_out = i;
4073                 } else if (started == STARTED_HOLDINGS) {
4074                     int gamenum;
4075                     char new_piece[MSG_SIZ];
4076                     started = STARTED_NONE;
4077                     parse[parse_pos] = NULLCHAR;
4078                     if (appData.debugMode)
4079                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4080                                                         parse, currentMove);
4081                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4082                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4083                         if (gameInfo.variant == VariantNormal) {
4084                           /* [HGM] We seem to switch variant during a game!
4085                            * Presumably no holdings were displayed, so we have
4086                            * to move the position two files to the right to
4087                            * create room for them!
4088                            */
4089                           VariantClass newVariant;
4090                           switch(gameInfo.boardWidth) { // base guess on board width
4091                                 case 9:  newVariant = VariantShogi; break;
4092                                 case 10: newVariant = VariantGreat; break;
4093                                 default: newVariant = VariantCrazyhouse; break;
4094                           }
4095                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4096                           /* Get a move list just to see the header, which
4097                              will tell us whether this is really bug or zh */
4098                           if (ics_getting_history == H_FALSE) {
4099                             ics_getting_history = H_REQUESTED;
4100                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4101                             SendToICS(str);
4102                           }
4103                         }
4104                         new_piece[0] = NULLCHAR;
4105                         sscanf(parse, "game %d white [%s black [%s <- %s",
4106                                &gamenum, white_holding, black_holding,
4107                                new_piece);
4108                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4109                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4110                         /* [HGM] copy holdings to board holdings area */
4111                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4112                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4113                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4114 #if ZIPPY
4115                         if (appData.zippyPlay && first.initDone) {
4116                             ZippyHoldings(white_holding, black_holding,
4117                                           new_piece);
4118                         }
4119 #endif /*ZIPPY*/
4120                         if (tinyLayout || smallLayout) {
4121                             char wh[16], bh[16];
4122                             PackHolding(wh, white_holding);
4123                             PackHolding(bh, black_holding);
4124                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4125                                     gameInfo.white, gameInfo.black);
4126                         } else {
4127                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4128                                     gameInfo.white, white_holding, _("vs."),
4129                                     gameInfo.black, black_holding);
4130                         }
4131                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4132                         DrawPosition(FALSE, boards[currentMove]);
4133                         DisplayTitle(str);
4134                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4135                         sscanf(parse, "game %d white [%s black [%s <- %s",
4136                                &gamenum, white_holding, black_holding,
4137                                new_piece);
4138                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4139                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4140                         /* [HGM] copy holdings to partner-board holdings area */
4141                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4142                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4143                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4144                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4145                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4146                       }
4147                     }
4148                     /* Suppress following prompt */
4149                     if (looking_at(buf, &i, "*% ")) {
4150                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4151                         savingComment = FALSE;
4152                         suppressKibitz = 0;
4153                     }
4154                     next_out = i;
4155                 }
4156                 continue;
4157             }
4158
4159             i++;                /* skip unparsed character and loop back */
4160         }
4161
4162         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4163 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4164 //          SendToPlayer(&buf[next_out], i - next_out);
4165             started != STARTED_HOLDINGS && leftover_start > next_out) {
4166             SendToPlayer(&buf[next_out], leftover_start - next_out);
4167             next_out = i;
4168         }
4169
4170         leftover_len = buf_len - leftover_start;
4171         /* if buffer ends with something we couldn't parse,
4172            reparse it after appending the next read */
4173
4174     } else if (count == 0) {
4175         RemoveInputSource(isr);
4176         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4177     } else {
4178         DisplayFatalError(_("Error reading from ICS"), error, 1);
4179     }
4180 }
4181
4182
4183 /* Board style 12 looks like this:
4184
4185    <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
4186
4187  * The "<12> " is stripped before it gets to this routine.  The two
4188  * trailing 0's (flip state and clock ticking) are later addition, and
4189  * some chess servers may not have them, or may have only the first.
4190  * Additional trailing fields may be added in the future.
4191  */
4192
4193 #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"
4194
4195 #define RELATION_OBSERVING_PLAYED    0
4196 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4197 #define RELATION_PLAYING_MYMOVE      1
4198 #define RELATION_PLAYING_NOTMYMOVE  -1
4199 #define RELATION_EXAMINING           2
4200 #define RELATION_ISOLATED_BOARD     -3
4201 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4202
4203 void
4204 ParseBoard12 (char *string)
4205 {
4206 #if ZIPPY
4207     int i, takeback;
4208     char *bookHit = NULL; // [HGM] book
4209 #endif
4210     GameMode newGameMode;
4211     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
4212     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
4213     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4214     char to_play, board_chars[200];
4215     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4216     char black[32], white[32];
4217     Board board;
4218     int prevMove = currentMove;
4219     int ticking = 2;
4220     ChessMove moveType;
4221     int fromX, fromY, toX, toY;
4222     char promoChar;
4223     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4224     Boolean weird = FALSE, reqFlag = FALSE;
4225
4226     fromX = fromY = toX = toY = -1;
4227
4228     newGame = FALSE;
4229
4230     if (appData.debugMode)
4231       fprintf(debugFP, "Parsing board: %s\n", string);
4232
4233     move_str[0] = NULLCHAR;
4234     elapsed_time[0] = NULLCHAR;
4235     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4236         int  i = 0, j;
4237         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4238             if(string[i] == ' ') { ranks++; files = 0; }
4239             else files++;
4240             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4241             i++;
4242         }
4243         for(j = 0; j <i; j++) board_chars[j] = string[j];
4244         board_chars[i] = '\0';
4245         string += i + 1;
4246     }
4247     n = sscanf(string, PATTERN, &to_play, &double_push,
4248                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4249                &gamenum, white, black, &relation, &basetime, &increment,
4250                &white_stren, &black_stren, &white_time, &black_time,
4251                &moveNum, str, elapsed_time, move_str, &ics_flip,
4252                &ticking);
4253
4254     if (n < 21) {
4255         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4256         DisplayError(str, 0);
4257         return;
4258     }
4259
4260     /* Convert the move number to internal form */
4261     moveNum = (moveNum - 1) * 2;
4262     if (to_play == 'B') moveNum++;
4263     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4264       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4265                         0, 1);
4266       return;
4267     }
4268
4269     switch (relation) {
4270       case RELATION_OBSERVING_PLAYED:
4271       case RELATION_OBSERVING_STATIC:
4272         if (gamenum == -1) {
4273             /* Old ICC buglet */
4274             relation = RELATION_OBSERVING_STATIC;
4275         }
4276         newGameMode = IcsObserving;
4277         break;
4278       case RELATION_PLAYING_MYMOVE:
4279       case RELATION_PLAYING_NOTMYMOVE:
4280         newGameMode =
4281           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4282             IcsPlayingWhite : IcsPlayingBlack;
4283         soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4284         break;
4285       case RELATION_EXAMINING:
4286         newGameMode = IcsExamining;
4287         break;
4288       case RELATION_ISOLATED_BOARD:
4289       default:
4290         /* Just display this board.  If user was doing something else,
4291            we will forget about it until the next board comes. */
4292         newGameMode = IcsIdle;
4293         break;
4294       case RELATION_STARTING_POSITION:
4295         newGameMode = gameMode;
4296         break;
4297     }
4298
4299     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4300         gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4301          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4302       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4303       int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4304       static int lastBgGame = -1;
4305       char *toSqr;
4306       for (k = 0; k < ranks; k++) {
4307         for (j = 0; j < files; j++)
4308           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4309         if(gameInfo.holdingsWidth > 1) {
4310              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4311              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4312         }
4313       }
4314       CopyBoard(partnerBoard, board);
4315       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4316         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4317         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4318       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4319       if(toSqr = strchr(str, '-')) {
4320         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4321         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4322       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4323       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4324       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4325       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4326       if(twoBoards) {
4327           DisplayWhiteClock(white_time*fac, to_play == 'W');
4328           DisplayBlackClock(black_time*fac, to_play != 'W');
4329           activePartner = to_play;
4330           if(gamenum != lastBgGame) {
4331               char buf[MSG_SIZ];
4332               snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4333               DisplayTitle(buf);
4334           }
4335           lastBgGame = gamenum;
4336           activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4337                       partnerUp = 0; flipView = !flipView; } // [HGM] dual
4338       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4339                  (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4340       if(!twoBoards) DisplayMessage(partnerStatus, "");
4341         partnerBoardValid = TRUE;
4342       return;
4343     }
4344
4345     if(appData.dualBoard && appData.bgObserve) {
4346         if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4347             SendToICS(ics_prefix), SendToICS("pobserve\n");
4348         else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4349             char buf[MSG_SIZ];
4350             snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4351             SendToICS(buf);
4352         }
4353     }
4354
4355     /* Modify behavior for initial board display on move listing
4356        of wild games.
4357        */
4358     switch (ics_getting_history) {
4359       case H_FALSE:
4360       case H_REQUESTED:
4361         break;
4362       case H_GOT_REQ_HEADER:
4363       case H_GOT_UNREQ_HEADER:
4364         /* This is the initial position of the current game */
4365         gamenum = ics_gamenum;
4366         moveNum = 0;            /* old ICS bug workaround */
4367         if (to_play == 'B') {
4368           startedFromSetupPosition = TRUE;
4369           blackPlaysFirst = TRUE;
4370           moveNum = 1;
4371           if (forwardMostMove == 0) forwardMostMove = 1;
4372           if (backwardMostMove == 0) backwardMostMove = 1;
4373           if (currentMove == 0) currentMove = 1;
4374         }
4375         newGameMode = gameMode;
4376         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4377         break;
4378       case H_GOT_UNWANTED_HEADER:
4379         /* This is an initial board that we don't want */
4380         return;
4381       case H_GETTING_MOVES:
4382         /* Should not happen */
4383         DisplayError(_("Error gathering move list: extra board"), 0);
4384         ics_getting_history = H_FALSE;
4385         return;
4386     }
4387
4388    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4389                                         move_str[1] == '@' && !gameInfo.holdingsWidth ||
4390                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4391      /* [HGM] We seem to have switched variant unexpectedly
4392       * Try to guess new variant from board size
4393       */
4394           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4395           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4396           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4397           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4398           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4399           if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4400           if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4401           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4402           /* Get a move list just to see the header, which
4403              will tell us whether this is really bug or zh */
4404           if (ics_getting_history == H_FALSE) {
4405             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4406             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4407             SendToICS(str);
4408           }
4409     }
4410
4411     /* Take action if this is the first board of a new game, or of a
4412        different game than is currently being displayed.  */
4413     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4414         relation == RELATION_ISOLATED_BOARD) {
4415
4416         /* Forget the old game and get the history (if any) of the new one */
4417         if (gameMode != BeginningOfGame) {
4418           Reset(TRUE, TRUE);
4419         }
4420         newGame = TRUE;
4421         if (appData.autoRaiseBoard) BoardToTop();
4422         prevMove = -3;
4423         if (gamenum == -1) {
4424             newGameMode = IcsIdle;
4425         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4426                    appData.getMoveList && !reqFlag) {
4427             /* Need to get game history */
4428             ics_getting_history = H_REQUESTED;
4429             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4430             SendToICS(str);
4431         }
4432
4433         /* Initially flip the board to have black on the bottom if playing
4434            black or if the ICS flip flag is set, but let the user change
4435            it with the Flip View button. */
4436         flipView = appData.autoFlipView ?
4437           (newGameMode == IcsPlayingBlack) || ics_flip :
4438           appData.flipView;
4439
4440         /* Done with values from previous mode; copy in new ones */
4441         gameMode = newGameMode;
4442         ModeHighlight();
4443         ics_gamenum = gamenum;
4444         if (gamenum == gs_gamenum) {
4445             int klen = strlen(gs_kind);
4446             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4447             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4448             gameInfo.event = StrSave(str);
4449         } else {
4450             gameInfo.event = StrSave("ICS game");
4451         }
4452         gameInfo.site = StrSave(appData.icsHost);
4453         gameInfo.date = PGNDate();
4454         gameInfo.round = StrSave("-");
4455         gameInfo.white = StrSave(white);
4456         gameInfo.black = StrSave(black);
4457         timeControl = basetime * 60 * 1000;
4458         timeControl_2 = 0;
4459         timeIncrement = increment * 1000;
4460         movesPerSession = 0;
4461         gameInfo.timeControl = TimeControlTagValue();
4462         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4463   if (appData.debugMode) {
4464     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4465     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4466     setbuf(debugFP, NULL);
4467   }
4468
4469         gameInfo.outOfBook = NULL;
4470
4471         /* Do we have the ratings? */
4472         if (strcmp(player1Name, white) == 0 &&
4473             strcmp(player2Name, black) == 0) {
4474             if (appData.debugMode)
4475               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4476                       player1Rating, player2Rating);
4477             gameInfo.whiteRating = player1Rating;
4478             gameInfo.blackRating = player2Rating;
4479         } else if (strcmp(player2Name, white) == 0 &&
4480                    strcmp(player1Name, black) == 0) {
4481             if (appData.debugMode)
4482               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4483                       player2Rating, player1Rating);
4484             gameInfo.whiteRating = player2Rating;
4485             gameInfo.blackRating = player1Rating;
4486         }
4487         player1Name[0] = player2Name[0] = NULLCHAR;
4488
4489         /* Silence shouts if requested */
4490         if (appData.quietPlay &&
4491             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4492             SendToICS(ics_prefix);
4493             SendToICS("set shout 0\n");
4494         }
4495     }
4496
4497     /* Deal with midgame name changes */
4498     if (!newGame) {
4499         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4500             if (gameInfo.white) free(gameInfo.white);
4501             gameInfo.white = StrSave(white);
4502         }
4503         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4504             if (gameInfo.black) free(gameInfo.black);
4505             gameInfo.black = StrSave(black);
4506         }
4507     }
4508
4509     /* Throw away game result if anything actually changes in examine mode */
4510     if (gameMode == IcsExamining && !newGame) {
4511         gameInfo.result = GameUnfinished;
4512         if (gameInfo.resultDetails != NULL) {
4513             free(gameInfo.resultDetails);
4514             gameInfo.resultDetails = NULL;
4515         }
4516     }
4517
4518     /* In pausing && IcsExamining mode, we ignore boards coming
4519        in if they are in a different variation than we are. */
4520     if (pauseExamInvalid) return;
4521     if (pausing && gameMode == IcsExamining) {
4522         if (moveNum <= pauseExamForwardMostMove) {
4523             pauseExamInvalid = TRUE;
4524             forwardMostMove = pauseExamForwardMostMove;
4525             return;
4526         }
4527     }
4528
4529   if (appData.debugMode) {
4530     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4531   }
4532     /* Parse the board */
4533     for (k = 0; k < ranks; k++) {
4534       for (j = 0; j < files; j++)
4535         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4536       if(gameInfo.holdingsWidth > 1) {
4537            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4538            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4539       }
4540     }
4541     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4542       board[5][BOARD_RGHT+1] = WhiteAngel;
4543       board[6][BOARD_RGHT+1] = WhiteMarshall;
4544       board[1][0] = BlackMarshall;
4545       board[2][0] = BlackAngel;
4546       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4547     }
4548     CopyBoard(boards[moveNum], board);
4549     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4550     if (moveNum == 0) {
4551         startedFromSetupPosition =
4552           !CompareBoards(board, initialPosition);
4553         if(startedFromSetupPosition)
4554             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4555     }
4556
4557     /* [HGM] Set castling rights. Take the outermost Rooks,
4558        to make it also work for FRC opening positions. Note that board12
4559        is really defective for later FRC positions, as it has no way to
4560        indicate which Rook can castle if they are on the same side of King.
4561        For the initial position we grant rights to the outermost Rooks,
4562        and remember thos rights, and we then copy them on positions
4563        later in an FRC game. This means WB might not recognize castlings with
4564        Rooks that have moved back to their original position as illegal,
4565        but in ICS mode that is not its job anyway.
4566     */
4567     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4568     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4569
4570         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4571             if(board[0][i] == WhiteRook) j = i;
4572         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4573         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4574             if(board[0][i] == WhiteRook) j = i;
4575         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4576         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4577             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4578         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4579         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4580             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4581         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4582
4583         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4584         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4585         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4586             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4587         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4588             if(board[BOARD_HEIGHT-1][k] == bKing)
4589                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4590         if(gameInfo.variant == VariantTwoKings) {
4591             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4592             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4593             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4594         }
4595     } else { int r;
4596         r = boards[moveNum][CASTLING][0] = initialRights[0];
4597         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4598         r = boards[moveNum][CASTLING][1] = initialRights[1];
4599         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4600         r = boards[moveNum][CASTLING][3] = initialRights[3];
4601         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4602         r = boards[moveNum][CASTLING][4] = initialRights[4];
4603         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4604         /* wildcastle kludge: always assume King has rights */
4605         r = boards[moveNum][CASTLING][2] = initialRights[2];
4606         r = boards[moveNum][CASTLING][5] = initialRights[5];
4607     }
4608     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4609     boards[moveNum][EP_STATUS] = EP_NONE;
4610     if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4611     if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4612     if(double_push !=  -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4613
4614
4615     if (ics_getting_history == H_GOT_REQ_HEADER ||
4616         ics_getting_history == H_GOT_UNREQ_HEADER) {
4617         /* This was an initial position from a move list, not
4618            the current position */
4619         return;
4620     }
4621
4622     /* Update currentMove and known move number limits */
4623     newMove = newGame || moveNum > forwardMostMove;
4624
4625     if (newGame) {
4626         forwardMostMove = backwardMostMove = currentMove = moveNum;
4627         if (gameMode == IcsExamining && moveNum == 0) {
4628           /* Workaround for ICS limitation: we are not told the wild
4629              type when starting to examine a game.  But if we ask for
4630              the move list, the move list header will tell us */
4631             ics_getting_history = H_REQUESTED;
4632             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4633             SendToICS(str);
4634         }
4635     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4636                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4637 #if ZIPPY
4638         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4639         /* [HGM] applied this also to an engine that is silently watching        */
4640         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4641             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4642             gameInfo.variant == currentlyInitializedVariant) {
4643           takeback = forwardMostMove - moveNum;
4644           for (i = 0; i < takeback; i++) {
4645             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4646             SendToProgram("undo\n", &first);
4647           }
4648         }
4649 #endif
4650
4651         forwardMostMove = moveNum;
4652         if (!pausing || currentMove > forwardMostMove)
4653           currentMove = forwardMostMove;
4654     } else {
4655         /* New part of history that is not contiguous with old part */
4656         if (pausing && gameMode == IcsExamining) {
4657             pauseExamInvalid = TRUE;
4658             forwardMostMove = pauseExamForwardMostMove;
4659             return;
4660         }
4661         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4662 #if ZIPPY
4663             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4664                 // [HGM] when we will receive the move list we now request, it will be
4665                 // fed to the engine from the first move on. So if the engine is not
4666                 // in the initial position now, bring it there.
4667                 InitChessProgram(&first, 0);
4668             }
4669 #endif
4670             ics_getting_history = H_REQUESTED;
4671             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4672             SendToICS(str);
4673         }
4674         forwardMostMove = backwardMostMove = currentMove = moveNum;
4675     }
4676
4677     /* Update the clocks */
4678     if (strchr(elapsed_time, '.')) {
4679       /* Time is in ms */
4680       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4681       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4682     } else {
4683       /* Time is in seconds */
4684       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4685       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4686     }
4687
4688
4689 #if ZIPPY
4690     if (appData.zippyPlay && newGame &&
4691         gameMode != IcsObserving && gameMode != IcsIdle &&
4692         gameMode != IcsExamining)
4693       ZippyFirstBoard(moveNum, basetime, increment);
4694 #endif
4695
4696     /* Put the move on the move list, first converting
4697        to canonical algebraic form. */
4698     if (moveNum > 0) {
4699   if (appData.debugMode) {
4700     int f = forwardMostMove;
4701     fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4702             boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4703             boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4704     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4705     fprintf(debugFP, "moveNum = %d\n", moveNum);
4706     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4707     setbuf(debugFP, NULL);
4708   }
4709         if (moveNum <= backwardMostMove) {
4710             /* We don't know what the board looked like before
4711                this move.  Punt. */
4712           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4713             strcat(parseList[moveNum - 1], " ");
4714             strcat(parseList[moveNum - 1], elapsed_time);
4715             moveList[moveNum - 1][0] = NULLCHAR;
4716         } else if (strcmp(move_str, "none") == 0) {
4717             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4718             /* Again, we don't know what the board looked like;
4719                this is really the start of the game. */
4720             parseList[moveNum - 1][0] = NULLCHAR;
4721             moveList[moveNum - 1][0] = NULLCHAR;
4722             backwardMostMove = moveNum;
4723             startedFromSetupPosition = TRUE;
4724             fromX = fromY = toX = toY = -1;
4725         } else {
4726           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4727           //                 So we parse the long-algebraic move string in stead of the SAN move
4728           int valid; char buf[MSG_SIZ], *prom;
4729
4730           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4731                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4732           // str looks something like "Q/a1-a2"; kill the slash
4733           if(str[1] == '/')
4734             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4735           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4736           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4737                 strcat(buf, prom); // long move lacks promo specification!
4738           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4739                 if(appData.debugMode)
4740                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4741                 safeStrCpy(move_str, buf, MSG_SIZ);
4742           }
4743           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4744                                 &fromX, &fromY, &toX, &toY, &promoChar)
4745                || ParseOneMove(buf, moveNum - 1, &moveType,
4746                                 &fromX, &fromY, &toX, &toY, &promoChar);
4747           // end of long SAN patch
4748           if (valid) {
4749             (void) CoordsToAlgebraic(boards[moveNum - 1],
4750                                      PosFlags(moveNum - 1),
4751                                      fromY, fromX, toY, toX, promoChar,
4752                                      parseList[moveNum-1]);
4753             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4754               case MT_NONE:
4755               case MT_STALEMATE:
4756               default:
4757                 break;
4758               case MT_CHECK:
4759                 if(gameInfo.variant != VariantShogi)
4760                     strcat(parseList[moveNum - 1], "+");
4761                 break;
4762               case MT_CHECKMATE:
4763               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4764                 strcat(parseList[moveNum - 1], "#");
4765                 break;
4766             }
4767             strcat(parseList[moveNum - 1], " ");
4768             strcat(parseList[moveNum - 1], elapsed_time);
4769             /* currentMoveString is set as a side-effect of ParseOneMove */
4770             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4771             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4772             strcat(moveList[moveNum - 1], "\n");
4773
4774             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4775                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4776               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4777                 ChessSquare old, new = boards[moveNum][k][j];
4778                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4779                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4780                   if(old == new) continue;
4781                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4782                   else if(new == WhiteWazir || new == BlackWazir) {
4783                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4784                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4785                       else boards[moveNum][k][j] = old; // preserve type of Gold
4786                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4787                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4788               }
4789           } else {
4790             /* Move from ICS was illegal!?  Punt. */
4791             if (appData.debugMode) {
4792               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4793               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4794             }
4795             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4796             strcat(parseList[moveNum - 1], " ");
4797             strcat(parseList[moveNum - 1], elapsed_time);
4798             moveList[moveNum - 1][0] = NULLCHAR;
4799             fromX = fromY = toX = toY = -1;
4800           }
4801         }
4802   if (appData.debugMode) {
4803     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4804     setbuf(debugFP, NULL);
4805   }
4806
4807 #if ZIPPY
4808         /* Send move to chess program (BEFORE animating it). */
4809         if (appData.zippyPlay && !newGame && newMove &&
4810            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4811
4812             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4813                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4814                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4815                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4816                             move_str);
4817                     DisplayError(str, 0);
4818                 } else {
4819                     if (first.sendTime) {
4820                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4821                     }
4822                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4823                     if (firstMove && !bookHit) {
4824                         firstMove = FALSE;
4825                         if (first.useColors) {
4826                           SendToProgram(gameMode == IcsPlayingWhite ?
4827                                         "white\ngo\n" :
4828                                         "black\ngo\n", &first);
4829                         } else {
4830                           SendToProgram("go\n", &first);
4831                         }
4832                         first.maybeThinking = TRUE;
4833                     }
4834                 }
4835             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4836               if (moveList[moveNum - 1][0] == NULLCHAR) {
4837                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4838                 DisplayError(str, 0);
4839               } else {
4840                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4841                 SendMoveToProgram(moveNum - 1, &first);
4842               }
4843             }
4844         }
4845 #endif
4846     }
4847
4848     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4849         /* If move comes from a remote source, animate it.  If it
4850            isn't remote, it will have already been animated. */
4851         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4852             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4853         }
4854         if (!pausing && appData.highlightLastMove) {
4855             SetHighlights(fromX, fromY, toX, toY);
4856         }
4857     }
4858
4859     /* Start the clocks */
4860     whiteFlag = blackFlag = FALSE;
4861     appData.clockMode = !(basetime == 0 && increment == 0);
4862     if (ticking == 0) {
4863       ics_clock_paused = TRUE;
4864       StopClocks();
4865     } else if (ticking == 1) {
4866       ics_clock_paused = FALSE;
4867     }
4868     if (gameMode == IcsIdle ||
4869         relation == RELATION_OBSERVING_STATIC ||
4870         relation == RELATION_EXAMINING ||
4871         ics_clock_paused)
4872       DisplayBothClocks();
4873     else
4874       StartClocks();
4875
4876     /* Display opponents and material strengths */
4877     if (gameInfo.variant != VariantBughouse &&
4878         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4879         if (tinyLayout || smallLayout) {
4880             if(gameInfo.variant == VariantNormal)
4881               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4882                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4883                     basetime, increment);
4884             else
4885               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4886                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4887                     basetime, increment, (int) gameInfo.variant);
4888         } else {
4889             if(gameInfo.variant == VariantNormal)
4890               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4891                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4892                     basetime, increment);
4893             else
4894               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
4895                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4896                     basetime, increment, VariantName(gameInfo.variant));
4897         }
4898         DisplayTitle(str);
4899   if (appData.debugMode) {
4900     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4901   }
4902     }
4903
4904
4905     /* Display the board */
4906     if (!pausing && !appData.noGUI) {
4907
4908       if (appData.premove)
4909           if (!gotPremove ||
4910              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4911              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4912               ClearPremoveHighlights();
4913
4914       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4915         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4916       DrawPosition(j, boards[currentMove]);
4917
4918       DisplayMove(moveNum - 1);
4919       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4920             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4921               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4922         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4923       }
4924     }
4925
4926     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4927 #if ZIPPY
4928     if(bookHit) { // [HGM] book: simulate book reply
4929         static char bookMove[MSG_SIZ]; // a bit generous?
4930
4931         programStats.nodes = programStats.depth = programStats.time =
4932         programStats.score = programStats.got_only_move = 0;
4933         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4934
4935         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4936         strcat(bookMove, bookHit);
4937         HandleMachineMove(bookMove, &first);
4938     }
4939 #endif
4940 }
4941
4942 void
4943 GetMoveListEvent ()
4944 {
4945     char buf[MSG_SIZ];
4946     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4947         ics_getting_history = H_REQUESTED;
4948         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4949         SendToICS(buf);
4950     }
4951 }
4952
4953 void
4954 SendToBoth (char *msg)
4955 {   // to make it easy to keep two engines in step in dual analysis
4956     SendToProgram(msg, &first);
4957     if(second.analyzing) SendToProgram(msg, &second);
4958 }
4959
4960 void
4961 AnalysisPeriodicEvent (int force)
4962 {
4963     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4964          && !force) || !appData.periodicUpdates)
4965       return;
4966
4967     /* Send . command to Crafty to collect stats */
4968     SendToBoth(".\n");
4969
4970     /* Don't send another until we get a response (this makes
4971        us stop sending to old Crafty's which don't understand
4972        the "." command (sending illegal cmds resets node count & time,
4973        which looks bad)) */
4974     programStats.ok_to_send = 0;
4975 }
4976
4977 void
4978 ics_update_width (int new_width)
4979 {
4980         ics_printf("set width %d\n", new_width);
4981 }
4982
4983 void
4984 SendMoveToProgram (int moveNum, ChessProgramState *cps)
4985 {
4986     char buf[MSG_SIZ];
4987
4988     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
4989         // null move in variant where engine does not understand it (for analysis purposes)
4990         SendBoard(cps, moveNum + 1); // send position after move in stead.
4991         return;
4992     }
4993     if (cps->useUsermove) {
4994       SendToProgram("usermove ", cps);
4995     }
4996     if (cps->useSAN) {
4997       char *space;
4998       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4999         int len = space - parseList[moveNum];
5000         memcpy(buf, parseList[moveNum], len);
5001         buf[len++] = '\n';
5002         buf[len] = NULLCHAR;
5003       } else {
5004         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
5005       }
5006       SendToProgram(buf, cps);
5007     } else {
5008       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
5009         AlphaRank(moveList[moveNum], 4);
5010         SendToProgram(moveList[moveNum], cps);
5011         AlphaRank(moveList[moveNum], 4); // and back
5012       } else
5013       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
5014        * the engine. It would be nice to have a better way to identify castle
5015        * moves here. */
5016       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
5017                                                                          && cps->useOOCastle) {
5018         int fromX = moveList[moveNum][0] - AAA;
5019         int fromY = moveList[moveNum][1] - ONE;
5020         int toX = moveList[moveNum][2] - AAA;
5021         int toY = moveList[moveNum][3] - ONE;
5022         if((boards[moveNum][fromY][fromX] == WhiteKing
5023             && boards[moveNum][toY][toX] == WhiteRook)
5024            || (boards[moveNum][fromY][fromX] == BlackKing
5025                && boards[moveNum][toY][toX] == BlackRook)) {
5026           if(toX > fromX) SendToProgram("O-O\n", cps);
5027           else SendToProgram("O-O-O\n", cps);
5028         }
5029         else SendToProgram(moveList[moveNum], cps);
5030       } else
5031       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5032         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5033           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5034           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5035                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5036         } else
5037           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5038                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5039         SendToProgram(buf, cps);
5040       }
5041       else SendToProgram(moveList[moveNum], cps);
5042       /* End of additions by Tord */
5043     }
5044
5045     /* [HGM] setting up the opening has brought engine in force mode! */
5046     /*       Send 'go' if we are in a mode where machine should play. */
5047     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5048         (gameMode == TwoMachinesPlay   ||
5049 #if ZIPPY
5050          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
5051 #endif
5052          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5053         SendToProgram("go\n", cps);
5054   if (appData.debugMode) {
5055     fprintf(debugFP, "(extra)\n");
5056   }
5057     }
5058     setboardSpoiledMachineBlack = 0;
5059 }
5060
5061 void
5062 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5063 {
5064     char user_move[MSG_SIZ];
5065     char suffix[4];
5066
5067     if(gameInfo.variant == VariantSChess && promoChar) {
5068         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5069         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5070     } else suffix[0] = NULLCHAR;
5071
5072     switch (moveType) {
5073       default:
5074         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5075                 (int)moveType, fromX, fromY, toX, toY);
5076         DisplayError(user_move + strlen("say "), 0);
5077         break;
5078       case WhiteKingSideCastle:
5079       case BlackKingSideCastle:
5080       case WhiteQueenSideCastleWild:
5081       case BlackQueenSideCastleWild:
5082       /* PUSH Fabien */
5083       case WhiteHSideCastleFR:
5084       case BlackHSideCastleFR:
5085       /* POP Fabien */
5086         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5087         break;
5088       case WhiteQueenSideCastle:
5089       case BlackQueenSideCastle:
5090       case WhiteKingSideCastleWild:
5091       case BlackKingSideCastleWild:
5092       /* PUSH Fabien */
5093       case WhiteASideCastleFR:
5094       case BlackASideCastleFR:
5095       /* POP Fabien */
5096         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5097         break;
5098       case WhiteNonPromotion:
5099       case BlackNonPromotion:
5100         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5101         break;
5102       case WhitePromotion:
5103       case BlackPromotion:
5104         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
5105            gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
5106           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5107                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5108                 PieceToChar(WhiteFerz));
5109         else if(gameInfo.variant == VariantGreat)
5110           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5111                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5112                 PieceToChar(WhiteMan));
5113         else
5114           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5115                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5116                 promoChar);
5117         break;
5118       case WhiteDrop:
5119       case BlackDrop:
5120       drop:
5121         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5122                  ToUpper(PieceToChar((ChessSquare) fromX)),
5123                  AAA + toX, ONE + toY);
5124         break;
5125       case IllegalMove:  /* could be a variant we don't quite understand */
5126         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5127       case NormalMove:
5128       case WhiteCapturesEnPassant:
5129       case BlackCapturesEnPassant:
5130         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5131                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5132         break;
5133     }
5134     SendToICS(user_move);
5135     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5136         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5137 }
5138
5139 void
5140 UploadGameEvent ()
5141 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5142     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5143     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5144     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5145       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5146       return;
5147     }
5148     if(gameMode != IcsExamining) { // is this ever not the case?
5149         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5150
5151         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5152           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5153         } else { // on FICS we must first go to general examine mode
5154           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5155         }
5156         if(gameInfo.variant != VariantNormal) {
5157             // try figure out wild number, as xboard names are not always valid on ICS
5158             for(i=1; i<=36; i++) {
5159               snprintf(buf, MSG_SIZ, "wild/%d", i);
5160                 if(StringToVariant(buf) == gameInfo.variant) break;
5161             }
5162             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5163             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5164             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5165         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5166         SendToICS(ics_prefix);
5167         SendToICS(buf);
5168         if(startedFromSetupPosition || backwardMostMove != 0) {
5169           fen = PositionToFEN(backwardMostMove, NULL, 1);
5170           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5171             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5172             SendToICS(buf);
5173           } else { // FICS: everything has to set by separate bsetup commands
5174             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5175             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5176             SendToICS(buf);
5177             if(!WhiteOnMove(backwardMostMove)) {
5178                 SendToICS("bsetup tomove black\n");
5179             }
5180             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5181             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5182             SendToICS(buf);
5183             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5184             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5185             SendToICS(buf);
5186             i = boards[backwardMostMove][EP_STATUS];
5187             if(i >= 0) { // set e.p.
5188               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5189                 SendToICS(buf);
5190             }
5191             bsetup++;
5192           }
5193         }
5194       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5195             SendToICS("bsetup done\n"); // switch to normal examining.
5196     }
5197     for(i = backwardMostMove; i<last; i++) {
5198         char buf[20];
5199         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5200         if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5201             int len = strlen(moveList[i]);
5202             snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5203             if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5204         }
5205         SendToICS(buf);
5206     }
5207     SendToICS(ics_prefix);
5208     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5209 }
5210
5211 void
5212 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5213 {
5214     if (rf == DROP_RANK) {
5215       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5216       sprintf(move, "%c@%c%c\n",
5217                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5218     } else {
5219         if (promoChar == 'x' || promoChar == NULLCHAR) {
5220           sprintf(move, "%c%c%c%c\n",
5221                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5222         } else {
5223             sprintf(move, "%c%c%c%c%c\n",
5224                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5225         }
5226     }
5227 }
5228
5229 void
5230 ProcessICSInitScript (FILE *f)
5231 {
5232     char buf[MSG_SIZ];
5233
5234     while (fgets(buf, MSG_SIZ, f)) {
5235         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5236     }
5237
5238     fclose(f);
5239 }
5240
5241
5242 static int lastX, lastY, lastLeftX, lastLeftY, selectFlag, dragging;
5243 static ClickType lastClickType;
5244
5245 void
5246 Sweep (int step)
5247 {
5248     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5249     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5250     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5251     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5252     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5253     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5254     do {
5255         promoSweep -= step;
5256         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5257         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5258         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5259         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5260         if(!step) step = -1;
5261     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5262             appData.testLegality && (promoSweep == king ||
5263             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5264     if(toX >= 0) {
5265         int victim = boards[currentMove][toY][toX];
5266         boards[currentMove][toY][toX] = promoSweep;
5267         DrawPosition(FALSE, boards[currentMove]);
5268         boards[currentMove][toY][toX] = victim;
5269     } else
5270     ChangeDragPiece(promoSweep);
5271 }
5272
5273 int
5274 PromoScroll (int x, int y)
5275 {
5276   int step = 0;
5277
5278   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5279   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5280   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5281   if(!step) return FALSE;
5282   lastX = x; lastY = y;
5283   if((promoSweep < BlackPawn) == flipView) step = -step;
5284   if(step > 0) selectFlag = 1;
5285   if(!selectFlag) Sweep(step);
5286   return FALSE;
5287 }
5288
5289 void
5290 NextPiece (int step)
5291 {
5292     ChessSquare piece = boards[currentMove][toY][toX];
5293     do {
5294         pieceSweep -= step;
5295         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5296         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5297         if(!step) step = -1;
5298     } while(PieceToChar(pieceSweep) == '.');
5299     boards[currentMove][toY][toX] = pieceSweep;
5300     DrawPosition(FALSE, boards[currentMove]);
5301     boards[currentMove][toY][toX] = piece;
5302 }
5303 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5304 void
5305 AlphaRank (char *move, int n)
5306 {
5307 //    char *p = move, c; int x, y;
5308
5309     if (appData.debugMode) {
5310         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5311     }
5312
5313     if(move[1]=='*' &&
5314        move[2]>='0' && move[2]<='9' &&
5315        move[3]>='a' && move[3]<='x'    ) {
5316         move[1] = '@';
5317         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5318         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5319     } else
5320     if(move[0]>='0' && move[0]<='9' &&
5321        move[1]>='a' && move[1]<='x' &&
5322        move[2]>='0' && move[2]<='9' &&
5323        move[3]>='a' && move[3]<='x'    ) {
5324         /* input move, Shogi -> normal */
5325         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5326         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5327         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5328         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5329     } else
5330     if(move[1]=='@' &&
5331        move[3]>='0' && move[3]<='9' &&
5332        move[2]>='a' && move[2]<='x'    ) {
5333         move[1] = '*';
5334         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5335         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5336     } else
5337     if(
5338        move[0]>='a' && move[0]<='x' &&
5339        move[3]>='0' && move[3]<='9' &&
5340        move[2]>='a' && move[2]<='x'    ) {
5341          /* output move, normal -> Shogi */
5342         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5343         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5344         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5345         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5346         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5347     }
5348     if (appData.debugMode) {
5349         fprintf(debugFP, "   out = '%s'\n", move);
5350     }
5351 }
5352
5353 char yy_textstr[8000];
5354
5355 /* Parser for moves from gnuchess, ICS, or user typein box */
5356 Boolean
5357 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5358 {
5359     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5360
5361     switch (*moveType) {
5362       case WhitePromotion:
5363       case BlackPromotion:
5364       case WhiteNonPromotion:
5365       case BlackNonPromotion:
5366       case NormalMove:
5367       case WhiteCapturesEnPassant:
5368       case BlackCapturesEnPassant:
5369       case WhiteKingSideCastle:
5370       case WhiteQueenSideCastle:
5371       case BlackKingSideCastle:
5372       case BlackQueenSideCastle:
5373       case WhiteKingSideCastleWild:
5374       case WhiteQueenSideCastleWild:
5375       case BlackKingSideCastleWild:
5376       case BlackQueenSideCastleWild:
5377       /* Code added by Tord: */
5378       case WhiteHSideCastleFR:
5379       case WhiteASideCastleFR:
5380       case BlackHSideCastleFR:
5381       case BlackASideCastleFR:
5382       /* End of code added by Tord */
5383       case IllegalMove:         /* bug or odd chess variant */
5384         *fromX = currentMoveString[0] - AAA;
5385         *fromY = currentMoveString[1] - ONE;
5386         *toX = currentMoveString[2] - AAA;
5387         *toY = currentMoveString[3] - ONE;
5388         *promoChar = currentMoveString[4];
5389         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5390             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5391     if (appData.debugMode) {
5392         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5393     }
5394             *fromX = *fromY = *toX = *toY = 0;
5395             return FALSE;
5396         }
5397         if (appData.testLegality) {
5398           return (*moveType != IllegalMove);
5399         } else {
5400           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5401                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5402         }
5403
5404       case WhiteDrop:
5405       case BlackDrop:
5406         *fromX = *moveType == WhiteDrop ?
5407           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5408           (int) CharToPiece(ToLower(currentMoveString[0]));
5409         *fromY = DROP_RANK;
5410         *toX = currentMoveString[2] - AAA;
5411         *toY = currentMoveString[3] - ONE;
5412         *promoChar = NULLCHAR;
5413         return TRUE;
5414
5415       case AmbiguousMove:
5416       case ImpossibleMove:
5417       case EndOfFile:
5418       case ElapsedTime:
5419       case Comment:
5420       case PGNTag:
5421       case NAG:
5422       case WhiteWins:
5423       case BlackWins:
5424       case GameIsDrawn:
5425       default:
5426     if (appData.debugMode) {
5427         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5428     }
5429         /* bug? */
5430         *fromX = *fromY = *toX = *toY = 0;
5431         *promoChar = NULLCHAR;
5432         return FALSE;
5433     }
5434 }
5435
5436 Boolean pushed = FALSE;
5437 char *lastParseAttempt;
5438
5439 void
5440 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5441 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5442   int fromX, fromY, toX, toY; char promoChar;
5443   ChessMove moveType;
5444   Boolean valid;
5445   int nr = 0;
5446
5447   lastParseAttempt = pv; if(!*pv) return;    // turns out we crash when we parse an empty PV
5448   if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5449     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5450     pushed = TRUE;
5451   }
5452   endPV = forwardMostMove;
5453   do {
5454     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5455     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5456     lastParseAttempt = pv;
5457     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5458     if(!valid && nr == 0 &&
5459        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5460         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5461         // Hande case where played move is different from leading PV move
5462         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5463         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5464         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5465         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5466           endPV += 2; // if position different, keep this
5467           moveList[endPV-1][0] = fromX + AAA;
5468           moveList[endPV-1][1] = fromY + ONE;
5469           moveList[endPV-1][2] = toX + AAA;
5470           moveList[endPV-1][3] = toY + ONE;
5471           parseList[endPV-1][0] = NULLCHAR;
5472           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5473         }
5474       }
5475     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5476     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5477     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5478     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5479         valid++; // allow comments in PV
5480         continue;
5481     }
5482     nr++;
5483     if(endPV+1 > framePtr) break; // no space, truncate
5484     if(!valid) break;
5485     endPV++;
5486     CopyBoard(boards[endPV], boards[endPV-1]);
5487     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5488     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5489     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5490     CoordsToAlgebraic(boards[endPV - 1],
5491                              PosFlags(endPV - 1),
5492                              fromY, fromX, toY, toX, promoChar,
5493                              parseList[endPV - 1]);
5494   } while(valid);
5495   if(atEnd == 2) return; // used hidden, for PV conversion
5496   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5497   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5498   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5499                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5500   DrawPosition(TRUE, boards[currentMove]);
5501 }
5502
5503 int
5504 MultiPV (ChessProgramState *cps)
5505 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5506         int i;
5507         for(i=0; i<cps->nrOptions; i++)
5508             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5509                 return i;
5510         return -1;
5511 }
5512
5513 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
5514
5515 Boolean
5516 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5517 {
5518         int startPV, multi, lineStart, origIndex = index;
5519         char *p, buf2[MSG_SIZ];
5520         ChessProgramState *cps = (pane ? &second : &first);
5521
5522         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5523         lastX = x; lastY = y;
5524         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5525         lineStart = startPV = index;
5526         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5527         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5528         index = startPV;
5529         do{ while(buf[index] && buf[index] != '\n') index++;
5530         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5531         buf[index] = 0;
5532         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(cps)) >= 0) {
5533                 int n = cps->option[multi].value;
5534                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5535                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5536                 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5537                 cps->option[multi].value = n;
5538                 *start = *end = 0;
5539                 return FALSE;
5540         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5541                 ExcludeClick(origIndex - lineStart);
5542                 return FALSE;
5543         }
5544         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5545         *start = startPV; *end = index-1;
5546         extendGame = (gameMode == AnalyzeMode && appData.autoExtend);
5547         return TRUE;
5548 }
5549
5550 char *
5551 PvToSAN (char *pv)
5552 {
5553         static char buf[10*MSG_SIZ];
5554         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5555         *buf = NULLCHAR;
5556         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5557         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5558         for(i = forwardMostMove; i<endPV; i++){
5559             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5560             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5561             k += strlen(buf+k);
5562         }
5563         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5564         if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5565         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5566         endPV = savedEnd;
5567         return buf;
5568 }
5569
5570 Boolean
5571 LoadPV (int x, int y)
5572 { // called on right mouse click to load PV
5573   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5574   lastX = x; lastY = y;
5575   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5576   extendGame = FALSE;
5577   return TRUE;
5578 }
5579
5580 void
5581 UnLoadPV ()
5582 {
5583   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5584   if(endPV < 0) return;
5585   if(appData.autoCopyPV) CopyFENToClipboard();
5586   endPV = -1;
5587   if(extendGame && currentMove > forwardMostMove) {
5588         Boolean saveAnimate = appData.animate;
5589         if(pushed) {
5590             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5591                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5592             } else storedGames--; // abandon shelved tail of original game
5593         }
5594         pushed = FALSE;
5595         forwardMostMove = currentMove;
5596         currentMove = oldFMM;
5597         appData.animate = FALSE;
5598         ToNrEvent(forwardMostMove);
5599         appData.animate = saveAnimate;
5600   }
5601   currentMove = forwardMostMove;
5602   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5603   ClearPremoveHighlights();
5604   DrawPosition(TRUE, boards[currentMove]);
5605 }
5606
5607 void
5608 MovePV (int x, int y, int h)
5609 { // step through PV based on mouse coordinates (called on mouse move)
5610   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5611
5612   // we must somehow check if right button is still down (might be released off board!)
5613   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5614   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5615   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5616   if(!step) return;
5617   lastX = x; lastY = y;
5618
5619   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5620   if(endPV < 0) return;
5621   if(y < margin) step = 1; else
5622   if(y > h - margin) step = -1;
5623   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5624   currentMove += step;
5625   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5626   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5627                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5628   DrawPosition(FALSE, boards[currentMove]);
5629 }
5630
5631
5632 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5633 // All positions will have equal probability, but the current method will not provide a unique
5634 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5635 #define DARK 1
5636 #define LITE 2
5637 #define ANY 3
5638
5639 int squaresLeft[4];
5640 int piecesLeft[(int)BlackPawn];
5641 int seed, nrOfShuffles;
5642
5643 void
5644 GetPositionNumber ()
5645 {       // sets global variable seed
5646         int i;
5647
5648         seed = appData.defaultFrcPosition;
5649         if(seed < 0) { // randomize based on time for negative FRC position numbers
5650                 for(i=0; i<50; i++) seed += random();
5651                 seed = random() ^ random() >> 8 ^ random() << 8;
5652                 if(seed<0) seed = -seed;
5653         }
5654 }
5655
5656 int
5657 put (Board board, int pieceType, int rank, int n, int shade)
5658 // put the piece on the (n-1)-th empty squares of the given shade
5659 {
5660         int i;
5661
5662         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5663                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5664                         board[rank][i] = (ChessSquare) pieceType;
5665                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5666                         squaresLeft[ANY]--;
5667                         piecesLeft[pieceType]--;
5668                         return i;
5669                 }
5670         }
5671         return -1;
5672 }
5673
5674
5675 void
5676 AddOnePiece (Board board, int pieceType, int rank, int shade)
5677 // calculate where the next piece goes, (any empty square), and put it there
5678 {
5679         int i;
5680
5681         i = seed % squaresLeft[shade];
5682         nrOfShuffles *= squaresLeft[shade];
5683         seed /= squaresLeft[shade];
5684         put(board, pieceType, rank, i, shade);
5685 }
5686
5687 void
5688 AddTwoPieces (Board board, int pieceType, int rank)
5689 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5690 {
5691         int i, n=squaresLeft[ANY], j=n-1, k;
5692
5693         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5694         i = seed % k;  // pick one
5695         nrOfShuffles *= k;
5696         seed /= k;
5697         while(i >= j) i -= j--;
5698         j = n - 1 - j; i += j;
5699         put(board, pieceType, rank, j, ANY);
5700         put(board, pieceType, rank, i, ANY);
5701 }
5702
5703 void
5704 SetUpShuffle (Board board, int number)
5705 {
5706         int i, p, first=1;
5707
5708         GetPositionNumber(); nrOfShuffles = 1;
5709
5710         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5711         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5712         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5713
5714         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5715
5716         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5717             p = (int) board[0][i];
5718             if(p < (int) BlackPawn) piecesLeft[p] ++;
5719             board[0][i] = EmptySquare;
5720         }
5721
5722         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5723             // shuffles restricted to allow normal castling put KRR first
5724             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5725                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5726             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5727                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5728             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5729                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5730             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5731                 put(board, WhiteRook, 0, 0, ANY);
5732             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5733         }
5734
5735         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5736             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5737             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5738                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5739                 while(piecesLeft[p] >= 2) {
5740                     AddOnePiece(board, p, 0, LITE);
5741                     AddOnePiece(board, p, 0, DARK);
5742                 }
5743                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5744             }
5745
5746         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5747             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5748             // but we leave King and Rooks for last, to possibly obey FRC restriction
5749             if(p == (int)WhiteRook) continue;
5750             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5751             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5752         }
5753
5754         // now everything is placed, except perhaps King (Unicorn) and Rooks
5755
5756         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5757             // Last King gets castling rights
5758             while(piecesLeft[(int)WhiteUnicorn]) {
5759                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5760                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5761             }
5762
5763             while(piecesLeft[(int)WhiteKing]) {
5764                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5765                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5766             }
5767
5768
5769         } else {
5770             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5771             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5772         }
5773
5774         // Only Rooks can be left; simply place them all
5775         while(piecesLeft[(int)WhiteRook]) {
5776                 i = put(board, WhiteRook, 0, 0, ANY);
5777                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5778                         if(first) {
5779                                 first=0;
5780                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5781                         }
5782                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5783                 }
5784         }
5785         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5786             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5787         }
5788
5789         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5790 }
5791
5792 int
5793 SetCharTable (char *table, const char * map)
5794 /* [HGM] moved here from winboard.c because of its general usefulness */
5795 /*       Basically a safe strcpy that uses the last character as King */
5796 {
5797     int result = FALSE; int NrPieces;
5798
5799     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5800                     && NrPieces >= 12 && !(NrPieces&1)) {
5801         int i; /* [HGM] Accept even length from 12 to 34 */
5802
5803         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5804         for( i=0; i<NrPieces/2-1; i++ ) {
5805             table[i] = map[i];
5806             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5807         }
5808         table[(int) WhiteKing]  = map[NrPieces/2-1];
5809         table[(int) BlackKing]  = map[NrPieces-1];
5810
5811         result = TRUE;
5812     }
5813
5814     return result;
5815 }
5816
5817 void
5818 Prelude (Board board)
5819 {       // [HGM] superchess: random selection of exo-pieces
5820         int i, j, k; ChessSquare p;
5821         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5822
5823         GetPositionNumber(); // use FRC position number
5824
5825         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5826             SetCharTable(pieceToChar, appData.pieceToCharTable);
5827             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5828                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5829         }
5830
5831         j = seed%4;                 seed /= 4;
5832         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5833         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5834         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5835         j = seed%3 + (seed%3 >= j); seed /= 3;
5836         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5837         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5838         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5839         j = seed%3;                 seed /= 3;
5840         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5841         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5842         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5843         j = seed%2 + (seed%2 >= j); seed /= 2;
5844         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5845         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5846         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5847         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5848         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5849         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5850         put(board, exoPieces[0],    0, 0, ANY);
5851         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5852 }
5853
5854 void
5855 InitPosition (int redraw)
5856 {
5857     ChessSquare (* pieces)[BOARD_FILES];
5858     int i, j, pawnRow, overrule,
5859     oldx = gameInfo.boardWidth,
5860     oldy = gameInfo.boardHeight,
5861     oldh = gameInfo.holdingsWidth;
5862     static int oldv;
5863
5864     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5865
5866     /* [AS] Initialize pv info list [HGM] and game status */
5867     {
5868         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5869             pvInfoList[i].depth = 0;
5870             boards[i][EP_STATUS] = EP_NONE;
5871             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5872         }
5873
5874         initialRulePlies = 0; /* 50-move counter start */
5875
5876         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5877         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5878     }
5879
5880
5881     /* [HGM] logic here is completely changed. In stead of full positions */
5882     /* the initialized data only consist of the two backranks. The switch */
5883     /* selects which one we will use, which is than copied to the Board   */
5884     /* initialPosition, which for the rest is initialized by Pawns and    */
5885     /* empty squares. This initial position is then copied to boards[0],  */
5886     /* possibly after shuffling, so that it remains available.            */
5887
5888     gameInfo.holdingsWidth = 0; /* default board sizes */
5889     gameInfo.boardWidth    = 8;
5890     gameInfo.boardHeight   = 8;
5891     gameInfo.holdingsSize  = 0;
5892     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5893     for(i=0; i<BOARD_FILES-2; i++)
5894       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5895     initialPosition[EP_STATUS] = EP_NONE;
5896     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5897     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5898          SetCharTable(pieceNickName, appData.pieceNickNames);
5899     else SetCharTable(pieceNickName, "............");
5900     pieces = FIDEArray;
5901
5902     switch (gameInfo.variant) {
5903     case VariantFischeRandom:
5904       shuffleOpenings = TRUE;
5905     default:
5906       break;
5907     case VariantShatranj:
5908       pieces = ShatranjArray;
5909       nrCastlingRights = 0;
5910       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5911       break;
5912     case VariantMakruk:
5913       pieces = makrukArray;
5914       nrCastlingRights = 0;
5915       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5916       break;
5917     case VariantASEAN:
5918       pieces = aseanArray;
5919       nrCastlingRights = 0;
5920       SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk");
5921       break;
5922     case VariantTwoKings:
5923       pieces = twoKingsArray;
5924       break;
5925     case VariantGrand:
5926       pieces = GrandArray;
5927       nrCastlingRights = 0;
5928       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5929       gameInfo.boardWidth = 10;
5930       gameInfo.boardHeight = 10;
5931       gameInfo.holdingsSize = 7;
5932       break;
5933     case VariantCapaRandom:
5934       shuffleOpenings = TRUE;
5935     case VariantCapablanca:
5936       pieces = CapablancaArray;
5937       gameInfo.boardWidth = 10;
5938       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5939       break;
5940     case VariantGothic:
5941       pieces = GothicArray;
5942       gameInfo.boardWidth = 10;
5943       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5944       break;
5945     case VariantSChess:
5946       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5947       gameInfo.holdingsSize = 7;
5948       for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
5949       break;
5950     case VariantJanus:
5951       pieces = JanusArray;
5952       gameInfo.boardWidth = 10;
5953       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5954       nrCastlingRights = 6;
5955         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5956         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5957         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5958         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5959         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5960         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5961       break;
5962     case VariantFalcon:
5963       pieces = FalconArray;
5964       gameInfo.boardWidth = 10;
5965       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5966       break;
5967     case VariantXiangqi:
5968       pieces = XiangqiArray;
5969       gameInfo.boardWidth  = 9;
5970       gameInfo.boardHeight = 10;
5971       nrCastlingRights = 0;
5972       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5973       break;
5974     case VariantShogi:
5975       pieces = ShogiArray;
5976       gameInfo.boardWidth  = 9;
5977       gameInfo.boardHeight = 9;
5978       gameInfo.holdingsSize = 7;
5979       nrCastlingRights = 0;
5980       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5981       break;
5982     case VariantCourier:
5983       pieces = CourierArray;
5984       gameInfo.boardWidth  = 12;
5985       nrCastlingRights = 0;
5986       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5987       break;
5988     case VariantKnightmate:
5989       pieces = KnightmateArray;
5990       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5991       break;
5992     case VariantSpartan:
5993       pieces = SpartanArray;
5994       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5995       break;
5996     case VariantFairy:
5997       pieces = fairyArray;
5998       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5999       break;
6000     case VariantGreat:
6001       pieces = GreatArray;
6002       gameInfo.boardWidth = 10;
6003       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
6004       gameInfo.holdingsSize = 8;
6005       break;
6006     case VariantSuper:
6007       pieces = FIDEArray;
6008       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
6009       gameInfo.holdingsSize = 8;
6010       startedFromSetupPosition = TRUE;
6011       break;
6012     case VariantCrazyhouse:
6013     case VariantBughouse:
6014       pieces = FIDEArray;
6015       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
6016       gameInfo.holdingsSize = 5;
6017       break;
6018     case VariantWildCastle:
6019       pieces = FIDEArray;
6020       /* !!?shuffle with kings guaranteed to be on d or e file */
6021       shuffleOpenings = 1;
6022       break;
6023     case VariantNoCastle:
6024       pieces = FIDEArray;
6025       nrCastlingRights = 0;
6026       /* !!?unconstrained back-rank shuffle */
6027       shuffleOpenings = 1;
6028       break;
6029     }
6030
6031     overrule = 0;
6032     if(appData.NrFiles >= 0) {
6033         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6034         gameInfo.boardWidth = appData.NrFiles;
6035     }
6036     if(appData.NrRanks >= 0) {
6037         gameInfo.boardHeight = appData.NrRanks;
6038     }
6039     if(appData.holdingsSize >= 0) {
6040         i = appData.holdingsSize;
6041         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6042         gameInfo.holdingsSize = i;
6043     }
6044     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6045     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6046         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6047
6048     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6049     if(pawnRow < 1) pawnRow = 1;
6050     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN || gameInfo.variant == VariantGrand) pawnRow = 2;
6051
6052     /* User pieceToChar list overrules defaults */
6053     if(appData.pieceToCharTable != NULL)
6054         SetCharTable(pieceToChar, appData.pieceToCharTable);
6055
6056     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6057
6058         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6059             s = (ChessSquare) 0; /* account holding counts in guard band */
6060         for( i=0; i<BOARD_HEIGHT; i++ )
6061             initialPosition[i][j] = s;
6062
6063         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6064         initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
6065         initialPosition[pawnRow][j] = WhitePawn;
6066         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6067         if(gameInfo.variant == VariantXiangqi) {
6068             if(j&1) {
6069                 initialPosition[pawnRow][j] =
6070                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6071                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6072                    initialPosition[2][j] = WhiteCannon;
6073                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6074                 }
6075             }
6076         }
6077         if(gameInfo.variant == VariantGrand) {
6078             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6079                initialPosition[0][j] = WhiteRook;
6080                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6081             }
6082         }
6083         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] =  pieces[1][j-gameInfo.holdingsWidth];
6084     }
6085     if( (gameInfo.variant == VariantShogi) && !overrule ) {
6086
6087             j=BOARD_LEFT+1;
6088             initialPosition[1][j] = WhiteBishop;
6089             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6090             j=BOARD_RGHT-2;
6091             initialPosition[1][j] = WhiteRook;
6092             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6093     }
6094
6095     if( nrCastlingRights == -1) {
6096         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6097         /*       This sets default castling rights from none to normal corners   */
6098         /* Variants with other castling rights must set them themselves above    */
6099         nrCastlingRights = 6;
6100
6101         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6102         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6103         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6104         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6105         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6106         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6107      }
6108
6109      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6110      if(gameInfo.variant == VariantGreat) { // promotion commoners
6111         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6112         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6113         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6114         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6115      }
6116      if( gameInfo.variant == VariantSChess ) {
6117       initialPosition[1][0] = BlackMarshall;
6118       initialPosition[2][0] = BlackAngel;
6119       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6120       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6121       initialPosition[1][1] = initialPosition[2][1] =
6122       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6123      }
6124   if (appData.debugMode) {
6125     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6126   }
6127     if(shuffleOpenings) {
6128         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6129         startedFromSetupPosition = TRUE;
6130     }
6131     if(startedFromPositionFile) {
6132       /* [HGM] loadPos: use PositionFile for every new game */
6133       CopyBoard(initialPosition, filePosition);
6134       for(i=0; i<nrCastlingRights; i++)
6135           initialRights[i] = filePosition[CASTLING][i];
6136       startedFromSetupPosition = TRUE;
6137     }
6138
6139     CopyBoard(boards[0], initialPosition);
6140
6141     if(oldx != gameInfo.boardWidth ||
6142        oldy != gameInfo.boardHeight ||
6143        oldv != gameInfo.variant ||
6144        oldh != gameInfo.holdingsWidth
6145                                          )
6146             InitDrawingSizes(-2 ,0);
6147
6148     oldv = gameInfo.variant;
6149     if (redraw)
6150       DrawPosition(TRUE, boards[currentMove]);
6151 }
6152
6153 void
6154 SendBoard (ChessProgramState *cps, int moveNum)
6155 {
6156     char message[MSG_SIZ];
6157
6158     if (cps->useSetboard) {
6159       char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6160       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6161       SendToProgram(message, cps);
6162       free(fen);
6163
6164     } else {
6165       ChessSquare *bp;
6166       int i, j, left=0, right=BOARD_WIDTH;
6167       /* Kludge to set black to move, avoiding the troublesome and now
6168        * deprecated "black" command.
6169        */
6170       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6171         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6172
6173       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6174
6175       SendToProgram("edit\n", cps);
6176       SendToProgram("#\n", cps);
6177       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6178         bp = &boards[moveNum][i][left];
6179         for (j = left; j < right; j++, bp++) {
6180           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6181           if ((int) *bp < (int) BlackPawn) {
6182             if(j == BOARD_RGHT+1)
6183                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6184             else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6185             if(message[0] == '+' || message[0] == '~') {
6186               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6187                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6188                         AAA + j, ONE + i);
6189             }
6190             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6191                 message[1] = BOARD_RGHT   - 1 - j + '1';
6192                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6193             }
6194             SendToProgram(message, cps);
6195           }
6196         }
6197       }
6198
6199       SendToProgram("c\n", cps);
6200       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6201         bp = &boards[moveNum][i][left];
6202         for (j = left; j < right; j++, bp++) {
6203           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6204           if (((int) *bp != (int) EmptySquare)
6205               && ((int) *bp >= (int) BlackPawn)) {
6206             if(j == BOARD_LEFT-2)
6207                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6208             else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6209                     AAA + j, ONE + i);
6210             if(message[0] == '+' || message[0] == '~') {
6211               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6212                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6213                         AAA + j, ONE + i);
6214             }
6215             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6216                 message[1] = BOARD_RGHT   - 1 - j + '1';
6217                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6218             }
6219             SendToProgram(message, cps);
6220           }
6221         }
6222       }
6223
6224       SendToProgram(".\n", cps);
6225     }
6226     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6227 }
6228
6229 char exclusionHeader[MSG_SIZ];
6230 int exCnt, excludePtr;
6231 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6232 static Exclusion excluTab[200];
6233 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6234
6235 static void
6236 WriteMap (int s)
6237 {
6238     int j;
6239     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6240     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6241 }
6242
6243 static void
6244 ClearMap ()
6245 {
6246     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6247     excludePtr = 24; exCnt = 0;
6248     WriteMap(0);
6249 }
6250
6251 static void
6252 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6253 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6254     char buf[2*MOVE_LEN], *p;
6255     Exclusion *e = excluTab;
6256     int i;
6257     for(i=0; i<exCnt; i++)
6258         if(e[i].ff == fromX && e[i].fr == fromY &&
6259            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6260     if(i == exCnt) { // was not in exclude list; add it
6261         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6262         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6263             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6264             return; // abort
6265         }
6266         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6267         excludePtr++; e[i].mark = excludePtr++;
6268         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6269         exCnt++;
6270     }
6271     exclusionHeader[e[i].mark] = state;
6272 }
6273
6274 static int
6275 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6276 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6277     char buf[MSG_SIZ];
6278     int j, k;
6279     ChessMove moveType;
6280     if((signed char)promoChar == -1) { // kludge to indicate best move
6281         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6282             return 1; // if unparsable, abort
6283     }
6284     // update exclusion map (resolving toggle by consulting existing state)
6285     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6286     j = k%8; k >>= 3;
6287     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6288     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6289          excludeMap[k] |=   1<<j;
6290     else excludeMap[k] &= ~(1<<j);
6291     // update header
6292     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6293     // inform engine
6294     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6295     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6296     SendToBoth(buf);
6297     return (state == '+');
6298 }
6299
6300 static void
6301 ExcludeClick (int index)
6302 {
6303     int i, j;
6304     Exclusion *e = excluTab;
6305     if(index < 25) { // none, best or tail clicked
6306         if(index < 13) { // none: include all
6307             WriteMap(0); // clear map
6308             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6309             SendToBoth("include all\n"); // and inform engine
6310         } else if(index > 18) { // tail
6311             if(exclusionHeader[19] == '-') { // tail was excluded
6312                 SendToBoth("include all\n");
6313                 WriteMap(0); // clear map completely
6314                 // now re-exclude selected moves
6315                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6316                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6317             } else { // tail was included or in mixed state
6318                 SendToBoth("exclude all\n");
6319                 WriteMap(0xFF); // fill map completely
6320                 // now re-include selected moves
6321                 j = 0; // count them
6322                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6323                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6324                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6325             }
6326         } else { // best
6327             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6328         }
6329     } else {
6330         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6331             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6332             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6333             break;
6334         }
6335     }
6336 }
6337
6338 ChessSquare
6339 DefaultPromoChoice (int white)
6340 {
6341     ChessSquare result;
6342     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6343        gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
6344         result = WhiteFerz; // no choice
6345     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6346         result= WhiteKing; // in Suicide Q is the last thing we want
6347     else if(gameInfo.variant == VariantSpartan)
6348         result = white ? WhiteQueen : WhiteAngel;
6349     else result = WhiteQueen;
6350     if(!white) result = WHITE_TO_BLACK result;
6351     return result;
6352 }
6353
6354 static int autoQueen; // [HGM] oneclick
6355
6356 int
6357 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6358 {
6359     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6360     /* [HGM] add Shogi promotions */
6361     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6362     ChessSquare piece;
6363     ChessMove moveType;
6364     Boolean premove;
6365
6366     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6367     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6368
6369     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6370       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6371         return FALSE;
6372
6373     piece = boards[currentMove][fromY][fromX];
6374     if(gameInfo.variant == VariantShogi) {
6375         promotionZoneSize = BOARD_HEIGHT/3;
6376         highestPromotingPiece = (int)WhiteFerz;
6377     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6378         promotionZoneSize = 3;
6379     }
6380
6381     // Treat Lance as Pawn when it is not representing Amazon
6382     if(gameInfo.variant != VariantSuper) {
6383         if(piece == WhiteLance) piece = WhitePawn; else
6384         if(piece == BlackLance) piece = BlackPawn;
6385     }
6386
6387     // next weed out all moves that do not touch the promotion zone at all
6388     if((int)piece >= BlackPawn) {
6389         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6390              return FALSE;
6391         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6392     } else {
6393         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6394            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6395     }
6396
6397     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6398
6399     // weed out mandatory Shogi promotions
6400     if(gameInfo.variant == VariantShogi) {
6401         if(piece >= BlackPawn) {
6402             if(toY == 0 && piece == BlackPawn ||
6403                toY == 0 && piece == BlackQueen ||
6404                toY <= 1 && piece == BlackKnight) {
6405                 *promoChoice = '+';
6406                 return FALSE;
6407             }
6408         } else {
6409             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6410                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6411                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6412                 *promoChoice = '+';
6413                 return FALSE;
6414             }
6415         }
6416     }
6417
6418     // weed out obviously illegal Pawn moves
6419     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6420         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6421         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6422         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6423         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6424         // note we are not allowed to test for valid (non-)capture, due to premove
6425     }
6426
6427     // we either have a choice what to promote to, or (in Shogi) whether to promote
6428     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6429        gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN) {
6430         *promoChoice = PieceToChar(BlackFerz);  // no choice
6431         return FALSE;
6432     }
6433     // no sense asking what we must promote to if it is going to explode...
6434     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6435         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6436         return FALSE;
6437     }
6438     // give caller the default choice even if we will not make it
6439     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6440     if(gameInfo.variant == VariantShogi) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6441     if(        sweepSelect && gameInfo.variant != VariantGreat
6442                            && gameInfo.variant != VariantGrand
6443                            && gameInfo.variant != VariantSuper) return FALSE;
6444     if(autoQueen) return FALSE; // predetermined
6445
6446     // suppress promotion popup on illegal moves that are not premoves
6447     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6448               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6449     if(appData.testLegality && !premove) {
6450         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6451                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6452         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6453             return FALSE;
6454     }
6455
6456     return TRUE;
6457 }
6458
6459 int
6460 InPalace (int row, int column)
6461 {   /* [HGM] for Xiangqi */
6462     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6463          column < (BOARD_WIDTH + 4)/2 &&
6464          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6465     return FALSE;
6466 }
6467
6468 int
6469 PieceForSquare (int x, int y)
6470 {
6471   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6472      return -1;
6473   else
6474      return boards[currentMove][y][x];
6475 }
6476
6477 int
6478 OKToStartUserMove (int x, int y)
6479 {
6480     ChessSquare from_piece;
6481     int white_piece;
6482
6483     if (matchMode) return FALSE;
6484     if (gameMode == EditPosition) return TRUE;
6485
6486     if (x >= 0 && y >= 0)
6487       from_piece = boards[currentMove][y][x];
6488     else
6489       from_piece = EmptySquare;
6490
6491     if (from_piece == EmptySquare) return FALSE;
6492
6493     white_piece = (int)from_piece >= (int)WhitePawn &&
6494       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6495
6496     switch (gameMode) {
6497       case AnalyzeFile:
6498       case TwoMachinesPlay:
6499       case EndOfGame:
6500         return FALSE;
6501
6502       case IcsObserving:
6503       case IcsIdle:
6504         return FALSE;
6505
6506       case MachinePlaysWhite:
6507       case IcsPlayingBlack:
6508         if (appData.zippyPlay) return FALSE;
6509         if (white_piece) {
6510             DisplayMoveError(_("You are playing Black"));
6511             return FALSE;
6512         }
6513         break;
6514
6515       case MachinePlaysBlack:
6516       case IcsPlayingWhite:
6517         if (appData.zippyPlay) return FALSE;
6518         if (!white_piece) {
6519             DisplayMoveError(_("You are playing White"));
6520             return FALSE;
6521         }
6522         break;
6523
6524       case PlayFromGameFile:
6525             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6526       case EditGame:
6527         if (!white_piece && WhiteOnMove(currentMove)) {
6528             DisplayMoveError(_("It is White's turn"));
6529             return FALSE;
6530         }
6531         if (white_piece && !WhiteOnMove(currentMove)) {
6532             DisplayMoveError(_("It is Black's turn"));
6533             return FALSE;
6534         }
6535         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6536             /* Editing correspondence game history */
6537             /* Could disallow this or prompt for confirmation */
6538             cmailOldMove = -1;
6539         }
6540         break;
6541
6542       case BeginningOfGame:
6543         if (appData.icsActive) return FALSE;
6544         if (!appData.noChessProgram) {
6545             if (!white_piece) {
6546                 DisplayMoveError(_("You are playing White"));
6547                 return FALSE;
6548             }
6549         }
6550         break;
6551
6552       case Training:
6553         if (!white_piece && WhiteOnMove(currentMove)) {
6554             DisplayMoveError(_("It is White's turn"));
6555             return FALSE;
6556         }
6557         if (white_piece && !WhiteOnMove(currentMove)) {
6558             DisplayMoveError(_("It is Black's turn"));
6559             return FALSE;
6560         }
6561         break;
6562
6563       default:
6564       case IcsExamining:
6565         break;
6566     }
6567     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6568         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6569         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6570         && gameMode != AnalyzeFile && gameMode != Training) {
6571         DisplayMoveError(_("Displayed position is not current"));
6572         return FALSE;
6573     }
6574     return TRUE;
6575 }
6576
6577 Boolean
6578 OnlyMove (int *x, int *y, Boolean captures)
6579 {
6580     DisambiguateClosure cl;
6581     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6582     switch(gameMode) {
6583       case MachinePlaysBlack:
6584       case IcsPlayingWhite:
6585       case BeginningOfGame:
6586         if(!WhiteOnMove(currentMove)) return FALSE;
6587         break;
6588       case MachinePlaysWhite:
6589       case IcsPlayingBlack:
6590         if(WhiteOnMove(currentMove)) return FALSE;
6591         break;
6592       case EditGame:
6593         break;
6594       default:
6595         return FALSE;
6596     }
6597     cl.pieceIn = EmptySquare;
6598     cl.rfIn = *y;
6599     cl.ffIn = *x;
6600     cl.rtIn = -1;
6601     cl.ftIn = -1;
6602     cl.promoCharIn = NULLCHAR;
6603     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6604     if( cl.kind == NormalMove ||
6605         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6606         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6607         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6608       fromX = cl.ff;
6609       fromY = cl.rf;
6610       *x = cl.ft;
6611       *y = cl.rt;
6612       return TRUE;
6613     }
6614     if(cl.kind != ImpossibleMove) return FALSE;
6615     cl.pieceIn = EmptySquare;
6616     cl.rfIn = -1;
6617     cl.ffIn = -1;
6618     cl.rtIn = *y;
6619     cl.ftIn = *x;
6620     cl.promoCharIn = NULLCHAR;
6621     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6622     if( cl.kind == NormalMove ||
6623         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6624         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6625         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6626       fromX = cl.ff;
6627       fromY = cl.rf;
6628       *x = cl.ft;
6629       *y = cl.rt;
6630       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6631       return TRUE;
6632     }
6633     return FALSE;
6634 }
6635
6636 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6637 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6638 int lastLoadGameUseList = FALSE;
6639 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6640 ChessMove lastLoadGameStart = EndOfFile;
6641 int doubleClick;
6642
6643 void
6644 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6645 {
6646     ChessMove moveType;
6647     ChessSquare pup;
6648     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6649
6650     /* Check if the user is playing in turn.  This is complicated because we
6651        let the user "pick up" a piece before it is his turn.  So the piece he
6652        tried to pick up may have been captured by the time he puts it down!
6653        Therefore we use the color the user is supposed to be playing in this
6654        test, not the color of the piece that is currently on the starting
6655        square---except in EditGame mode, where the user is playing both
6656        sides; fortunately there the capture race can't happen.  (It can
6657        now happen in IcsExamining mode, but that's just too bad.  The user
6658        will get a somewhat confusing message in that case.)
6659        */
6660
6661     switch (gameMode) {
6662       case AnalyzeFile:
6663       case TwoMachinesPlay:
6664       case EndOfGame:
6665       case IcsObserving:
6666       case IcsIdle:
6667         /* We switched into a game mode where moves are not accepted,
6668            perhaps while the mouse button was down. */
6669         return;
6670
6671       case MachinePlaysWhite:
6672         /* User is moving for Black */
6673         if (WhiteOnMove(currentMove)) {
6674             DisplayMoveError(_("It is White's turn"));
6675             return;
6676         }
6677         break;
6678
6679       case MachinePlaysBlack:
6680         /* User is moving for White */
6681         if (!WhiteOnMove(currentMove)) {
6682             DisplayMoveError(_("It is Black's turn"));
6683             return;
6684         }
6685         break;
6686
6687       case PlayFromGameFile:
6688             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6689       case EditGame:
6690       case IcsExamining:
6691       case BeginningOfGame:
6692       case AnalyzeMode:
6693       case Training:
6694         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6695         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6696             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6697             /* User is moving for Black */
6698             if (WhiteOnMove(currentMove)) {
6699                 DisplayMoveError(_("It is White's turn"));
6700                 return;
6701             }
6702         } else {
6703             /* User is moving for White */
6704             if (!WhiteOnMove(currentMove)) {
6705                 DisplayMoveError(_("It is Black's turn"));
6706                 return;
6707             }
6708         }
6709         break;
6710
6711       case IcsPlayingBlack:
6712         /* User is moving for Black */
6713         if (WhiteOnMove(currentMove)) {
6714             if (!appData.premove) {
6715                 DisplayMoveError(_("It is White's turn"));
6716             } else if (toX >= 0 && toY >= 0) {
6717                 premoveToX = toX;
6718                 premoveToY = toY;
6719                 premoveFromX = fromX;
6720                 premoveFromY = fromY;
6721                 premovePromoChar = promoChar;
6722                 gotPremove = 1;
6723                 if (appData.debugMode)
6724                     fprintf(debugFP, "Got premove: fromX %d,"
6725                             "fromY %d, toX %d, toY %d\n",
6726                             fromX, fromY, toX, toY);
6727             }
6728             return;
6729         }
6730         break;
6731
6732       case IcsPlayingWhite:
6733         /* User is moving for White */
6734         if (!WhiteOnMove(currentMove)) {
6735             if (!appData.premove) {
6736                 DisplayMoveError(_("It is Black's turn"));
6737             } else if (toX >= 0 && toY >= 0) {
6738                 premoveToX = toX;
6739                 premoveToY = toY;
6740                 premoveFromX = fromX;
6741                 premoveFromY = fromY;
6742                 premovePromoChar = promoChar;
6743                 gotPremove = 1;
6744                 if (appData.debugMode)
6745                     fprintf(debugFP, "Got premove: fromX %d,"
6746                             "fromY %d, toX %d, toY %d\n",
6747                             fromX, fromY, toX, toY);
6748             }
6749             return;
6750         }
6751         break;
6752
6753       default:
6754         break;
6755
6756       case EditPosition:
6757         /* EditPosition, empty square, or different color piece;
6758            click-click move is possible */
6759         if (toX == -2 || toY == -2) {
6760             boards[0][fromY][fromX] = EmptySquare;
6761             DrawPosition(FALSE, boards[currentMove]);
6762             return;
6763         } else if (toX >= 0 && toY >= 0) {
6764             boards[0][toY][toX] = boards[0][fromY][fromX];
6765             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6766                 if(boards[0][fromY][0] != EmptySquare) {
6767                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6768                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6769                 }
6770             } else
6771             if(fromX == BOARD_RGHT+1) {
6772                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6773                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6774                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6775                 }
6776             } else
6777             boards[0][fromY][fromX] = gatingPiece;
6778             DrawPosition(FALSE, boards[currentMove]);
6779             return;
6780         }
6781         return;
6782     }
6783
6784     if(toX < 0 || toY < 0) return;
6785     pup = boards[currentMove][toY][toX];
6786
6787     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6788     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6789          if( pup != EmptySquare ) return;
6790          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6791            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6792                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6793            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6794            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6795            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6796            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6797          fromY = DROP_RANK;
6798     }
6799
6800     /* [HGM] always test for legality, to get promotion info */
6801     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6802                                          fromY, fromX, toY, toX, promoChar);
6803
6804     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6805
6806     /* [HGM] but possibly ignore an IllegalMove result */
6807     if (appData.testLegality) {
6808         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6809             DisplayMoveError(_("Illegal move"));
6810             return;
6811         }
6812     }
6813
6814     if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
6815         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
6816              ClearPremoveHighlights(); // was included
6817         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
6818         return;
6819     }
6820
6821     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6822 }
6823
6824 /* Common tail of UserMoveEvent and DropMenuEvent */
6825 int
6826 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
6827 {
6828     char *bookHit = 0;
6829
6830     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6831         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6832         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6833         if(WhiteOnMove(currentMove)) {
6834             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6835         } else {
6836             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6837         }
6838     }
6839
6840     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6841        move type in caller when we know the move is a legal promotion */
6842     if(moveType == NormalMove && promoChar)
6843         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6844
6845     /* [HGM] <popupFix> The following if has been moved here from
6846        UserMoveEvent(). Because it seemed to belong here (why not allow
6847        piece drops in training games?), and because it can only be
6848        performed after it is known to what we promote. */
6849     if (gameMode == Training) {
6850       /* compare the move played on the board to the next move in the
6851        * game. If they match, display the move and the opponent's response.
6852        * If they don't match, display an error message.
6853        */
6854       int saveAnimate;
6855       Board testBoard;
6856       CopyBoard(testBoard, boards[currentMove]);
6857       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6858
6859       if (CompareBoards(testBoard, boards[currentMove+1])) {
6860         ForwardInner(currentMove+1);
6861
6862         /* Autoplay the opponent's response.
6863          * if appData.animate was TRUE when Training mode was entered,
6864          * the response will be animated.
6865          */
6866         saveAnimate = appData.animate;
6867         appData.animate = animateTraining;
6868         ForwardInner(currentMove+1);
6869         appData.animate = saveAnimate;
6870
6871         /* check for the end of the game */
6872         if (currentMove >= forwardMostMove) {
6873           gameMode = PlayFromGameFile;
6874           ModeHighlight();
6875           SetTrainingModeOff();
6876           DisplayInformation(_("End of game"));
6877         }
6878       } else {
6879         DisplayError(_("Incorrect move"), 0);
6880       }
6881       return 1;
6882     }
6883
6884   /* Ok, now we know that the move is good, so we can kill
6885      the previous line in Analysis Mode */
6886   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6887                                 && currentMove < forwardMostMove) {
6888     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6889     else forwardMostMove = currentMove;
6890   }
6891
6892   ClearMap();
6893
6894   /* If we need the chess program but it's dead, restart it */
6895   ResurrectChessProgram();
6896
6897   /* A user move restarts a paused game*/
6898   if (pausing)
6899     PauseEvent();
6900
6901   thinkOutput[0] = NULLCHAR;
6902
6903   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6904
6905   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6906     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6907     return 1;
6908   }
6909
6910   if (gameMode == BeginningOfGame) {
6911     if (appData.noChessProgram) {
6912       gameMode = EditGame;
6913       SetGameInfo();
6914     } else {
6915       char buf[MSG_SIZ];
6916       gameMode = MachinePlaysBlack;
6917       StartClocks();
6918       SetGameInfo();
6919       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
6920       DisplayTitle(buf);
6921       if (first.sendName) {
6922         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6923         SendToProgram(buf, &first);
6924       }
6925       StartClocks();
6926     }
6927     ModeHighlight();
6928   }
6929
6930   /* Relay move to ICS or chess engine */
6931   if (appData.icsActive) {
6932     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6933         gameMode == IcsExamining) {
6934       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6935         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6936         SendToICS("draw ");
6937         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6938       }
6939       // also send plain move, in case ICS does not understand atomic claims
6940       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6941       ics_user_moved = 1;
6942     }
6943   } else {
6944     if (first.sendTime && (gameMode == BeginningOfGame ||
6945                            gameMode == MachinePlaysWhite ||
6946                            gameMode == MachinePlaysBlack)) {
6947       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6948     }
6949     if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
6950          // [HGM] book: if program might be playing, let it use book
6951         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6952         first.maybeThinking = TRUE;
6953     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
6954         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
6955         SendBoard(&first, currentMove+1);
6956         if(second.analyzing) {
6957             if(!second.useSetboard) SendToProgram("undo\n", &second);
6958             SendBoard(&second, currentMove+1);
6959         }
6960     } else {
6961         SendMoveToProgram(forwardMostMove-1, &first);
6962         if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
6963     }
6964     if (currentMove == cmailOldMove + 1) {
6965       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6966     }
6967   }
6968
6969   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6970
6971   switch (gameMode) {
6972   case EditGame:
6973     if(appData.testLegality)
6974     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6975     case MT_NONE:
6976     case MT_CHECK:
6977       break;
6978     case MT_CHECKMATE:
6979     case MT_STAINMATE:
6980       if (WhiteOnMove(currentMove)) {
6981         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6982       } else {
6983         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6984       }
6985       break;
6986     case MT_STALEMATE:
6987       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6988       break;
6989     }
6990     break;
6991
6992   case MachinePlaysBlack:
6993   case MachinePlaysWhite:
6994     /* disable certain menu options while machine is thinking */
6995     SetMachineThinkingEnables();
6996     break;
6997
6998   default:
6999     break;
7000   }
7001
7002   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7003   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7004
7005   if(bookHit) { // [HGM] book: simulate book reply
7006         static char bookMove[MSG_SIZ]; // a bit generous?
7007
7008         programStats.nodes = programStats.depth = programStats.time =
7009         programStats.score = programStats.got_only_move = 0;
7010         sprintf(programStats.movelist, "%s (xbook)", bookHit);
7011
7012         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7013         strcat(bookMove, bookHit);
7014         HandleMachineMove(bookMove, &first);
7015   }
7016   return 1;
7017 }
7018
7019 void
7020 MarkByFEN(char *fen)
7021 {
7022         int r, f;
7023         if(!appData.markers || !appData.highlightDragging) return;
7024         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 0;
7025         r=BOARD_HEIGHT-1; f=BOARD_LEFT;
7026         while(*fen) {
7027             int s = 0;
7028             marker[r][f] = 0;
7029             if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7030             if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 1; else
7031             if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7032             if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7033             if(*fen == 'T') marker[r][f++] = 0; else
7034             if(*fen == 'Y') marker[r][f++] = 1; else
7035             if(*fen == 'G') marker[r][f++] = 3; else
7036             if(*fen == 'B') marker[r][f++] = 4; else
7037             if(*fen == 'C') marker[r][f++] = 5; else
7038             if(*fen == 'M') marker[r][f++] = 6; else
7039             if(*fen == 'W') marker[r][f++] = 7; else
7040             if(*fen == 'D') marker[r][f++] = 8; else
7041             if(*fen == 'R') marker[r][f++] = 2; else {
7042                 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7043               f += s; fen -= s>0;
7044             }
7045             while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7046             if(r < 0) break;
7047             fen++;
7048         }
7049         DrawPosition(TRUE, NULL);
7050 }
7051
7052 void
7053 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7054 {
7055     typedef char Markers[BOARD_RANKS][BOARD_FILES];
7056     Markers *m = (Markers *) closure;
7057     if(rf == fromY && ff == fromX)
7058         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7059                          || kind == WhiteCapturesEnPassant
7060                          || kind == BlackCapturesEnPassant);
7061     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
7062 }
7063
7064 void
7065 MarkTargetSquares (int clear)
7066 {
7067   int x, y, sum=0;
7068   if(clear) { // no reason to ever suppress clearing
7069     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = 0;
7070     if(!sum) return; // nothing was cleared,no redraw needed
7071   } else {
7072     int capt = 0;
7073     if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7074        !appData.testLegality || gameMode == EditPosition) return;
7075     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7076     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7077       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7078       if(capt)
7079       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7080     }
7081   }
7082   DrawPosition(FALSE, NULL);
7083 }
7084
7085 int
7086 Explode (Board board, int fromX, int fromY, int toX, int toY)
7087 {
7088     if(gameInfo.variant == VariantAtomic &&
7089        (board[toY][toX] != EmptySquare ||                     // capture?
7090         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
7091                          board[fromY][fromX] == BlackPawn   )
7092       )) {
7093         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7094         return TRUE;
7095     }
7096     return FALSE;
7097 }
7098
7099 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7100
7101 int
7102 CanPromote (ChessSquare piece, int y)
7103 {
7104         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7105         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7106         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
7107            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7108            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7109          gameInfo.variant == VariantMakruk   || gameInfo.variant == VariantASEAN) return FALSE;
7110         return (piece == BlackPawn && y == 1 ||
7111                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
7112                 piece == BlackLance && y == 1 ||
7113                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
7114 }
7115
7116 void
7117 HoverEvent (int hiX, int hiY, int x, int y)
7118 {
7119         static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7120         int r, f;
7121         if(!first.highlight) return;
7122         if(hiX == -1 && hiY == -1 && x == fromX && y == fromY) // record markings 
7123           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7124             baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7125         else if(hiX != x || hiY != y) {
7126           // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7127           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7128             marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7129           if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7130             char buf[MSG_SIZ];
7131             snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7132             SendToProgram(buf, &first);
7133           }
7134           SetHighlights(fromX, fromY, x, y);
7135         }
7136 }
7137
7138 void ReportClick(char *action, int x, int y)
7139 {
7140         char buf[MSG_SIZ]; // Inform engine of what user does
7141         int r, f;
7142         if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7143           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 1, marker[r][f] = 0;
7144         if(!first.highlight || gameMode == EditPosition) return;
7145         snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7146         SendToProgram(buf, &first);
7147 }
7148
7149 void
7150 LeftClick (ClickType clickType, int xPix, int yPix)
7151 {
7152     int x, y;
7153     Boolean saveAnimate;
7154     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7155     char promoChoice = NULLCHAR;
7156     ChessSquare piece;
7157     static TimeMark lastClickTime, prevClickTime;
7158
7159     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7160
7161     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7162
7163     if (clickType == Press) ErrorPopDown();
7164     lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7165
7166     x = EventToSquare(xPix, BOARD_WIDTH);
7167     y = EventToSquare(yPix, BOARD_HEIGHT);
7168     if (!flipView && y >= 0) {
7169         y = BOARD_HEIGHT - 1 - y;
7170     }
7171     if (flipView && x >= 0) {
7172         x = BOARD_WIDTH - 1 - x;
7173     }
7174
7175     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7176         defaultPromoChoice = promoSweep;
7177         promoSweep = EmptySquare;   // terminate sweep
7178         promoDefaultAltered = TRUE;
7179         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7180     }
7181
7182     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7183         if(clickType == Release) return; // ignore upclick of click-click destination
7184         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7185         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7186         if(gameInfo.holdingsWidth &&
7187                 (WhiteOnMove(currentMove)
7188                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7189                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7190             // click in right holdings, for determining promotion piece
7191             ChessSquare p = boards[currentMove][y][x];
7192             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7193             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7194             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7195                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7196                 fromX = fromY = -1;
7197                 return;
7198             }
7199         }
7200         DrawPosition(FALSE, boards[currentMove]);
7201         return;
7202     }
7203
7204     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7205     if(clickType == Press
7206             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7207               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7208               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7209         return;
7210
7211     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7212         // could be static click on premove from-square: abort premove
7213         gotPremove = 0;
7214         ClearPremoveHighlights();
7215     }
7216
7217     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7218         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7219
7220     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7221         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7222                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7223         defaultPromoChoice = DefaultPromoChoice(side);
7224     }
7225
7226     autoQueen = appData.alwaysPromoteToQueen;
7227
7228     if (fromX == -1) {
7229       int originalY = y;
7230       gatingPiece = EmptySquare;
7231       if (clickType != Press) {
7232         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7233             DragPieceEnd(xPix, yPix); dragging = 0;
7234             DrawPosition(FALSE, NULL);
7235         }
7236         return;
7237       }
7238       doubleClick = FALSE;
7239       if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7240         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7241       }
7242       fromX = x; fromY = y; toX = toY = -1;
7243       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7244          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7245          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7246             /* First square */
7247             if (OKToStartUserMove(fromX, fromY)) {
7248                 second = 0;
7249                 ReportClick("lift", x, y);
7250                 MarkTargetSquares(0);
7251                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7252                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7253                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7254                     promoSweep = defaultPromoChoice;
7255                     selectFlag = 0; lastX = xPix; lastY = yPix;
7256                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7257                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7258                 }
7259                 if (appData.highlightDragging) {
7260                     SetHighlights(fromX, fromY, -1, -1);
7261                 } else {
7262                     ClearHighlights();
7263                 }
7264             } else fromX = fromY = -1;
7265             return;
7266         }
7267     }
7268
7269     /* fromX != -1 */
7270     if (clickType == Press && gameMode != EditPosition) {
7271         ChessSquare fromP;
7272         ChessSquare toP;
7273         int frc;
7274
7275         // ignore off-board to clicks
7276         if(y < 0 || x < 0) return;
7277
7278         /* Check if clicking again on the same color piece */
7279         fromP = boards[currentMove][fromY][fromX];
7280         toP = boards[currentMove][y][x];
7281         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
7282         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
7283              WhitePawn <= toP && toP <= WhiteKing &&
7284              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7285              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7286             (BlackPawn <= fromP && fromP <= BlackKing &&
7287              BlackPawn <= toP && toP <= BlackKing &&
7288              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7289              !(fromP == BlackKing && toP == BlackRook && frc))) {
7290             /* Clicked again on same color piece -- changed his mind */
7291             second = (x == fromX && y == fromY);
7292             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7293                 second = FALSE; // first double-click rather than scond click
7294                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7295             }
7296             promoDefaultAltered = FALSE;
7297             MarkTargetSquares(1);
7298            if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7299             if (appData.highlightDragging) {
7300                 SetHighlights(x, y, -1, -1);
7301             } else {
7302                 ClearHighlights();
7303             }
7304             if (OKToStartUserMove(x, y)) {
7305                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7306                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7307                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7308                  gatingPiece = boards[currentMove][fromY][fromX];
7309                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7310                 fromX = x;
7311                 fromY = y; dragging = 1;
7312                 ReportClick("lift", x, y);
7313                 MarkTargetSquares(0);
7314                 DragPieceBegin(xPix, yPix, FALSE);
7315                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7316                     promoSweep = defaultPromoChoice;
7317                     selectFlag = 0; lastX = xPix; lastY = yPix;
7318                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7319                 }
7320             }
7321            }
7322            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7323            second = FALSE;
7324         }
7325         // ignore clicks on holdings
7326         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7327     }
7328
7329     if (clickType == Release && x == fromX && y == fromY) {
7330         DragPieceEnd(xPix, yPix); dragging = 0;
7331         if(clearFlag) {
7332             // a deferred attempt to click-click move an empty square on top of a piece
7333             boards[currentMove][y][x] = EmptySquare;
7334             ClearHighlights();
7335             DrawPosition(FALSE, boards[currentMove]);
7336             fromX = fromY = -1; clearFlag = 0;
7337             return;
7338         }
7339         if (appData.animateDragging) {
7340             /* Undo animation damage if any */
7341             DrawPosition(FALSE, NULL);
7342         }
7343         if (second || sweepSelecting) {
7344             /* Second up/down in same square; just abort move */
7345             if(sweepSelecting) DrawPosition(FALSE, boards[currentMove]);
7346             second = sweepSelecting = 0;
7347             fromX = fromY = -1;
7348             gatingPiece = EmptySquare;
7349             MarkTargetSquares(1);
7350             ClearHighlights();
7351             gotPremove = 0;
7352             ClearPremoveHighlights();
7353         } else {
7354             /* First upclick in same square; start click-click mode */
7355             SetHighlights(x, y, -1, -1);
7356         }
7357         return;
7358     }
7359
7360     clearFlag = 0;
7361
7362     if(gameMode != EditPosition && !appData.testLegality && !legal[y][x]) {
7363         if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7364         DisplayMessage(_("only marked squares are legal"),"");
7365         DrawPosition(TRUE, NULL);
7366         return; // ignore to-click
7367     }
7368
7369     /* we now have a different from- and (possibly off-board) to-square */
7370     /* Completed move */
7371     if(!sweepSelecting) {
7372         toX = x;
7373         toY = y;
7374     } else sweepSelecting = 0; // this must be the up-click corresponding to the down-click that started the sweep
7375
7376     saveAnimate = appData.animate;
7377     if (clickType == Press) {
7378         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7379             // must be Edit Position mode with empty-square selected
7380             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7381             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7382             return;
7383         }
7384         if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7385           if(appData.sweepSelect) {
7386             ChessSquare piece = boards[currentMove][fromY][fromX];
7387             promoSweep = defaultPromoChoice;
7388             if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
7389             selectFlag = 0; lastX = xPix; lastY = yPix;
7390             Sweep(0); // Pawn that is going to promote: preview promotion piece
7391             sweepSelecting = 1;
7392             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7393             MarkTargetSquares(1);
7394           }
7395           return; // promo popup appears on up-click
7396         }
7397         /* Finish clickclick move */
7398         if (appData.animate || appData.highlightLastMove) {
7399             SetHighlights(fromX, fromY, toX, toY);
7400         } else {
7401             ClearHighlights();
7402         }
7403     } else {
7404 #if 0
7405 // [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
7406         /* Finish drag move */
7407         if (appData.highlightLastMove) {
7408             SetHighlights(fromX, fromY, toX, toY);
7409         } else {
7410             ClearHighlights();
7411         }
7412 #endif
7413         DragPieceEnd(xPix, yPix); dragging = 0;
7414         /* Don't animate move and drag both */
7415         appData.animate = FALSE;
7416     }
7417
7418     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7419     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7420         ChessSquare piece = boards[currentMove][fromY][fromX];
7421         if(gameMode == EditPosition && piece != EmptySquare &&
7422            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7423             int n;
7424
7425             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7426                 n = PieceToNumber(piece - (int)BlackPawn);
7427                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7428                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7429                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7430             } else
7431             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7432                 n = PieceToNumber(piece);
7433                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7434                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7435                 boards[currentMove][n][BOARD_WIDTH-2]++;
7436             }
7437             boards[currentMove][fromY][fromX] = EmptySquare;
7438         }
7439         ClearHighlights();
7440         fromX = fromY = -1;
7441         MarkTargetSquares(1);
7442         DrawPosition(TRUE, boards[currentMove]);
7443         return;
7444     }
7445
7446     // off-board moves should not be highlighted
7447     if(x < 0 || y < 0) ClearHighlights();
7448     else ReportClick("put", x, y);
7449
7450     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7451
7452     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7453         SetHighlights(fromX, fromY, toX, toY);
7454         MarkTargetSquares(1);
7455         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7456             // [HGM] super: promotion to captured piece selected from holdings
7457             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7458             promotionChoice = TRUE;
7459             // kludge follows to temporarily execute move on display, without promoting yet
7460             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7461             boards[currentMove][toY][toX] = p;
7462             DrawPosition(FALSE, boards[currentMove]);
7463             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7464             boards[currentMove][toY][toX] = q;
7465             DisplayMessage("Click in holdings to choose piece", "");
7466             return;
7467         }
7468         PromotionPopUp();
7469     } else {
7470         int oldMove = currentMove;
7471         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7472         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7473         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7474         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7475            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7476             DrawPosition(TRUE, boards[currentMove]);
7477         MarkTargetSquares(1);
7478         fromX = fromY = -1;
7479     }
7480     appData.animate = saveAnimate;
7481     if (appData.animate || appData.animateDragging) {
7482         /* Undo animation damage if needed */
7483         DrawPosition(FALSE, NULL);
7484     }
7485 }
7486
7487 int
7488 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7489 {   // front-end-free part taken out of PieceMenuPopup
7490     int whichMenu; int xSqr, ySqr;
7491
7492     if(seekGraphUp) { // [HGM] seekgraph
7493         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7494         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7495         return -2;
7496     }
7497
7498     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7499          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7500         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7501         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7502         if(action == Press)   {
7503             originalFlip = flipView;
7504             flipView = !flipView; // temporarily flip board to see game from partners perspective
7505             DrawPosition(TRUE, partnerBoard);
7506             DisplayMessage(partnerStatus, "");
7507             partnerUp = TRUE;
7508         } else if(action == Release) {
7509             flipView = originalFlip;
7510             DrawPosition(TRUE, boards[currentMove]);
7511             partnerUp = FALSE;
7512         }
7513         return -2;
7514     }
7515
7516     xSqr = EventToSquare(x, BOARD_WIDTH);
7517     ySqr = EventToSquare(y, BOARD_HEIGHT);
7518     if (action == Release) {
7519         if(pieceSweep != EmptySquare) {
7520             EditPositionMenuEvent(pieceSweep, toX, toY);
7521             pieceSweep = EmptySquare;
7522         } else UnLoadPV(); // [HGM] pv
7523     }
7524     if (action != Press) return -2; // return code to be ignored
7525     switch (gameMode) {
7526       case IcsExamining:
7527         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7528       case EditPosition:
7529         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7530         if (xSqr < 0 || ySqr < 0) return -1;
7531         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7532         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7533         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7534         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7535         NextPiece(0);
7536         return 2; // grab
7537       case IcsObserving:
7538         if(!appData.icsEngineAnalyze) return -1;
7539       case IcsPlayingWhite:
7540       case IcsPlayingBlack:
7541         if(!appData.zippyPlay) goto noZip;
7542       case AnalyzeMode:
7543       case AnalyzeFile:
7544       case MachinePlaysWhite:
7545       case MachinePlaysBlack:
7546       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7547         if (!appData.dropMenu) {
7548           LoadPV(x, y);
7549           return 2; // flag front-end to grab mouse events
7550         }
7551         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7552            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7553       case EditGame:
7554       noZip:
7555         if (xSqr < 0 || ySqr < 0) return -1;
7556         if (!appData.dropMenu || appData.testLegality &&
7557             gameInfo.variant != VariantBughouse &&
7558             gameInfo.variant != VariantCrazyhouse) return -1;
7559         whichMenu = 1; // drop menu
7560         break;
7561       default:
7562         return -1;
7563     }
7564
7565     if (((*fromX = xSqr) < 0) ||
7566         ((*fromY = ySqr) < 0)) {
7567         *fromX = *fromY = -1;
7568         return -1;
7569     }
7570     if (flipView)
7571       *fromX = BOARD_WIDTH - 1 - *fromX;
7572     else
7573       *fromY = BOARD_HEIGHT - 1 - *fromY;
7574
7575     return whichMenu;
7576 }
7577
7578 void
7579 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7580 {
7581 //    char * hint = lastHint;
7582     FrontEndProgramStats stats;
7583
7584     stats.which = cps == &first ? 0 : 1;
7585     stats.depth = cpstats->depth;
7586     stats.nodes = cpstats->nodes;
7587     stats.score = cpstats->score;
7588     stats.time = cpstats->time;
7589     stats.pv = cpstats->movelist;
7590     stats.hint = lastHint;
7591     stats.an_move_index = 0;
7592     stats.an_move_count = 0;
7593
7594     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7595         stats.hint = cpstats->move_name;
7596         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7597         stats.an_move_count = cpstats->nr_moves;
7598     }
7599
7600     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
7601
7602     SetProgramStats( &stats );
7603 }
7604
7605 void
7606 ClearEngineOutputPane (int which)
7607 {
7608     static FrontEndProgramStats dummyStats;
7609     dummyStats.which = which;
7610     dummyStats.pv = "#";
7611     SetProgramStats( &dummyStats );
7612 }
7613
7614 #define MAXPLAYERS 500
7615
7616 char *
7617 TourneyStandings (int display)
7618 {
7619     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7620     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7621     char result, *p, *names[MAXPLAYERS];
7622
7623     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7624         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7625     names[0] = p = strdup(appData.participants);
7626     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7627
7628     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7629
7630     while(result = appData.results[nr]) {
7631         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7632         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7633         wScore = bScore = 0;
7634         switch(result) {
7635           case '+': wScore = 2; break;
7636           case '-': bScore = 2; break;
7637           case '=': wScore = bScore = 1; break;
7638           case ' ':
7639           case '*': return strdup("busy"); // tourney not finished
7640         }
7641         score[w] += wScore;
7642         score[b] += bScore;
7643         games[w]++;
7644         games[b]++;
7645         nr++;
7646     }
7647     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7648     for(w=0; w<nPlayers; w++) {
7649         bScore = -1;
7650         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7651         ranking[w] = b; points[w] = bScore; score[b] = -2;
7652     }
7653     p = malloc(nPlayers*34+1);
7654     for(w=0; w<nPlayers && w<display; w++)
7655         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7656     free(names[0]);
7657     return p;
7658 }
7659
7660 void
7661 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7662 {       // count all piece types
7663         int p, f, r;
7664         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7665         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7666         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7667                 p = board[r][f];
7668                 pCnt[p]++;
7669                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7670                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7671                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7672                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7673                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7674                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7675         }
7676 }
7677
7678 int
7679 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7680 {
7681         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7682         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7683
7684         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7685         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7686         if(myPawns == 2 && nMine == 3) // KPP
7687             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7688         if(myPawns == 1 && nMine == 2) // KP
7689             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7690         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7691             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7692         if(myPawns) return FALSE;
7693         if(pCnt[WhiteRook+side])
7694             return pCnt[BlackRook-side] ||
7695                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7696                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7697                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7698         if(pCnt[WhiteCannon+side]) {
7699             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7700             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7701         }
7702         if(pCnt[WhiteKnight+side])
7703             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7704         return FALSE;
7705 }
7706
7707 int
7708 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7709 {
7710         VariantClass v = gameInfo.variant;
7711
7712         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7713         if(v == VariantShatranj) return TRUE; // always winnable through baring
7714         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7715         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7716
7717         if(v == VariantXiangqi) {
7718                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7719
7720                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7721                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7722                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7723                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7724                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7725                 if(stale) // we have at least one last-rank P plus perhaps C
7726                     return majors // KPKX
7727                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7728                 else // KCA*E*
7729                     return pCnt[WhiteFerz+side] // KCAK
7730                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7731                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7732                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7733
7734         } else if(v == VariantKnightmate) {
7735                 if(nMine == 1) return FALSE;
7736                 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
7737         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7738                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7739
7740                 if(nMine == 1) return FALSE; // bare King
7741                 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
7742                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7743                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7744                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7745                 if(pCnt[WhiteKnight+side])
7746                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7747                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7748                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7749                 if(nBishops)
7750                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7751                 if(pCnt[WhiteAlfil+side])
7752                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7753                 if(pCnt[WhiteWazir+side])
7754                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7755         }
7756
7757         return TRUE;
7758 }
7759
7760 int
7761 CompareWithRights (Board b1, Board b2)
7762 {
7763     int rights = 0;
7764     if(!CompareBoards(b1, b2)) return FALSE;
7765     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7766     /* compare castling rights */
7767     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7768            rights++; /* King lost rights, while rook still had them */
7769     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7770         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7771            rights++; /* but at least one rook lost them */
7772     }
7773     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7774            rights++;
7775     if( b1[CASTLING][5] != NoRights ) {
7776         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7777            rights++;
7778     }
7779     return rights == 0;
7780 }
7781
7782 int
7783 Adjudicate (ChessProgramState *cps)
7784 {       // [HGM] some adjudications useful with buggy engines
7785         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7786         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7787         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7788         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7789         int k, drop, count = 0; static int bare = 1;
7790         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7791         Boolean canAdjudicate = !appData.icsActive;
7792
7793         // most tests only when we understand the game, i.e. legality-checking on
7794             if( appData.testLegality )
7795             {   /* [HGM] Some more adjudications for obstinate engines */
7796                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7797                 static int moveCount = 6;
7798                 ChessMove result;
7799                 char *reason = NULL;
7800
7801                 /* Count what is on board. */
7802                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7803
7804                 /* Some material-based adjudications that have to be made before stalemate test */
7805                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7806                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7807                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7808                      if(canAdjudicate && appData.checkMates) {
7809                          if(engineOpponent)
7810                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7811                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7812                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7813                          return 1;
7814                      }
7815                 }
7816
7817                 /* Bare King in Shatranj (loses) or Losers (wins) */
7818                 if( nrW == 1 || nrB == 1) {
7819                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7820                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7821                      if(canAdjudicate && appData.checkMates) {
7822                          if(engineOpponent)
7823                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7824                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7825                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7826                          return 1;
7827                      }
7828                   } else
7829                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7830                   {    /* bare King */
7831                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7832                         if(canAdjudicate && appData.checkMates) {
7833                             /* but only adjudicate if adjudication enabled */
7834                             if(engineOpponent)
7835                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7836                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7837                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7838                             return 1;
7839                         }
7840                   }
7841                 } else bare = 1;
7842
7843
7844             // don't wait for engine to announce game end if we can judge ourselves
7845             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7846               case MT_CHECK:
7847                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7848                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7849                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7850                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7851                             checkCnt++;
7852                         if(checkCnt >= 2) {
7853                             reason = "Xboard adjudication: 3rd check";
7854                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7855                             break;
7856                         }
7857                     }
7858                 }
7859               case MT_NONE:
7860               default:
7861                 break;
7862               case MT_STALEMATE:
7863               case MT_STAINMATE:
7864                 reason = "Xboard adjudication: Stalemate";
7865                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7866                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7867                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7868                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7869                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7870                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7871                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7872                                                                         EP_CHECKMATE : EP_WINS);
7873                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
7874                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7875                 }
7876                 break;
7877               case MT_CHECKMATE:
7878                 reason = "Xboard adjudication: Checkmate";
7879                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7880                 if(gameInfo.variant == VariantShogi) {
7881                     if(forwardMostMove > backwardMostMove
7882                        && moveList[forwardMostMove-1][1] == '@'
7883                        && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
7884                         reason = "XBoard adjudication: pawn-drop mate";
7885                         boards[forwardMostMove][EP_STATUS] = EP_WINS;
7886                     }
7887                 }
7888                 break;
7889             }
7890
7891                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7892                     case EP_STALEMATE:
7893                         result = GameIsDrawn; break;
7894                     case EP_CHECKMATE:
7895                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7896                     case EP_WINS:
7897                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7898                     default:
7899                         result = EndOfFile;
7900                 }
7901                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7902                     if(engineOpponent)
7903                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7904                     GameEnds( result, reason, GE_XBOARD );
7905                     return 1;
7906                 }
7907
7908                 /* Next absolutely insufficient mating material. */
7909                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7910                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7911                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7912
7913                      /* always flag draws, for judging claims */
7914                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7915
7916                      if(canAdjudicate && appData.materialDraws) {
7917                          /* but only adjudicate them if adjudication enabled */
7918                          if(engineOpponent) {
7919                            SendToProgram("force\n", engineOpponent); // suppress reply
7920                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7921                          }
7922                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7923                          return 1;
7924                      }
7925                 }
7926
7927                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7928                 if(gameInfo.variant == VariantXiangqi ?
7929                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7930                  : nrW + nrB == 4 &&
7931                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7932                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7933                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7934                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7935                    ) ) {
7936                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7937                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7938                           if(engineOpponent) {
7939                             SendToProgram("force\n", engineOpponent); // suppress reply
7940                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7941                           }
7942                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7943                           return 1;
7944                      }
7945                 } else moveCount = 6;
7946             }
7947
7948         // Repetition draws and 50-move rule can be applied independently of legality testing
7949
7950                 /* Check for rep-draws */
7951                 count = 0;
7952                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
7953                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
7954                 for(k = forwardMostMove-2;
7955                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
7956                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7957                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
7958                     k-=2)
7959                 {   int rights=0;
7960                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7961                         /* compare castling rights */
7962                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7963                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7964                                 rights++; /* King lost rights, while rook still had them */
7965                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7966                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7967                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7968                                    rights++; /* but at least one rook lost them */
7969                         }
7970                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7971                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7972                                 rights++;
7973                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7974                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7975                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7976                                    rights++;
7977                         }
7978                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7979                             && appData.drawRepeats > 1) {
7980                              /* adjudicate after user-specified nr of repeats */
7981                              int result = GameIsDrawn;
7982                              char *details = "XBoard adjudication: repetition draw";
7983                              if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
7984                                 // [HGM] xiangqi: check for forbidden perpetuals
7985                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7986                                 for(m=forwardMostMove; m>k; m-=2) {
7987                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7988                                         ourPerpetual = 0; // the current mover did not always check
7989                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7990                                         hisPerpetual = 0; // the opponent did not always check
7991                                 }
7992                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7993                                                                         ourPerpetual, hisPerpetual);
7994                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7995                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7996                                     details = "Xboard adjudication: perpetual checking";
7997                                 } else
7998                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7999                                     break; // (or we would have caught him before). Abort repetition-checking loop.
8000                                 } else
8001                                 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8002                                     if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8003                                         result = BlackWins;
8004                                         details = "Xboard adjudication: repetition";
8005                                     }
8006                                 } else // it must be XQ
8007                                 // Now check for perpetual chases
8008                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8009                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
8010                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8011                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8012                                         static char resdet[MSG_SIZ];
8013                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8014                                         details = resdet;
8015                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8016                                     } else
8017                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
8018                                         break; // Abort repetition-checking loop.
8019                                 }
8020                                 // if neither of us is checking or chasing all the time, or both are, it is draw
8021                              }
8022                              if(engineOpponent) {
8023                                SendToProgram("force\n", engineOpponent); // suppress reply
8024                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8025                              }
8026                              GameEnds( result, details, GE_XBOARD );
8027                              return 1;
8028                         }
8029                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8030                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8031                     }
8032                 }
8033
8034                 /* Now we test for 50-move draws. Determine ply count */
8035                 count = forwardMostMove;
8036                 /* look for last irreversble move */
8037                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8038                     count--;
8039                 /* if we hit starting position, add initial plies */
8040                 if( count == backwardMostMove )
8041                     count -= initialRulePlies;
8042                 count = forwardMostMove - count;
8043                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8044                         // adjust reversible move counter for checks in Xiangqi
8045                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
8046                         if(i < backwardMostMove) i = backwardMostMove;
8047                         while(i <= forwardMostMove) {
8048                                 lastCheck = inCheck; // check evasion does not count
8049                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8050                                 if(inCheck || lastCheck) count--; // check does not count
8051                                 i++;
8052                         }
8053                 }
8054                 if( count >= 100)
8055                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8056                          /* this is used to judge if draw claims are legal */
8057                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8058                          if(engineOpponent) {
8059                            SendToProgram("force\n", engineOpponent); // suppress reply
8060                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8061                          }
8062                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8063                          return 1;
8064                 }
8065
8066                 /* if draw offer is pending, treat it as a draw claim
8067                  * when draw condition present, to allow engines a way to
8068                  * claim draws before making their move to avoid a race
8069                  * condition occurring after their move
8070                  */
8071                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8072                          char *p = NULL;
8073                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8074                              p = "Draw claim: 50-move rule";
8075                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8076                              p = "Draw claim: 3-fold repetition";
8077                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8078                              p = "Draw claim: insufficient mating material";
8079                          if( p != NULL && canAdjudicate) {
8080                              if(engineOpponent) {
8081                                SendToProgram("force\n", engineOpponent); // suppress reply
8082                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8083                              }
8084                              GameEnds( GameIsDrawn, p, GE_XBOARD );
8085                              return 1;
8086                          }
8087                 }
8088
8089                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8090                     if(engineOpponent) {
8091                       SendToProgram("force\n", engineOpponent); // suppress reply
8092                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8093                     }
8094                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8095                     return 1;
8096                 }
8097         return 0;
8098 }
8099
8100 char *
8101 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8102 {   // [HGM] book: this routine intercepts moves to simulate book replies
8103     char *bookHit = NULL;
8104
8105     //first determine if the incoming move brings opponent into his book
8106     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8107         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8108     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8109     if(bookHit != NULL && !cps->bookSuspend) {
8110         // make sure opponent is not going to reply after receiving move to book position
8111         SendToProgram("force\n", cps);
8112         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8113     }
8114     if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8115     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8116     // now arrange restart after book miss
8117     if(bookHit) {
8118         // after a book hit we never send 'go', and the code after the call to this routine
8119         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8120         char buf[MSG_SIZ], *move = bookHit;
8121         if(cps->useSAN) {
8122             int fromX, fromY, toX, toY;
8123             char promoChar;
8124             ChessMove moveType;
8125             move = buf + 30;
8126             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8127                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8128                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8129                                     PosFlags(forwardMostMove),
8130                                     fromY, fromX, toY, toX, promoChar, move);
8131             } else {
8132                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8133                 bookHit = NULL;
8134             }
8135         }
8136         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8137         SendToProgram(buf, cps);
8138         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8139     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8140         SendToProgram("go\n", cps);
8141         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8142     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8143         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8144             SendToProgram("go\n", cps);
8145         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8146     }
8147     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8148 }
8149
8150 int
8151 LoadError (char *errmess, ChessProgramState *cps)
8152 {   // unloads engine and switches back to -ncp mode if it was first
8153     if(cps->initDone) return FALSE;
8154     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8155     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8156     cps->pr = NoProc;
8157     if(cps == &first) {
8158         appData.noChessProgram = TRUE;
8159         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8160         gameMode = BeginningOfGame; ModeHighlight();
8161         SetNCPMode();
8162     }
8163     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8164     DisplayMessage("", ""); // erase waiting message
8165     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8166     return TRUE;
8167 }
8168
8169 char *savedMessage;
8170 ChessProgramState *savedState;
8171 void
8172 DeferredBookMove (void)
8173 {
8174         if(savedState->lastPing != savedState->lastPong)
8175                     ScheduleDelayedEvent(DeferredBookMove, 10);
8176         else
8177         HandleMachineMove(savedMessage, savedState);
8178 }
8179
8180 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8181 static ChessProgramState *stalledEngine;
8182 static char stashedInputMove[MSG_SIZ];
8183
8184 void
8185 HandleMachineMove (char *message, ChessProgramState *cps)
8186 {
8187     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8188     char realname[MSG_SIZ];
8189     int fromX, fromY, toX, toY;
8190     ChessMove moveType;
8191     char promoChar;
8192     char *p, *pv=buf1;
8193     int machineWhite, oldError;
8194     char *bookHit;
8195
8196     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8197         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8198         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8199             DisplayError(_("Invalid pairing from pairing engine"), 0);
8200             return;
8201         }
8202         pairingReceived = 1;
8203         NextMatchGame();
8204         return; // Skim the pairing messages here.
8205     }
8206
8207     oldError = cps->userError; cps->userError = 0;
8208
8209 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8210     /*
8211      * Kludge to ignore BEL characters
8212      */
8213     while (*message == '\007') message++;
8214
8215     /*
8216      * [HGM] engine debug message: ignore lines starting with '#' character
8217      */
8218     if(cps->debug && *message == '#') return;
8219
8220     /*
8221      * Look for book output
8222      */
8223     if (cps == &first && bookRequested) {
8224         if (message[0] == '\t' || message[0] == ' ') {
8225             /* Part of the book output is here; append it */
8226             strcat(bookOutput, message);
8227             strcat(bookOutput, "  \n");
8228             return;
8229         } else if (bookOutput[0] != NULLCHAR) {
8230             /* All of book output has arrived; display it */
8231             char *p = bookOutput;
8232             while (*p != NULLCHAR) {
8233                 if (*p == '\t') *p = ' ';
8234                 p++;
8235             }
8236             DisplayInformation(bookOutput);
8237             bookRequested = FALSE;
8238             /* Fall through to parse the current output */
8239         }
8240     }
8241
8242     /*
8243      * Look for machine move.
8244      */
8245     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8246         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8247     {
8248         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8249             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8250             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8251             stalledEngine = cps;
8252             if(appData.ponderNextMove) { // bring opponent out of ponder
8253                 if(gameMode == TwoMachinesPlay) {
8254                     if(cps->other->pause)
8255                         PauseEngine(cps->other);
8256                     else
8257                         SendToProgram("easy\n", cps->other);
8258                 }
8259             }
8260             StopClocks();
8261             return;
8262         }
8263
8264         /* This method is only useful on engines that support ping */
8265         if (cps->lastPing != cps->lastPong) {
8266           if (gameMode == BeginningOfGame) {
8267             /* Extra move from before last new; ignore */
8268             if (appData.debugMode) {
8269                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8270             }
8271           } else {
8272             if (appData.debugMode) {
8273                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8274                         cps->which, gameMode);
8275             }
8276
8277             SendToProgram("undo\n", cps);
8278           }
8279           return;
8280         }
8281
8282         switch (gameMode) {
8283           case BeginningOfGame:
8284             /* Extra move from before last reset; ignore */
8285             if (appData.debugMode) {
8286                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8287             }
8288             return;
8289
8290           case EndOfGame:
8291           case IcsIdle:
8292           default:
8293             /* Extra move after we tried to stop.  The mode test is
8294                not a reliable way of detecting this problem, but it's
8295                the best we can do on engines that don't support ping.
8296             */
8297             if (appData.debugMode) {
8298                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8299                         cps->which, gameMode);
8300             }
8301             SendToProgram("undo\n", cps);
8302             return;
8303
8304           case MachinePlaysWhite:
8305           case IcsPlayingWhite:
8306             machineWhite = TRUE;
8307             break;
8308
8309           case MachinePlaysBlack:
8310           case IcsPlayingBlack:
8311             machineWhite = FALSE;
8312             break;
8313
8314           case TwoMachinesPlay:
8315             machineWhite = (cps->twoMachinesColor[0] == 'w');
8316             break;
8317         }
8318         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8319             if (appData.debugMode) {
8320                 fprintf(debugFP,
8321                         "Ignoring move out of turn by %s, gameMode %d"
8322                         ", forwardMost %d\n",
8323                         cps->which, gameMode, forwardMostMove);
8324             }
8325             return;
8326         }
8327
8328         if(cps->alphaRank) AlphaRank(machineMove, 4);
8329         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8330                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8331             /* Machine move could not be parsed; ignore it. */
8332           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8333                     machineMove, _(cps->which));
8334             DisplayMoveError(buf1);
8335             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
8336                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
8337             if (gameMode == TwoMachinesPlay) {
8338               GameEnds(machineWhite ? BlackWins : WhiteWins,
8339                        buf1, GE_XBOARD);
8340             }
8341             return;
8342         }
8343
8344         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8345         /* So we have to redo legality test with true e.p. status here,  */
8346         /* to make sure an illegal e.p. capture does not slip through,   */
8347         /* to cause a forfeit on a justified illegal-move complaint      */
8348         /* of the opponent.                                              */
8349         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8350            ChessMove moveType;
8351            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8352                              fromY, fromX, toY, toX, promoChar);
8353             if(moveType == IllegalMove) {
8354               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8355                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8356                 GameEnds(machineWhite ? BlackWins : WhiteWins,
8357                            buf1, GE_XBOARD);
8358                 return;
8359            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
8360            /* [HGM] Kludge to handle engines that send FRC-style castling
8361               when they shouldn't (like TSCP-Gothic) */
8362            switch(moveType) {
8363              case WhiteASideCastleFR:
8364              case BlackASideCastleFR:
8365                toX+=2;
8366                currentMoveString[2]++;
8367                break;
8368              case WhiteHSideCastleFR:
8369              case BlackHSideCastleFR:
8370                toX--;
8371                currentMoveString[2]--;
8372                break;
8373              default: ; // nothing to do, but suppresses warning of pedantic compilers
8374            }
8375         }
8376         hintRequested = FALSE;
8377         lastHint[0] = NULLCHAR;
8378         bookRequested = FALSE;
8379         /* Program may be pondering now */
8380         cps->maybeThinking = TRUE;
8381         if (cps->sendTime == 2) cps->sendTime = 1;
8382         if (cps->offeredDraw) cps->offeredDraw--;
8383
8384         /* [AS] Save move info*/
8385         pvInfoList[ forwardMostMove ].score = programStats.score;
8386         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8387         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8388
8389         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8390
8391         /* Test suites abort the 'game' after one move */
8392         if(*appData.finger) {
8393            static FILE *f;
8394            char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8395            if(!f) f = fopen(appData.finger, "w");
8396            if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8397            else { DisplayFatalError("Bad output file", errno, 0); return; }
8398            free(fen);
8399            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8400         }
8401
8402         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8403         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8404             int count = 0;
8405
8406             while( count < adjudicateLossPlies ) {
8407                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8408
8409                 if( count & 1 ) {
8410                     score = -score; /* Flip score for winning side */
8411                 }
8412
8413                 if( score > adjudicateLossThreshold ) {
8414                     break;
8415                 }
8416
8417                 count++;
8418             }
8419
8420             if( count >= adjudicateLossPlies ) {
8421                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8422
8423                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8424                     "Xboard adjudication",
8425                     GE_XBOARD );
8426
8427                 return;
8428             }
8429         }
8430
8431         if(Adjudicate(cps)) {
8432             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8433             return; // [HGM] adjudicate: for all automatic game ends
8434         }
8435
8436 #if ZIPPY
8437         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8438             first.initDone) {
8439           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8440                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8441                 SendToICS("draw ");
8442                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8443           }
8444           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8445           ics_user_moved = 1;
8446           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8447                 char buf[3*MSG_SIZ];
8448
8449                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8450                         programStats.score / 100.,
8451                         programStats.depth,
8452                         programStats.time / 100.,
8453                         (unsigned int)programStats.nodes,
8454                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8455                         programStats.movelist);
8456                 SendToICS(buf);
8457 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8458           }
8459         }
8460 #endif
8461
8462         /* [AS] Clear stats for next move */
8463         ClearProgramStats();
8464         thinkOutput[0] = NULLCHAR;
8465         hiddenThinkOutputState = 0;
8466
8467         bookHit = NULL;
8468         if (gameMode == TwoMachinesPlay) {
8469             /* [HGM] relaying draw offers moved to after reception of move */
8470             /* and interpreting offer as claim if it brings draw condition */
8471             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8472                 SendToProgram("draw\n", cps->other);
8473             }
8474             if (cps->other->sendTime) {
8475                 SendTimeRemaining(cps->other,
8476                                   cps->other->twoMachinesColor[0] == 'w');
8477             }
8478             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8479             if (firstMove && !bookHit) {
8480                 firstMove = FALSE;
8481                 if (cps->other->useColors) {
8482                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8483                 }
8484                 SendToProgram("go\n", cps->other);
8485             }
8486             cps->other->maybeThinking = TRUE;
8487         }
8488
8489         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8490
8491         if (!pausing && appData.ringBellAfterMoves) {
8492             RingBell();
8493         }
8494
8495         /*
8496          * Reenable menu items that were disabled while
8497          * machine was thinking
8498          */
8499         if (gameMode != TwoMachinesPlay)
8500             SetUserThinkingEnables();
8501
8502         // [HGM] book: after book hit opponent has received move and is now in force mode
8503         // force the book reply into it, and then fake that it outputted this move by jumping
8504         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8505         if(bookHit) {
8506                 static char bookMove[MSG_SIZ]; // a bit generous?
8507
8508                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8509                 strcat(bookMove, bookHit);
8510                 message = bookMove;
8511                 cps = cps->other;
8512                 programStats.nodes = programStats.depth = programStats.time =
8513                 programStats.score = programStats.got_only_move = 0;
8514                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8515
8516                 if(cps->lastPing != cps->lastPong) {
8517                     savedMessage = message; // args for deferred call
8518                     savedState = cps;
8519                     ScheduleDelayedEvent(DeferredBookMove, 10);
8520                     return;
8521                 }
8522                 goto FakeBookMove;
8523         }
8524
8525         return;
8526     }
8527
8528     /* Set special modes for chess engines.  Later something general
8529      *  could be added here; for now there is just one kludge feature,
8530      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8531      *  when "xboard" is given as an interactive command.
8532      */
8533     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8534         cps->useSigint = FALSE;
8535         cps->useSigterm = FALSE;
8536     }
8537     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8538       ParseFeatures(message+8, cps);
8539       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8540     }
8541
8542     if (!strncmp(message, "setup ", 6) && 
8543         (!appData.testLegality || gameInfo.variant == VariantFairy || NonStandardBoardSize())
8544                                         ) { // [HGM] allow first engine to define opening position
8545       int dummy, s=6; char buf[MSG_SIZ];
8546       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8547       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8548       if(startedFromSetupPosition) return;
8549       if(sscanf(message+s, "%dx%d+%d", &dummy, &dummy, &dummy) == 3) while(message[s] && message[s++] != ' '); // for compatibility with Alien Edition
8550       ParseFEN(boards[0], &dummy, message+s);
8551       DrawPosition(TRUE, boards[0]);
8552       startedFromSetupPosition = TRUE;
8553       return;
8554     }
8555     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8556      * want this, I was asked to put it in, and obliged.
8557      */
8558     if (!strncmp(message, "setboard ", 9)) {
8559         Board initial_position;
8560
8561         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8562
8563         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8564             DisplayError(_("Bad FEN received from engine"), 0);
8565             return ;
8566         } else {
8567            Reset(TRUE, FALSE);
8568            CopyBoard(boards[0], initial_position);
8569            initialRulePlies = FENrulePlies;
8570            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8571            else gameMode = MachinePlaysBlack;
8572            DrawPosition(FALSE, boards[currentMove]);
8573         }
8574         return;
8575     }
8576
8577     /*
8578      * Look for communication commands
8579      */
8580     if (!strncmp(message, "telluser ", 9)) {
8581         if(message[9] == '\\' && message[10] == '\\')
8582             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8583         PlayTellSound();
8584         DisplayNote(message + 9);
8585         return;
8586     }
8587     if (!strncmp(message, "tellusererror ", 14)) {
8588         cps->userError = 1;
8589         if(message[14] == '\\' && message[15] == '\\')
8590             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8591         PlayTellSound();
8592         DisplayError(message + 14, 0);
8593         return;
8594     }
8595     if (!strncmp(message, "tellopponent ", 13)) {
8596       if (appData.icsActive) {
8597         if (loggedOn) {
8598           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8599           SendToICS(buf1);
8600         }
8601       } else {
8602         DisplayNote(message + 13);
8603       }
8604       return;
8605     }
8606     if (!strncmp(message, "tellothers ", 11)) {
8607       if (appData.icsActive) {
8608         if (loggedOn) {
8609           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8610           SendToICS(buf1);
8611         }
8612       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
8613       return;
8614     }
8615     if (!strncmp(message, "tellall ", 8)) {
8616       if (appData.icsActive) {
8617         if (loggedOn) {
8618           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8619           SendToICS(buf1);
8620         }
8621       } else {
8622         DisplayNote(message + 8);
8623       }
8624       return;
8625     }
8626     if (strncmp(message, "warning", 7) == 0) {
8627         /* Undocumented feature, use tellusererror in new code */
8628         DisplayError(message, 0);
8629         return;
8630     }
8631     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8632         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8633         strcat(realname, " query");
8634         AskQuestion(realname, buf2, buf1, cps->pr);
8635         return;
8636     }
8637     /* Commands from the engine directly to ICS.  We don't allow these to be
8638      *  sent until we are logged on. Crafty kibitzes have been known to
8639      *  interfere with the login process.
8640      */
8641     if (loggedOn) {
8642         if (!strncmp(message, "tellics ", 8)) {
8643             SendToICS(message + 8);
8644             SendToICS("\n");
8645             return;
8646         }
8647         if (!strncmp(message, "tellicsnoalias ", 15)) {
8648             SendToICS(ics_prefix);
8649             SendToICS(message + 15);
8650             SendToICS("\n");
8651             return;
8652         }
8653         /* The following are for backward compatibility only */
8654         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8655             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8656             SendToICS(ics_prefix);
8657             SendToICS(message);
8658             SendToICS("\n");
8659             return;
8660         }
8661     }
8662     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8663         return;
8664     }
8665     if(!strncmp(message, "highlight ", 10)) {
8666         if(appData.testLegality && appData.markers) return;
8667         MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
8668         return;
8669     }
8670     if(!strncmp(message, "click ", 6)) {
8671         char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
8672         if(appData.testLegality || !appData.oneClick) return;
8673         sscanf(message+6, "%c%d%c", &f, &y, &c);
8674         x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
8675         if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
8676         x = x*squareSize + (x+1)*lineGap + squareSize/2;
8677         y = y*squareSize + (y+1)*lineGap + squareSize/2;
8678         f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
8679         if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
8680             LeftClick(Release, lastLeftX, lastLeftY);
8681         controlKey  = (c == ',');
8682         LeftClick(Press, x, y);
8683         LeftClick(Release, x, y);
8684         first.highlight = f;
8685         return;
8686     }
8687     /*
8688      * If the move is illegal, cancel it and redraw the board.
8689      * Also deal with other error cases.  Matching is rather loose
8690      * here to accommodate engines written before the spec.
8691      */
8692     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8693         strncmp(message, "Error", 5) == 0) {
8694         if (StrStr(message, "name") ||
8695             StrStr(message, "rating") || StrStr(message, "?") ||
8696             StrStr(message, "result") || StrStr(message, "board") ||
8697             StrStr(message, "bk") || StrStr(message, "computer") ||
8698             StrStr(message, "variant") || StrStr(message, "hint") ||
8699             StrStr(message, "random") || StrStr(message, "depth") ||
8700             StrStr(message, "accepted")) {
8701             return;
8702         }
8703         if (StrStr(message, "protover")) {
8704           /* Program is responding to input, so it's apparently done
8705              initializing, and this error message indicates it is
8706              protocol version 1.  So we don't need to wait any longer
8707              for it to initialize and send feature commands. */
8708           FeatureDone(cps, 1);
8709           cps->protocolVersion = 1;
8710           return;
8711         }
8712         cps->maybeThinking = FALSE;
8713
8714         if (StrStr(message, "draw")) {
8715             /* Program doesn't have "draw" command */
8716             cps->sendDrawOffers = 0;
8717             return;
8718         }
8719         if (cps->sendTime != 1 &&
8720             (StrStr(message, "time") || StrStr(message, "otim"))) {
8721           /* Program apparently doesn't have "time" or "otim" command */
8722           cps->sendTime = 0;
8723           return;
8724         }
8725         if (StrStr(message, "analyze")) {
8726             cps->analysisSupport = FALSE;
8727             cps->analyzing = FALSE;
8728 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8729             EditGameEvent(); // [HGM] try to preserve loaded game
8730             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8731             DisplayError(buf2, 0);
8732             return;
8733         }
8734         if (StrStr(message, "(no matching move)st")) {
8735           /* Special kludge for GNU Chess 4 only */
8736           cps->stKludge = TRUE;
8737           SendTimeControl(cps, movesPerSession, timeControl,
8738                           timeIncrement, appData.searchDepth,
8739                           searchTime);
8740           return;
8741         }
8742         if (StrStr(message, "(no matching move)sd")) {
8743           /* Special kludge for GNU Chess 4 only */
8744           cps->sdKludge = TRUE;
8745           SendTimeControl(cps, movesPerSession, timeControl,
8746                           timeIncrement, appData.searchDepth,
8747                           searchTime);
8748           return;
8749         }
8750         if (!StrStr(message, "llegal")) {
8751             return;
8752         }
8753         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8754             gameMode == IcsIdle) return;
8755         if (forwardMostMove <= backwardMostMove) return;
8756         if (pausing) PauseEvent();
8757       if(appData.forceIllegal) {
8758             // [HGM] illegal: machine refused move; force position after move into it
8759           SendToProgram("force\n", cps);
8760           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8761                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8762                 // when black is to move, while there might be nothing on a2 or black
8763                 // might already have the move. So send the board as if white has the move.
8764                 // But first we must change the stm of the engine, as it refused the last move
8765                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8766                 if(WhiteOnMove(forwardMostMove)) {
8767                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8768                     SendBoard(cps, forwardMostMove); // kludgeless board
8769                 } else {
8770                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8771                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8772                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8773                 }
8774           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8775             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8776                  gameMode == TwoMachinesPlay)
8777               SendToProgram("go\n", cps);
8778             return;
8779       } else
8780         if (gameMode == PlayFromGameFile) {
8781             /* Stop reading this game file */
8782             gameMode = EditGame;
8783             ModeHighlight();
8784         }
8785         /* [HGM] illegal-move claim should forfeit game when Xboard */
8786         /* only passes fully legal moves                            */
8787         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8788             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8789                                 "False illegal-move claim", GE_XBOARD );
8790             return; // do not take back move we tested as valid
8791         }
8792         currentMove = forwardMostMove-1;
8793         DisplayMove(currentMove-1); /* before DisplayMoveError */
8794         SwitchClocks(forwardMostMove-1); // [HGM] race
8795         DisplayBothClocks();
8796         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8797                 parseList[currentMove], _(cps->which));
8798         DisplayMoveError(buf1);
8799         DrawPosition(FALSE, boards[currentMove]);
8800
8801         SetUserThinkingEnables();
8802         return;
8803     }
8804     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8805         /* Program has a broken "time" command that
8806            outputs a string not ending in newline.
8807            Don't use it. */
8808         cps->sendTime = 0;
8809     }
8810
8811     /*
8812      * If chess program startup fails, exit with an error message.
8813      * Attempts to recover here are futile. [HGM] Well, we try anyway
8814      */
8815     if ((StrStr(message, "unknown host") != NULL)
8816         || (StrStr(message, "No remote directory") != NULL)
8817         || (StrStr(message, "not found") != NULL)
8818         || (StrStr(message, "No such file") != NULL)
8819         || (StrStr(message, "can't alloc") != NULL)
8820         || (StrStr(message, "Permission denied") != NULL)) {
8821
8822         cps->maybeThinking = FALSE;
8823         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8824                 _(cps->which), cps->program, cps->host, message);
8825         RemoveInputSource(cps->isr);
8826         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8827             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
8828             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
8829         }
8830         return;
8831     }
8832
8833     /*
8834      * Look for hint output
8835      */
8836     if (sscanf(message, "Hint: %s", buf1) == 1) {
8837         if (cps == &first && hintRequested) {
8838             hintRequested = FALSE;
8839             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8840                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8841                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8842                                     PosFlags(forwardMostMove),
8843                                     fromY, fromX, toY, toX, promoChar, buf1);
8844                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8845                 DisplayInformation(buf2);
8846             } else {
8847                 /* Hint move could not be parsed!? */
8848               snprintf(buf2, sizeof(buf2),
8849                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8850                         buf1, _(cps->which));
8851                 DisplayError(buf2, 0);
8852             }
8853         } else {
8854           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8855         }
8856         return;
8857     }
8858
8859     /*
8860      * Ignore other messages if game is not in progress
8861      */
8862     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8863         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8864
8865     /*
8866      * look for win, lose, draw, or draw offer
8867      */
8868     if (strncmp(message, "1-0", 3) == 0) {
8869         char *p, *q, *r = "";
8870         p = strchr(message, '{');
8871         if (p) {
8872             q = strchr(p, '}');
8873             if (q) {
8874                 *q = NULLCHAR;
8875                 r = p + 1;
8876             }
8877         }
8878         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8879         return;
8880     } else if (strncmp(message, "0-1", 3) == 0) {
8881         char *p, *q, *r = "";
8882         p = strchr(message, '{');
8883         if (p) {
8884             q = strchr(p, '}');
8885             if (q) {
8886                 *q = NULLCHAR;
8887                 r = p + 1;
8888             }
8889         }
8890         /* Kludge for Arasan 4.1 bug */
8891         if (strcmp(r, "Black resigns") == 0) {
8892             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8893             return;
8894         }
8895         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8896         return;
8897     } else if (strncmp(message, "1/2", 3) == 0) {
8898         char *p, *q, *r = "";
8899         p = strchr(message, '{');
8900         if (p) {
8901             q = strchr(p, '}');
8902             if (q) {
8903                 *q = NULLCHAR;
8904                 r = p + 1;
8905             }
8906         }
8907
8908         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8909         return;
8910
8911     } else if (strncmp(message, "White resign", 12) == 0) {
8912         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8913         return;
8914     } else if (strncmp(message, "Black resign", 12) == 0) {
8915         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8916         return;
8917     } else if (strncmp(message, "White matches", 13) == 0 ||
8918                strncmp(message, "Black matches", 13) == 0   ) {
8919         /* [HGM] ignore GNUShogi noises */
8920         return;
8921     } else if (strncmp(message, "White", 5) == 0 &&
8922                message[5] != '(' &&
8923                StrStr(message, "Black") == NULL) {
8924         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8925         return;
8926     } else if (strncmp(message, "Black", 5) == 0 &&
8927                message[5] != '(') {
8928         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8929         return;
8930     } else if (strcmp(message, "resign") == 0 ||
8931                strcmp(message, "computer resigns") == 0) {
8932         switch (gameMode) {
8933           case MachinePlaysBlack:
8934           case IcsPlayingBlack:
8935             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8936             break;
8937           case MachinePlaysWhite:
8938           case IcsPlayingWhite:
8939             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8940             break;
8941           case TwoMachinesPlay:
8942             if (cps->twoMachinesColor[0] == 'w')
8943               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8944             else
8945               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8946             break;
8947           default:
8948             /* can't happen */
8949             break;
8950         }
8951         return;
8952     } else if (strncmp(message, "opponent mates", 14) == 0) {
8953         switch (gameMode) {
8954           case MachinePlaysBlack:
8955           case IcsPlayingBlack:
8956             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8957             break;
8958           case MachinePlaysWhite:
8959           case IcsPlayingWhite:
8960             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8961             break;
8962           case TwoMachinesPlay:
8963             if (cps->twoMachinesColor[0] == 'w')
8964               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8965             else
8966               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8967             break;
8968           default:
8969             /* can't happen */
8970             break;
8971         }
8972         return;
8973     } else if (strncmp(message, "computer mates", 14) == 0) {
8974         switch (gameMode) {
8975           case MachinePlaysBlack:
8976           case IcsPlayingBlack:
8977             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8978             break;
8979           case MachinePlaysWhite:
8980           case IcsPlayingWhite:
8981             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8982             break;
8983           case TwoMachinesPlay:
8984             if (cps->twoMachinesColor[0] == 'w')
8985               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8986             else
8987               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8988             break;
8989           default:
8990             /* can't happen */
8991             break;
8992         }
8993         return;
8994     } else if (strncmp(message, "checkmate", 9) == 0) {
8995         if (WhiteOnMove(forwardMostMove)) {
8996             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8997         } else {
8998             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8999         }
9000         return;
9001     } else if (strstr(message, "Draw") != NULL ||
9002                strstr(message, "game is a draw") != NULL) {
9003         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9004         return;
9005     } else if (strstr(message, "offer") != NULL &&
9006                strstr(message, "draw") != NULL) {
9007 #if ZIPPY
9008         if (appData.zippyPlay && first.initDone) {
9009             /* Relay offer to ICS */
9010             SendToICS(ics_prefix);
9011             SendToICS("draw\n");
9012         }
9013 #endif
9014         cps->offeredDraw = 2; /* valid until this engine moves twice */
9015         if (gameMode == TwoMachinesPlay) {
9016             if (cps->other->offeredDraw) {
9017                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9018             /* [HGM] in two-machine mode we delay relaying draw offer      */
9019             /* until after we also have move, to see if it is really claim */
9020             }
9021         } else if (gameMode == MachinePlaysWhite ||
9022                    gameMode == MachinePlaysBlack) {
9023           if (userOfferedDraw) {
9024             DisplayInformation(_("Machine accepts your draw offer"));
9025             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9026           } else {
9027             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
9028           }
9029         }
9030     }
9031
9032
9033     /*
9034      * Look for thinking output
9035      */
9036     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9037           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9038                                 ) {
9039         int plylev, mvleft, mvtot, curscore, time;
9040         char mvname[MOVE_LEN];
9041         u64 nodes; // [DM]
9042         char plyext;
9043         int ignore = FALSE;
9044         int prefixHint = FALSE;
9045         mvname[0] = NULLCHAR;
9046
9047         switch (gameMode) {
9048           case MachinePlaysBlack:
9049           case IcsPlayingBlack:
9050             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9051             break;
9052           case MachinePlaysWhite:
9053           case IcsPlayingWhite:
9054             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9055             break;
9056           case AnalyzeMode:
9057           case AnalyzeFile:
9058             break;
9059           case IcsObserving: /* [DM] icsEngineAnalyze */
9060             if (!appData.icsEngineAnalyze) ignore = TRUE;
9061             break;
9062           case TwoMachinesPlay:
9063             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9064                 ignore = TRUE;
9065             }
9066             break;
9067           default:
9068             ignore = TRUE;
9069             break;
9070         }
9071
9072         if (!ignore) {
9073             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9074             buf1[0] = NULLCHAR;
9075             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9076                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9077
9078                 if (plyext != ' ' && plyext != '\t') {
9079                     time *= 100;
9080                 }
9081
9082                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9083                 if( cps->scoreIsAbsolute &&
9084                     ( gameMode == MachinePlaysBlack ||
9085                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9086                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
9087                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9088                      !WhiteOnMove(currentMove)
9089                     ) )
9090                 {
9091                     curscore = -curscore;
9092                 }
9093
9094                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9095
9096                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9097                         char buf[MSG_SIZ];
9098                         FILE *f;
9099                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9100                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9101                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9102                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9103                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
9104                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9105                                 fclose(f);
9106                         } else DisplayError(_("failed writing PV"), 0);
9107                 }
9108
9109                 tempStats.depth = plylev;
9110                 tempStats.nodes = nodes;
9111                 tempStats.time = time;
9112                 tempStats.score = curscore;
9113                 tempStats.got_only_move = 0;
9114
9115                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9116                         int ticklen;
9117
9118                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
9119                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9120                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9121                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9122                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9123                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9124                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9125                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9126                 }
9127
9128                 /* Buffer overflow protection */
9129                 if (pv[0] != NULLCHAR) {
9130                     if (strlen(pv) >= sizeof(tempStats.movelist)
9131                         && appData.debugMode) {
9132                         fprintf(debugFP,
9133                                 "PV is too long; using the first %u bytes.\n",
9134                                 (unsigned) sizeof(tempStats.movelist) - 1);
9135                     }
9136
9137                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9138                 } else {
9139                     sprintf(tempStats.movelist, " no PV\n");
9140                 }
9141
9142                 if (tempStats.seen_stat) {
9143                     tempStats.ok_to_send = 1;
9144                 }
9145
9146                 if (strchr(tempStats.movelist, '(') != NULL) {
9147                     tempStats.line_is_book = 1;
9148                     tempStats.nr_moves = 0;
9149                     tempStats.moves_left = 0;
9150                 } else {
9151                     tempStats.line_is_book = 0;
9152                 }
9153
9154                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9155                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9156
9157                 SendProgramStatsToFrontend( cps, &tempStats );
9158
9159                 /*
9160                     [AS] Protect the thinkOutput buffer from overflow... this
9161                     is only useful if buf1 hasn't overflowed first!
9162                 */
9163                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
9164                          plylev,
9165                          (gameMode == TwoMachinesPlay ?
9166                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9167                          ((double) curscore) / 100.0,
9168                          prefixHint ? lastHint : "",
9169                          prefixHint ? " " : "" );
9170
9171                 if( buf1[0] != NULLCHAR ) {
9172                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9173
9174                     if( strlen(pv) > max_len ) {
9175                         if( appData.debugMode) {
9176                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9177                         }
9178                         pv[max_len+1] = '\0';
9179                     }
9180
9181                     strcat( thinkOutput, pv);
9182                 }
9183
9184                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9185                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9186                     DisplayMove(currentMove - 1);
9187                 }
9188                 return;
9189
9190             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9191                 /* crafty (9.25+) says "(only move) <move>"
9192                  * if there is only 1 legal move
9193                  */
9194                 sscanf(p, "(only move) %s", buf1);
9195                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9196                 sprintf(programStats.movelist, "%s (only move)", buf1);
9197                 programStats.depth = 1;
9198                 programStats.nr_moves = 1;
9199                 programStats.moves_left = 1;
9200                 programStats.nodes = 1;
9201                 programStats.time = 1;
9202                 programStats.got_only_move = 1;
9203
9204                 /* Not really, but we also use this member to
9205                    mean "line isn't going to change" (Crafty
9206                    isn't searching, so stats won't change) */
9207                 programStats.line_is_book = 1;
9208
9209                 SendProgramStatsToFrontend( cps, &programStats );
9210
9211                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9212                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9213                     DisplayMove(currentMove - 1);
9214                 }
9215                 return;
9216             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9217                               &time, &nodes, &plylev, &mvleft,
9218                               &mvtot, mvname) >= 5) {
9219                 /* The stat01: line is from Crafty (9.29+) in response
9220                    to the "." command */
9221                 programStats.seen_stat = 1;
9222                 cps->maybeThinking = TRUE;
9223
9224                 if (programStats.got_only_move || !appData.periodicUpdates)
9225                   return;
9226
9227                 programStats.depth = plylev;
9228                 programStats.time = time;
9229                 programStats.nodes = nodes;
9230                 programStats.moves_left = mvleft;
9231                 programStats.nr_moves = mvtot;
9232                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9233                 programStats.ok_to_send = 1;
9234                 programStats.movelist[0] = '\0';
9235
9236                 SendProgramStatsToFrontend( cps, &programStats );
9237
9238                 return;
9239
9240             } else if (strncmp(message,"++",2) == 0) {
9241                 /* Crafty 9.29+ outputs this */
9242                 programStats.got_fail = 2;
9243                 return;
9244
9245             } else if (strncmp(message,"--",2) == 0) {
9246                 /* Crafty 9.29+ outputs this */
9247                 programStats.got_fail = 1;
9248                 return;
9249
9250             } else if (thinkOutput[0] != NULLCHAR &&
9251                        strncmp(message, "    ", 4) == 0) {
9252                 unsigned message_len;
9253
9254                 p = message;
9255                 while (*p && *p == ' ') p++;
9256
9257                 message_len = strlen( p );
9258
9259                 /* [AS] Avoid buffer overflow */
9260                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9261                     strcat(thinkOutput, " ");
9262                     strcat(thinkOutput, p);
9263                 }
9264
9265                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9266                     strcat(programStats.movelist, " ");
9267                     strcat(programStats.movelist, p);
9268                 }
9269
9270                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9271                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9272                     DisplayMove(currentMove - 1);
9273                 }
9274                 return;
9275             }
9276         }
9277         else {
9278             buf1[0] = NULLCHAR;
9279
9280             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9281                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9282             {
9283                 ChessProgramStats cpstats;
9284
9285                 if (plyext != ' ' && plyext != '\t') {
9286                     time *= 100;
9287                 }
9288
9289                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9290                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9291                     curscore = -curscore;
9292                 }
9293
9294                 cpstats.depth = plylev;
9295                 cpstats.nodes = nodes;
9296                 cpstats.time = time;
9297                 cpstats.score = curscore;
9298                 cpstats.got_only_move = 0;
9299                 cpstats.movelist[0] = '\0';
9300
9301                 if (buf1[0] != NULLCHAR) {
9302                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9303                 }
9304
9305                 cpstats.ok_to_send = 0;
9306                 cpstats.line_is_book = 0;
9307                 cpstats.nr_moves = 0;
9308                 cpstats.moves_left = 0;
9309
9310                 SendProgramStatsToFrontend( cps, &cpstats );
9311             }
9312         }
9313     }
9314 }
9315
9316
9317 /* Parse a game score from the character string "game", and
9318    record it as the history of the current game.  The game
9319    score is NOT assumed to start from the standard position.
9320    The display is not updated in any way.
9321    */
9322 void
9323 ParseGameHistory (char *game)
9324 {
9325     ChessMove moveType;
9326     int fromX, fromY, toX, toY, boardIndex;
9327     char promoChar;
9328     char *p, *q;
9329     char buf[MSG_SIZ];
9330
9331     if (appData.debugMode)
9332       fprintf(debugFP, "Parsing game history: %s\n", game);
9333
9334     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9335     gameInfo.site = StrSave(appData.icsHost);
9336     gameInfo.date = PGNDate();
9337     gameInfo.round = StrSave("-");
9338
9339     /* Parse out names of players */
9340     while (*game == ' ') game++;
9341     p = buf;
9342     while (*game != ' ') *p++ = *game++;
9343     *p = NULLCHAR;
9344     gameInfo.white = StrSave(buf);
9345     while (*game == ' ') game++;
9346     p = buf;
9347     while (*game != ' ' && *game != '\n') *p++ = *game++;
9348     *p = NULLCHAR;
9349     gameInfo.black = StrSave(buf);
9350
9351     /* Parse moves */
9352     boardIndex = blackPlaysFirst ? 1 : 0;
9353     yynewstr(game);
9354     for (;;) {
9355         yyboardindex = boardIndex;
9356         moveType = (ChessMove) Myylex();
9357         switch (moveType) {
9358           case IllegalMove:             /* maybe suicide chess, etc. */
9359   if (appData.debugMode) {
9360     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9361     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9362     setbuf(debugFP, NULL);
9363   }
9364           case WhitePromotion:
9365           case BlackPromotion:
9366           case WhiteNonPromotion:
9367           case BlackNonPromotion:
9368           case NormalMove:
9369           case WhiteCapturesEnPassant:
9370           case BlackCapturesEnPassant:
9371           case WhiteKingSideCastle:
9372           case WhiteQueenSideCastle:
9373           case BlackKingSideCastle:
9374           case BlackQueenSideCastle:
9375           case WhiteKingSideCastleWild:
9376           case WhiteQueenSideCastleWild:
9377           case BlackKingSideCastleWild:
9378           case BlackQueenSideCastleWild:
9379           /* PUSH Fabien */
9380           case WhiteHSideCastleFR:
9381           case WhiteASideCastleFR:
9382           case BlackHSideCastleFR:
9383           case BlackASideCastleFR:
9384           /* POP Fabien */
9385             fromX = currentMoveString[0] - AAA;
9386             fromY = currentMoveString[1] - ONE;
9387             toX = currentMoveString[2] - AAA;
9388             toY = currentMoveString[3] - ONE;
9389             promoChar = currentMoveString[4];
9390             break;
9391           case WhiteDrop:
9392           case BlackDrop:
9393             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9394             fromX = moveType == WhiteDrop ?
9395               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9396             (int) CharToPiece(ToLower(currentMoveString[0]));
9397             fromY = DROP_RANK;
9398             toX = currentMoveString[2] - AAA;
9399             toY = currentMoveString[3] - ONE;
9400             promoChar = NULLCHAR;
9401             break;
9402           case AmbiguousMove:
9403             /* bug? */
9404             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9405   if (appData.debugMode) {
9406     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9407     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9408     setbuf(debugFP, NULL);
9409   }
9410             DisplayError(buf, 0);
9411             return;
9412           case ImpossibleMove:
9413             /* bug? */
9414             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9415   if (appData.debugMode) {
9416     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9417     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9418     setbuf(debugFP, NULL);
9419   }
9420             DisplayError(buf, 0);
9421             return;
9422           case EndOfFile:
9423             if (boardIndex < backwardMostMove) {
9424                 /* Oops, gap.  How did that happen? */
9425                 DisplayError(_("Gap in move list"), 0);
9426                 return;
9427             }
9428             backwardMostMove =  blackPlaysFirst ? 1 : 0;
9429             if (boardIndex > forwardMostMove) {
9430                 forwardMostMove = boardIndex;
9431             }
9432             return;
9433           case ElapsedTime:
9434             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9435                 strcat(parseList[boardIndex-1], " ");
9436                 strcat(parseList[boardIndex-1], yy_text);
9437             }
9438             continue;
9439           case Comment:
9440           case PGNTag:
9441           case NAG:
9442           default:
9443             /* ignore */
9444             continue;
9445           case WhiteWins:
9446           case BlackWins:
9447           case GameIsDrawn:
9448           case GameUnfinished:
9449             if (gameMode == IcsExamining) {
9450                 if (boardIndex < backwardMostMove) {
9451                     /* Oops, gap.  How did that happen? */
9452                     return;
9453                 }
9454                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9455                 return;
9456             }
9457             gameInfo.result = moveType;
9458             p = strchr(yy_text, '{');
9459             if (p == NULL) p = strchr(yy_text, '(');
9460             if (p == NULL) {
9461                 p = yy_text;
9462                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9463             } else {
9464                 q = strchr(p, *p == '{' ? '}' : ')');
9465                 if (q != NULL) *q = NULLCHAR;
9466                 p++;
9467             }
9468             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9469             gameInfo.resultDetails = StrSave(p);
9470             continue;
9471         }
9472         if (boardIndex >= forwardMostMove &&
9473             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9474             backwardMostMove = blackPlaysFirst ? 1 : 0;
9475             return;
9476         }
9477         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9478                                  fromY, fromX, toY, toX, promoChar,
9479                                  parseList[boardIndex]);
9480         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9481         /* currentMoveString is set as a side-effect of yylex */
9482         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9483         strcat(moveList[boardIndex], "\n");
9484         boardIndex++;
9485         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9486         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9487           case MT_NONE:
9488           case MT_STALEMATE:
9489           default:
9490             break;
9491           case MT_CHECK:
9492             if(gameInfo.variant != VariantShogi)
9493                 strcat(parseList[boardIndex - 1], "+");
9494             break;
9495           case MT_CHECKMATE:
9496           case MT_STAINMATE:
9497             strcat(parseList[boardIndex - 1], "#");
9498             break;
9499         }
9500     }
9501 }
9502
9503
9504 /* Apply a move to the given board  */
9505 void
9506 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9507 {
9508   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9509   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9510
9511     /* [HGM] compute & store e.p. status and castling rights for new position */
9512     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9513
9514       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9515       oldEP = (signed char)board[EP_STATUS];
9516       board[EP_STATUS] = EP_NONE;
9517
9518   if (fromY == DROP_RANK) {
9519         /* must be first */
9520         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9521             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9522             return;
9523         }
9524         piece = board[toY][toX] = (ChessSquare) fromX;
9525   } else {
9526       int i;
9527
9528       if( board[toY][toX] != EmptySquare )
9529            board[EP_STATUS] = EP_CAPTURE;
9530
9531       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9532            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9533                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9534       } else
9535       if( board[fromY][fromX] == WhitePawn ) {
9536            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9537                board[EP_STATUS] = EP_PAWN_MOVE;
9538            if( toY-fromY==2) {
9539                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9540                         gameInfo.variant != VariantBerolina || toX < fromX)
9541                       board[EP_STATUS] = toX | berolina;
9542                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9543                         gameInfo.variant != VariantBerolina || toX > fromX)
9544                       board[EP_STATUS] = toX;
9545            }
9546       } else
9547       if( board[fromY][fromX] == BlackPawn ) {
9548            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9549                board[EP_STATUS] = EP_PAWN_MOVE;
9550            if( toY-fromY== -2) {
9551                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9552                         gameInfo.variant != VariantBerolina || toX < fromX)
9553                       board[EP_STATUS] = toX | berolina;
9554                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9555                         gameInfo.variant != VariantBerolina || toX > fromX)
9556                       board[EP_STATUS] = toX;
9557            }
9558        }
9559
9560        for(i=0; i<nrCastlingRights; i++) {
9561            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9562               board[CASTLING][i] == toX   && castlingRank[i] == toY
9563              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9564        }
9565
9566        if(gameInfo.variant == VariantSChess) { // update virginity
9567            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
9568            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
9569            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
9570            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
9571        }
9572
9573      if (fromX == toX && fromY == toY) return;
9574
9575      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9576      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9577      if(gameInfo.variant == VariantKnightmate)
9578          king += (int) WhiteUnicorn - (int) WhiteKing;
9579
9580     /* Code added by Tord: */
9581     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9582     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9583         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9584       board[fromY][fromX] = EmptySquare;
9585       board[toY][toX] = EmptySquare;
9586       if((toX > fromX) != (piece == WhiteRook)) {
9587         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9588       } else {
9589         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9590       }
9591     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9592                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9593       board[fromY][fromX] = EmptySquare;
9594       board[toY][toX] = EmptySquare;
9595       if((toX > fromX) != (piece == BlackRook)) {
9596         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9597       } else {
9598         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9599       }
9600     /* End of code added by Tord */
9601
9602     } else if (board[fromY][fromX] == king
9603         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9604         && toY == fromY && toX > fromX+1) {
9605         board[fromY][fromX] = EmptySquare;
9606         board[toY][toX] = king;
9607         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9608         board[fromY][BOARD_RGHT-1] = EmptySquare;
9609     } else if (board[fromY][fromX] == king
9610         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9611                && toY == fromY && toX < fromX-1) {
9612         board[fromY][fromX] = EmptySquare;
9613         board[toY][toX] = king;
9614         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9615         board[fromY][BOARD_LEFT] = EmptySquare;
9616     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9617                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9618                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9619                ) {
9620         /* white pawn promotion */
9621         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9622         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9623             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9624         board[fromY][fromX] = EmptySquare;
9625     } else if ((fromY >= BOARD_HEIGHT>>1)
9626                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9627                && (toX != fromX)
9628                && gameInfo.variant != VariantXiangqi
9629                && gameInfo.variant != VariantBerolina
9630                && (board[fromY][fromX] == WhitePawn)
9631                && (board[toY][toX] == EmptySquare)) {
9632         board[fromY][fromX] = EmptySquare;
9633         board[toY][toX] = WhitePawn;
9634         captured = board[toY - 1][toX];
9635         board[toY - 1][toX] = EmptySquare;
9636     } else if ((fromY == BOARD_HEIGHT-4)
9637                && (toX == fromX)
9638                && gameInfo.variant == VariantBerolina
9639                && (board[fromY][fromX] == WhitePawn)
9640                && (board[toY][toX] == EmptySquare)) {
9641         board[fromY][fromX] = EmptySquare;
9642         board[toY][toX] = WhitePawn;
9643         if(oldEP & EP_BEROLIN_A) {
9644                 captured = board[fromY][fromX-1];
9645                 board[fromY][fromX-1] = EmptySquare;
9646         }else{  captured = board[fromY][fromX+1];
9647                 board[fromY][fromX+1] = EmptySquare;
9648         }
9649     } else if (board[fromY][fromX] == king
9650         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9651                && toY == fromY && toX > fromX+1) {
9652         board[fromY][fromX] = EmptySquare;
9653         board[toY][toX] = king;
9654         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9655         board[fromY][BOARD_RGHT-1] = EmptySquare;
9656     } else if (board[fromY][fromX] == king
9657         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9658                && toY == fromY && toX < fromX-1) {
9659         board[fromY][fromX] = EmptySquare;
9660         board[toY][toX] = king;
9661         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9662         board[fromY][BOARD_LEFT] = EmptySquare;
9663     } else if (fromY == 7 && fromX == 3
9664                && board[fromY][fromX] == BlackKing
9665                && toY == 7 && toX == 5) {
9666         board[fromY][fromX] = EmptySquare;
9667         board[toY][toX] = BlackKing;
9668         board[fromY][7] = EmptySquare;
9669         board[toY][4] = BlackRook;
9670     } else if (fromY == 7 && fromX == 3
9671                && board[fromY][fromX] == BlackKing
9672                && toY == 7 && toX == 1) {
9673         board[fromY][fromX] = EmptySquare;
9674         board[toY][toX] = BlackKing;
9675         board[fromY][0] = EmptySquare;
9676         board[toY][2] = BlackRook;
9677     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9678                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9679                && toY < promoRank && promoChar
9680                ) {
9681         /* black pawn promotion */
9682         board[toY][toX] = CharToPiece(ToLower(promoChar));
9683         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9684             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9685         board[fromY][fromX] = EmptySquare;
9686     } else if ((fromY < BOARD_HEIGHT>>1)
9687                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9688                && (toX != fromX)
9689                && gameInfo.variant != VariantXiangqi
9690                && gameInfo.variant != VariantBerolina
9691                && (board[fromY][fromX] == BlackPawn)
9692                && (board[toY][toX] == EmptySquare)) {
9693         board[fromY][fromX] = EmptySquare;
9694         board[toY][toX] = BlackPawn;
9695         captured = board[toY + 1][toX];
9696         board[toY + 1][toX] = EmptySquare;
9697     } else if ((fromY == 3)
9698                && (toX == fromX)
9699                && gameInfo.variant == VariantBerolina
9700                && (board[fromY][fromX] == BlackPawn)
9701                && (board[toY][toX] == EmptySquare)) {
9702         board[fromY][fromX] = EmptySquare;
9703         board[toY][toX] = BlackPawn;
9704         if(oldEP & EP_BEROLIN_A) {
9705                 captured = board[fromY][fromX-1];
9706                 board[fromY][fromX-1] = EmptySquare;
9707         }else{  captured = board[fromY][fromX+1];
9708                 board[fromY][fromX+1] = EmptySquare;
9709         }
9710     } else {
9711         board[toY][toX] = board[fromY][fromX];
9712         board[fromY][fromX] = EmptySquare;
9713     }
9714   }
9715
9716     if (gameInfo.holdingsWidth != 0) {
9717
9718       /* !!A lot more code needs to be written to support holdings  */
9719       /* [HGM] OK, so I have written it. Holdings are stored in the */
9720       /* penultimate board files, so they are automaticlly stored   */
9721       /* in the game history.                                       */
9722       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9723                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9724         /* Delete from holdings, by decreasing count */
9725         /* and erasing image if necessary            */
9726         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9727         if(p < (int) BlackPawn) { /* white drop */
9728              p -= (int)WhitePawn;
9729                  p = PieceToNumber((ChessSquare)p);
9730              if(p >= gameInfo.holdingsSize) p = 0;
9731              if(--board[p][BOARD_WIDTH-2] <= 0)
9732                   board[p][BOARD_WIDTH-1] = EmptySquare;
9733              if((int)board[p][BOARD_WIDTH-2] < 0)
9734                         board[p][BOARD_WIDTH-2] = 0;
9735         } else {                  /* black drop */
9736              p -= (int)BlackPawn;
9737                  p = PieceToNumber((ChessSquare)p);
9738              if(p >= gameInfo.holdingsSize) p = 0;
9739              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9740                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9741              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9742                         board[BOARD_HEIGHT-1-p][1] = 0;
9743         }
9744       }
9745       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9746           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9747         /* [HGM] holdings: Add to holdings, if holdings exist */
9748         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9749                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9750                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9751         }
9752         p = (int) captured;
9753         if (p >= (int) BlackPawn) {
9754           p -= (int)BlackPawn;
9755           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9756                   /* in Shogi restore piece to its original  first */
9757                   captured = (ChessSquare) (DEMOTED captured);
9758                   p = DEMOTED p;
9759           }
9760           p = PieceToNumber((ChessSquare)p);
9761           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9762           board[p][BOARD_WIDTH-2]++;
9763           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9764         } else {
9765           p -= (int)WhitePawn;
9766           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9767                   captured = (ChessSquare) (DEMOTED captured);
9768                   p = DEMOTED p;
9769           }
9770           p = PieceToNumber((ChessSquare)p);
9771           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9772           board[BOARD_HEIGHT-1-p][1]++;
9773           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9774         }
9775       }
9776     } else if (gameInfo.variant == VariantAtomic) {
9777       if (captured != EmptySquare) {
9778         int y, x;
9779         for (y = toY-1; y <= toY+1; y++) {
9780           for (x = toX-1; x <= toX+1; x++) {
9781             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9782                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9783               board[y][x] = EmptySquare;
9784             }
9785           }
9786         }
9787         board[toY][toX] = EmptySquare;
9788       }
9789     }
9790     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9791         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9792     } else
9793     if(promoChar == '+') {
9794         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
9795         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9796     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9797         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9798         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
9799            && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
9800         board[toY][toX] = newPiece;
9801     }
9802     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9803                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9804         // [HGM] superchess: take promotion piece out of holdings
9805         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9806         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9807             if(!--board[k][BOARD_WIDTH-2])
9808                 board[k][BOARD_WIDTH-1] = EmptySquare;
9809         } else {
9810             if(!--board[BOARD_HEIGHT-1-k][1])
9811                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9812         }
9813     }
9814
9815 }
9816
9817 /* Updates forwardMostMove */
9818 void
9819 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
9820 {
9821 //    forwardMostMove++; // [HGM] bare: moved downstream
9822
9823     (void) CoordsToAlgebraic(boards[forwardMostMove],
9824                              PosFlags(forwardMostMove),
9825                              fromY, fromX, toY, toX, promoChar,
9826                              parseList[forwardMostMove]);
9827
9828     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9829         int timeLeft; static int lastLoadFlag=0; int king, piece;
9830         piece = boards[forwardMostMove][fromY][fromX];
9831         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9832         if(gameInfo.variant == VariantKnightmate)
9833             king += (int) WhiteUnicorn - (int) WhiteKing;
9834         if(forwardMostMove == 0) {
9835             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9836                 fprintf(serverMoves, "%s;", UserName());
9837             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9838                 fprintf(serverMoves, "%s;", second.tidy);
9839             fprintf(serverMoves, "%s;", first.tidy);
9840             if(gameMode == MachinePlaysWhite)
9841                 fprintf(serverMoves, "%s;", UserName());
9842             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9843                 fprintf(serverMoves, "%s;", second.tidy);
9844         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9845         lastLoadFlag = loadFlag;
9846         // print base move
9847         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9848         // print castling suffix
9849         if( toY == fromY && piece == king ) {
9850             if(toX-fromX > 1)
9851                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9852             if(fromX-toX >1)
9853                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9854         }
9855         // e.p. suffix
9856         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9857              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9858              boards[forwardMostMove][toY][toX] == EmptySquare
9859              && fromX != toX && fromY != toY)
9860                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9861         // promotion suffix
9862         if(promoChar != NULLCHAR) {
9863             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
9864                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
9865                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
9866             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
9867         }
9868         if(!loadFlag) {
9869                 char buf[MOVE_LEN*2], *p; int len;
9870             fprintf(serverMoves, "/%d/%d",
9871                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9872             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9873             else                      timeLeft = blackTimeRemaining/1000;
9874             fprintf(serverMoves, "/%d", timeLeft);
9875                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
9876                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
9877                 if(p = strchr(buf, '=')) *p = NULLCHAR;
9878                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
9879             fprintf(serverMoves, "/%s", buf);
9880         }
9881         fflush(serverMoves);
9882     }
9883
9884     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
9885         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
9886       return;
9887     }
9888     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9889     if (commentList[forwardMostMove+1] != NULL) {
9890         free(commentList[forwardMostMove+1]);
9891         commentList[forwardMostMove+1] = NULL;
9892     }
9893     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9894     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9895     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9896     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9897     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9898     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9899     adjustedClock = FALSE;
9900     gameInfo.result = GameUnfinished;
9901     if (gameInfo.resultDetails != NULL) {
9902         free(gameInfo.resultDetails);
9903         gameInfo.resultDetails = NULL;
9904     }
9905     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9906                               moveList[forwardMostMove - 1]);
9907     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9908       case MT_NONE:
9909       case MT_STALEMATE:
9910       default:
9911         break;
9912       case MT_CHECK:
9913         if(gameInfo.variant != VariantShogi)
9914             strcat(parseList[forwardMostMove - 1], "+");
9915         break;
9916       case MT_CHECKMATE:
9917       case MT_STAINMATE:
9918         strcat(parseList[forwardMostMove - 1], "#");
9919         break;
9920     }
9921
9922 }
9923
9924 /* Updates currentMove if not pausing */
9925 void
9926 ShowMove (int fromX, int fromY, int toX, int toY)
9927 {
9928     int instant = (gameMode == PlayFromGameFile) ?
9929         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9930     if(appData.noGUI) return;
9931     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9932         if (!instant) {
9933             if (forwardMostMove == currentMove + 1) {
9934                 AnimateMove(boards[forwardMostMove - 1],
9935                             fromX, fromY, toX, toY);
9936             }
9937         }
9938         currentMove = forwardMostMove;
9939     }
9940
9941     if (instant) return;
9942
9943     DisplayMove(currentMove - 1);
9944     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9945             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
9946                 SetHighlights(fromX, fromY, toX, toY);
9947             }
9948     }
9949     DrawPosition(FALSE, boards[currentMove]);
9950     DisplayBothClocks();
9951     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9952 }
9953
9954 void
9955 SendEgtPath (ChessProgramState *cps)
9956 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9957         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9958
9959         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9960
9961         while(*p) {
9962             char c, *q = name+1, *r, *s;
9963
9964             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9965             while(*p && *p != ',') *q++ = *p++;
9966             *q++ = ':'; *q = 0;
9967             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9968                 strcmp(name, ",nalimov:") == 0 ) {
9969                 // take nalimov path from the menu-changeable option first, if it is defined
9970               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9971                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9972             } else
9973             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9974                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9975                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9976                 s = r = StrStr(s, ":") + 1; // beginning of path info
9977                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9978                 c = *r; *r = 0;             // temporarily null-terminate path info
9979                     *--q = 0;               // strip of trailig ':' from name
9980                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9981                 *r = c;
9982                 SendToProgram(buf,cps);     // send egtbpath command for this format
9983             }
9984             if(*p == ',') p++; // read away comma to position for next format name
9985         }
9986 }
9987
9988 static int
9989 NonStandardBoardSize ()
9990 {
9991       /* [HGM] Awkward testing. Should really be a table */
9992       int overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9993       if( gameInfo.variant == VariantXiangqi )
9994            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9995       if( gameInfo.variant == VariantShogi )
9996            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9997       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9998            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9999       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
10000           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
10001            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
10002       if( gameInfo.variant == VariantCourier )
10003            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
10004       if( gameInfo.variant == VariantSuper )
10005            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
10006       if( gameInfo.variant == VariantGreat )
10007            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
10008       if( gameInfo.variant == VariantSChess )
10009            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
10010       if( gameInfo.variant == VariantGrand )
10011            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
10012       return overruled;
10013 }
10014
10015 void
10016 InitChessProgram (ChessProgramState *cps, int setup)
10017 /* setup needed to setup FRC opening position */
10018 {
10019     char buf[MSG_SIZ], b[MSG_SIZ];
10020     if (appData.noChessProgram) return;
10021     hintRequested = FALSE;
10022     bookRequested = FALSE;
10023
10024     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10025     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10026     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10027     if(cps->memSize) { /* [HGM] memory */
10028       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10029         SendToProgram(buf, cps);
10030     }
10031     SendEgtPath(cps); /* [HGM] EGT */
10032     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10033       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10034         SendToProgram(buf, cps);
10035     }
10036
10037     SendToProgram(cps->initString, cps);
10038     if (gameInfo.variant != VariantNormal &&
10039         gameInfo.variant != VariantLoadable
10040         /* [HGM] also send variant if board size non-standard */
10041         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
10042                                             ) {
10043       char *v = VariantName(gameInfo.variant);
10044       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
10045         /* [HGM] in protocol 1 we have to assume all variants valid */
10046         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
10047         DisplayFatalError(buf, 0, 1);
10048         return;
10049       }
10050
10051       if(NonStandardBoardSize()) { /* [HGM] make prefix for non-standard board size. */
10052         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
10053                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
10054            /* [HGM] varsize: try first if this defiant size variant is specifically known */
10055            if(StrStr(cps->variants, b) == NULL) {
10056                // specific sized variant not known, check if general sizing allowed
10057                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
10058                    if(StrStr(cps->variants, "boardsize") == NULL) {
10059                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10060                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
10061                        DisplayFatalError(buf, 0, 1);
10062                        return;
10063                    }
10064                    /* [HGM] here we really should compare with the maximum supported board size */
10065                }
10066            }
10067       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
10068       snprintf(buf, MSG_SIZ, "variant %s\n", b);
10069       SendToProgram(buf, cps);
10070     }
10071     currentlyInitializedVariant = gameInfo.variant;
10072
10073     /* [HGM] send opening position in FRC to first engine */
10074     if(setup) {
10075           SendToProgram("force\n", cps);
10076           SendBoard(cps, 0);
10077           /* engine is now in force mode! Set flag to wake it up after first move. */
10078           setboardSpoiledMachineBlack = 1;
10079     }
10080
10081     if (cps->sendICS) {
10082       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10083       SendToProgram(buf, cps);
10084     }
10085     cps->maybeThinking = FALSE;
10086     cps->offeredDraw = 0;
10087     if (!appData.icsActive) {
10088         SendTimeControl(cps, movesPerSession, timeControl,
10089                         timeIncrement, appData.searchDepth,
10090                         searchTime);
10091     }
10092     if (appData.showThinking
10093         // [HGM] thinking: four options require thinking output to be sent
10094         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10095                                 ) {
10096         SendToProgram("post\n", cps);
10097     }
10098     SendToProgram("hard\n", cps);
10099     if (!appData.ponderNextMove) {
10100         /* Warning: "easy" is a toggle in GNU Chess, so don't send
10101            it without being sure what state we are in first.  "hard"
10102            is not a toggle, so that one is OK.
10103          */
10104         SendToProgram("easy\n", cps);
10105     }
10106     if (cps->usePing) {
10107       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
10108       SendToProgram(buf, cps);
10109     }
10110     cps->initDone = TRUE;
10111     ClearEngineOutputPane(cps == &second);
10112 }
10113
10114
10115 void
10116 ResendOptions (ChessProgramState *cps)
10117 { // send the stored value of the options
10118   int i;
10119   char buf[MSG_SIZ];
10120   Option *opt = cps->option;
10121   for(i=0; i<cps->nrOptions; i++, opt++) {
10122       switch(opt->type) {
10123         case Spin:
10124         case Slider:
10125         case CheckBox:
10126             snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10127           break;
10128         case ComboBox:
10129           snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10130           break;
10131         default:
10132             snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10133           break;
10134         case Button:
10135         case SaveButton:
10136           continue;
10137       }
10138       SendToProgram(buf, cps);
10139   }
10140 }
10141
10142 void
10143 StartChessProgram (ChessProgramState *cps)
10144 {
10145     char buf[MSG_SIZ];
10146     int err;
10147
10148     if (appData.noChessProgram) return;
10149     cps->initDone = FALSE;
10150
10151     if (strcmp(cps->host, "localhost") == 0) {
10152         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10153     } else if (*appData.remoteShell == NULLCHAR) {
10154         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10155     } else {
10156         if (*appData.remoteUser == NULLCHAR) {
10157           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10158                     cps->program);
10159         } else {
10160           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10161                     cps->host, appData.remoteUser, cps->program);
10162         }
10163         err = StartChildProcess(buf, "", &cps->pr);
10164     }
10165
10166     if (err != 0) {
10167       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10168         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10169         if(cps != &first) return;
10170         appData.noChessProgram = TRUE;
10171         ThawUI();
10172         SetNCPMode();
10173 //      DisplayFatalError(buf, err, 1);
10174 //      cps->pr = NoProc;
10175 //      cps->isr = NULL;
10176         return;
10177     }
10178
10179     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10180     if (cps->protocolVersion > 1) {
10181       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10182       if(!cps->reload) { // do not clear options when reloading because of -xreuse
10183         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10184         cps->comboCnt = 0;  //                and values of combo boxes
10185       }
10186       SendToProgram(buf, cps);
10187       if(cps->reload) ResendOptions(cps);
10188     } else {
10189       SendToProgram("xboard\n", cps);
10190     }
10191 }
10192
10193 void
10194 TwoMachinesEventIfReady P((void))
10195 {
10196   static int curMess = 0;
10197   if (first.lastPing != first.lastPong) {
10198     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10199     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10200     return;
10201   }
10202   if (second.lastPing != second.lastPong) {
10203     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10204     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10205     return;
10206   }
10207   DisplayMessage("", ""); curMess = 0;
10208   TwoMachinesEvent();
10209 }
10210
10211 char *
10212 MakeName (char *template)
10213 {
10214     time_t clock;
10215     struct tm *tm;
10216     static char buf[MSG_SIZ];
10217     char *p = buf;
10218     int i;
10219
10220     clock = time((time_t *)NULL);
10221     tm = localtime(&clock);
10222
10223     while(*p++ = *template++) if(p[-1] == '%') {
10224         switch(*template++) {
10225           case 0:   *p = 0; return buf;
10226           case 'Y': i = tm->tm_year+1900; break;
10227           case 'y': i = tm->tm_year-100; break;
10228           case 'M': i = tm->tm_mon+1; break;
10229           case 'd': i = tm->tm_mday; break;
10230           case 'h': i = tm->tm_hour; break;
10231           case 'm': i = tm->tm_min; break;
10232           case 's': i = tm->tm_sec; break;
10233           default:  i = 0;
10234         }
10235         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10236     }
10237     return buf;
10238 }
10239
10240 int
10241 CountPlayers (char *p)
10242 {
10243     int n = 0;
10244     while(p = strchr(p, '\n')) p++, n++; // count participants
10245     return n;
10246 }
10247
10248 FILE *
10249 WriteTourneyFile (char *results, FILE *f)
10250 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10251     if(f == NULL) f = fopen(appData.tourneyFile, "w");
10252     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10253         // create a file with tournament description
10254         fprintf(f, "-participants {%s}\n", appData.participants);
10255         fprintf(f, "-seedBase %d\n", appData.seedBase);
10256         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10257         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10258         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10259         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10260         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10261         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10262         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10263         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10264         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10265         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10266         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10267         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10268         fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
10269         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10270         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10271         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10272         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10273         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10274         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10275         fprintf(f, "-smpCores %d\n", appData.smpCores);
10276         if(searchTime > 0)
10277                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10278         else {
10279                 fprintf(f, "-mps %d\n", appData.movesPerSession);
10280                 fprintf(f, "-tc %s\n", appData.timeControl);
10281                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10282         }
10283         fprintf(f, "-results \"%s\"\n", results);
10284     }
10285     return f;
10286 }
10287
10288 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10289
10290 void
10291 Substitute (char *participants, int expunge)
10292 {
10293     int i, changed, changes=0, nPlayers=0;
10294     char *p, *q, *r, buf[MSG_SIZ];
10295     if(participants == NULL) return;
10296     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10297     r = p = participants; q = appData.participants;
10298     while(*p && *p == *q) {
10299         if(*p == '\n') r = p+1, nPlayers++;
10300         p++; q++;
10301     }
10302     if(*p) { // difference
10303         while(*p && *p++ != '\n');
10304         while(*q && *q++ != '\n');
10305       changed = nPlayers;
10306         changes = 1 + (strcmp(p, q) != 0);
10307     }
10308     if(changes == 1) { // a single engine mnemonic was changed
10309         q = r; while(*q) nPlayers += (*q++ == '\n');
10310         p = buf; while(*r && (*p = *r++) != '\n') p++;
10311         *p = NULLCHAR;
10312         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10313         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10314         if(mnemonic[i]) { // The substitute is valid
10315             FILE *f;
10316             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10317                 flock(fileno(f), LOCK_EX);
10318                 ParseArgsFromFile(f);
10319                 fseek(f, 0, SEEK_SET);
10320                 FREE(appData.participants); appData.participants = participants;
10321                 if(expunge) { // erase results of replaced engine
10322                     int len = strlen(appData.results), w, b, dummy;
10323                     for(i=0; i<len; i++) {
10324                         Pairing(i, nPlayers, &w, &b, &dummy);
10325                         if((w == changed || b == changed) && appData.results[i] == '*') {
10326                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10327                             fclose(f);
10328                             return;
10329                         }
10330                     }
10331                     for(i=0; i<len; i++) {
10332                         Pairing(i, nPlayers, &w, &b, &dummy);
10333                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10334                     }
10335                 }
10336                 WriteTourneyFile(appData.results, f);
10337                 fclose(f); // release lock
10338                 return;
10339             }
10340         } else DisplayError(_("No engine with the name you gave is installed"), 0);
10341     }
10342     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10343     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
10344     free(participants);
10345     return;
10346 }
10347
10348 int
10349 CheckPlayers (char *participants)
10350 {
10351         int i;
10352         char buf[MSG_SIZ], *p;
10353         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10354         while(p = strchr(participants, '\n')) {
10355             *p = NULLCHAR;
10356             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10357             if(!mnemonic[i]) {
10358                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10359                 *p = '\n';
10360                 DisplayError(buf, 0);
10361                 return 1;
10362             }
10363             *p = '\n';
10364             participants = p + 1;
10365         }
10366         return 0;
10367 }
10368
10369 int
10370 CreateTourney (char *name)
10371 {
10372         FILE *f;
10373         if(matchMode && strcmp(name, appData.tourneyFile)) {
10374              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10375         }
10376         if(name[0] == NULLCHAR) {
10377             if(appData.participants[0])
10378                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10379             return 0;
10380         }
10381         f = fopen(name, "r");
10382         if(f) { // file exists
10383             ASSIGN(appData.tourneyFile, name);
10384             ParseArgsFromFile(f); // parse it
10385         } else {
10386             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10387             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10388                 DisplayError(_("Not enough participants"), 0);
10389                 return 0;
10390             }
10391             if(CheckPlayers(appData.participants)) return 0;
10392             ASSIGN(appData.tourneyFile, name);
10393             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10394             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10395         }
10396         fclose(f);
10397         appData.noChessProgram = FALSE;
10398         appData.clockMode = TRUE;
10399         SetGNUMode();
10400         return 1;
10401 }
10402
10403 int
10404 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10405 {
10406     char buf[MSG_SIZ], *p, *q;
10407     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10408     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10409     skip = !all && group[0]; // if group requested, we start in skip mode
10410     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10411         p = names; q = buf; header = 0;
10412         while(*p && *p != '\n') *q++ = *p++;
10413         *q = 0;
10414         if(*p == '\n') p++;
10415         if(buf[0] == '#') {
10416             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10417             depth++; // we must be entering a new group
10418             if(all) continue; // suppress printing group headers when complete list requested
10419             header = 1;
10420             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10421         }
10422         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10423         if(engineList[i]) free(engineList[i]);
10424         engineList[i] = strdup(buf);
10425         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10426         if(engineMnemonic[i]) free(engineMnemonic[i]);
10427         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10428             strcat(buf, " (");
10429             sscanf(q + 8, "%s", buf + strlen(buf));
10430             strcat(buf, ")");
10431         }
10432         engineMnemonic[i] = strdup(buf);
10433         i++;
10434     }
10435     engineList[i] = engineMnemonic[i] = NULL;
10436     return i;
10437 }
10438
10439 // following implemented as macro to avoid type limitations
10440 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10441
10442 void
10443 SwapEngines (int n)
10444 {   // swap settings for first engine and other engine (so far only some selected options)
10445     int h;
10446     char *p;
10447     if(n == 0) return;
10448     SWAP(directory, p)
10449     SWAP(chessProgram, p)
10450     SWAP(isUCI, h)
10451     SWAP(hasOwnBookUCI, h)
10452     SWAP(protocolVersion, h)
10453     SWAP(reuse, h)
10454     SWAP(scoreIsAbsolute, h)
10455     SWAP(timeOdds, h)
10456     SWAP(logo, p)
10457     SWAP(pgnName, p)
10458     SWAP(pvSAN, h)
10459     SWAP(engOptions, p)
10460     SWAP(engInitString, p)
10461     SWAP(computerString, p)
10462     SWAP(features, p)
10463     SWAP(fenOverride, p)
10464     SWAP(NPS, h)
10465     SWAP(accumulateTC, h)
10466     SWAP(host, p)
10467 }
10468
10469 int
10470 GetEngineLine (char *s, int n)
10471 {
10472     int i;
10473     char buf[MSG_SIZ];
10474     extern char *icsNames;
10475     if(!s || !*s) return 0;
10476     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
10477     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
10478     if(!mnemonic[i]) return 0;
10479     if(n == 11) return 1; // just testing if there was a match
10480     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
10481     if(n == 1) SwapEngines(n);
10482     ParseArgsFromString(buf);
10483     if(n == 1) SwapEngines(n);
10484     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
10485         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
10486         ParseArgsFromString(buf);
10487     }
10488     return 1;
10489 }
10490
10491 int
10492 SetPlayer (int player, char *p)
10493 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
10494     int i;
10495     char buf[MSG_SIZ], *engineName;
10496     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10497     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10498     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10499     if(mnemonic[i]) {
10500         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10501         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10502         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10503         ParseArgsFromString(buf);
10504     } else { // no engine with this nickname is installed!
10505         snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
10506         ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
10507         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10508         ModeHighlight();
10509         DisplayError(buf, 0);
10510         return 0;
10511     }
10512     free(engineName);
10513     return i;
10514 }
10515
10516 char *recentEngines;
10517
10518 void
10519 RecentEngineEvent (int nr)
10520 {
10521     int n;
10522 //    SwapEngines(1); // bump first to second
10523 //    ReplaceEngine(&second, 1); // and load it there
10524     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10525     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10526     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10527         ReplaceEngine(&first, 0);
10528         FloatToFront(&appData.recentEngineList, command[n]);
10529     }
10530 }
10531
10532 int
10533 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10534 {   // determine players from game number
10535     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10536
10537     if(appData.tourneyType == 0) {
10538         roundsPerCycle = (nPlayers - 1) | 1;
10539         pairingsPerRound = nPlayers / 2;
10540     } else if(appData.tourneyType > 0) {
10541         roundsPerCycle = nPlayers - appData.tourneyType;
10542         pairingsPerRound = appData.tourneyType;
10543     }
10544     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10545     gamesPerCycle = gamesPerRound * roundsPerCycle;
10546     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10547     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10548     curRound = nr / gamesPerRound; nr %= gamesPerRound;
10549     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10550     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10551     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10552
10553     if(appData.cycleSync) *syncInterval = gamesPerCycle;
10554     if(appData.roundSync) *syncInterval = gamesPerRound;
10555
10556     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10557
10558     if(appData.tourneyType == 0) {
10559         if(curPairing == (nPlayers-1)/2 ) {
10560             *whitePlayer = curRound;
10561             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10562         } else {
10563             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10564             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10565             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10566             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10567         }
10568     } else if(appData.tourneyType > 1) {
10569         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10570         *whitePlayer = curRound + appData.tourneyType;
10571     } else if(appData.tourneyType > 0) {
10572         *whitePlayer = curPairing;
10573         *blackPlayer = curRound + appData.tourneyType;
10574     }
10575
10576     // take care of white/black alternation per round.
10577     // For cycles and games this is already taken care of by default, derived from matchGame!
10578     return curRound & 1;
10579 }
10580
10581 int
10582 NextTourneyGame (int nr, int *swapColors)
10583 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10584     char *p, *q;
10585     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
10586     FILE *tf;
10587     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10588     tf = fopen(appData.tourneyFile, "r");
10589     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10590     ParseArgsFromFile(tf); fclose(tf);
10591     InitTimeControls(); // TC might be altered from tourney file
10592
10593     nPlayers = CountPlayers(appData.participants); // count participants
10594     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10595     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10596
10597     if(syncInterval) {
10598         p = q = appData.results;
10599         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10600         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10601             DisplayMessage(_("Waiting for other game(s)"),"");
10602             waitingForGame = TRUE;
10603             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10604             return 0;
10605         }
10606         waitingForGame = FALSE;
10607     }
10608
10609     if(appData.tourneyType < 0) {
10610         if(nr>=0 && !pairingReceived) {
10611             char buf[1<<16];
10612             if(pairing.pr == NoProc) {
10613                 if(!appData.pairingEngine[0]) {
10614                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10615                     return 0;
10616                 }
10617                 StartChessProgram(&pairing); // starts the pairing engine
10618             }
10619             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10620             SendToProgram(buf, &pairing);
10621             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10622             SendToProgram(buf, &pairing);
10623             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10624         }
10625         pairingReceived = 0;                              // ... so we continue here
10626         *swapColors = 0;
10627         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10628         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10629         matchGame = 1; roundNr = nr / syncInterval + 1;
10630     }
10631
10632     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10633
10634     // redefine engines, engine dir, etc.
10635     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10636     if(first.pr == NoProc) {
10637       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
10638       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10639     }
10640     if(second.pr == NoProc) {
10641       SwapEngines(1);
10642       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
10643       SwapEngines(1);         // and make that valid for second engine by swapping
10644       InitEngine(&second, 1);
10645     }
10646     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10647     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10648     return OK;
10649 }
10650
10651 void
10652 NextMatchGame ()
10653 {   // performs game initialization that does not invoke engines, and then tries to start the game
10654     int res, firstWhite, swapColors = 0;
10655     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10656     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
10657         char buf[MSG_SIZ];
10658         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
10659         if(strcmp(buf, currentDebugFile)) { // name has changed
10660             FILE *f = fopen(buf, "w");
10661             if(f) { // if opening the new file failed, just keep using the old one
10662                 ASSIGN(currentDebugFile, buf);
10663                 fclose(debugFP);
10664                 debugFP = f;
10665             }
10666             if(appData.serverFileName) {
10667                 if(serverFP) fclose(serverFP);
10668                 serverFP = fopen(appData.serverFileName, "w");
10669                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
10670                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
10671             }
10672         }
10673     }
10674     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10675     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10676     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
10677     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10678     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10679     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10680     Reset(FALSE, first.pr != NoProc);
10681     res = LoadGameOrPosition(matchGame); // setup game
10682     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10683     if(!res) return; // abort when bad game/pos file
10684     TwoMachinesEvent();
10685 }
10686
10687 void
10688 UserAdjudicationEvent (int result)
10689 {
10690     ChessMove gameResult = GameIsDrawn;
10691
10692     if( result > 0 ) {
10693         gameResult = WhiteWins;
10694     }
10695     else if( result < 0 ) {
10696         gameResult = BlackWins;
10697     }
10698
10699     if( gameMode == TwoMachinesPlay ) {
10700         GameEnds( gameResult, "User adjudication", GE_XBOARD );
10701     }
10702 }
10703
10704
10705 // [HGM] save: calculate checksum of game to make games easily identifiable
10706 int
10707 StringCheckSum (char *s)
10708 {
10709         int i = 0;
10710         if(s==NULL) return 0;
10711         while(*s) i = i*259 + *s++;
10712         return i;
10713 }
10714
10715 int
10716 GameCheckSum ()
10717 {
10718         int i, sum=0;
10719         for(i=backwardMostMove; i<forwardMostMove; i++) {
10720                 sum += pvInfoList[i].depth;
10721                 sum += StringCheckSum(parseList[i]);
10722                 sum += StringCheckSum(commentList[i]);
10723                 sum *= 261;
10724         }
10725         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10726         return sum + StringCheckSum(commentList[i]);
10727 } // end of save patch
10728
10729 void
10730 GameEnds (ChessMove result, char *resultDetails, int whosays)
10731 {
10732     GameMode nextGameMode;
10733     int isIcsGame;
10734     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10735
10736     if(endingGame) return; /* [HGM] crash: forbid recursion */
10737     endingGame = 1;
10738     if(twoBoards) { // [HGM] dual: switch back to one board
10739         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10740         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10741     }
10742     if (appData.debugMode) {
10743       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10744               result, resultDetails ? resultDetails : "(null)", whosays);
10745     }
10746
10747     fromX = fromY = -1; // [HGM] abort any move the user is entering.
10748
10749     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
10750
10751     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10752         /* If we are playing on ICS, the server decides when the
10753            game is over, but the engine can offer to draw, claim
10754            a draw, or resign.
10755          */
10756 #if ZIPPY
10757         if (appData.zippyPlay && first.initDone) {
10758             if (result == GameIsDrawn) {
10759                 /* In case draw still needs to be claimed */
10760                 SendToICS(ics_prefix);
10761                 SendToICS("draw\n");
10762             } else if (StrCaseStr(resultDetails, "resign")) {
10763                 SendToICS(ics_prefix);
10764                 SendToICS("resign\n");
10765             }
10766         }
10767 #endif
10768         endingGame = 0; /* [HGM] crash */
10769         return;
10770     }
10771
10772     /* If we're loading the game from a file, stop */
10773     if (whosays == GE_FILE) {
10774       (void) StopLoadGameTimer();
10775       gameFileFP = NULL;
10776     }
10777
10778     /* Cancel draw offers */
10779     first.offeredDraw = second.offeredDraw = 0;
10780
10781     /* If this is an ICS game, only ICS can really say it's done;
10782        if not, anyone can. */
10783     isIcsGame = (gameMode == IcsPlayingWhite ||
10784                  gameMode == IcsPlayingBlack ||
10785                  gameMode == IcsObserving    ||
10786                  gameMode == IcsExamining);
10787
10788     if (!isIcsGame || whosays == GE_ICS) {
10789         /* OK -- not an ICS game, or ICS said it was done */
10790         StopClocks();
10791         if (!isIcsGame && !appData.noChessProgram)
10792           SetUserThinkingEnables();
10793
10794         /* [HGM] if a machine claims the game end we verify this claim */
10795         if(gameMode == TwoMachinesPlay && appData.testClaims) {
10796             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10797                 char claimer;
10798                 ChessMove trueResult = (ChessMove) -1;
10799
10800                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
10801                                             first.twoMachinesColor[0] :
10802                                             second.twoMachinesColor[0] ;
10803
10804                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10805                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10806                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10807                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10808                 } else
10809                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10810                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10811                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10812                 } else
10813                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10814                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10815                 }
10816
10817                 // now verify win claims, but not in drop games, as we don't understand those yet
10818                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10819                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10820                     (result == WhiteWins && claimer == 'w' ||
10821                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
10822                       if (appData.debugMode) {
10823                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
10824                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10825                       }
10826                       if(result != trueResult) {
10827                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10828                               result = claimer == 'w' ? BlackWins : WhiteWins;
10829                               resultDetails = buf;
10830                       }
10831                 } else
10832                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10833                     && (forwardMostMove <= backwardMostMove ||
10834                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10835                         (claimer=='b')==(forwardMostMove&1))
10836                                                                                   ) {
10837                       /* [HGM] verify: draws that were not flagged are false claims */
10838                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10839                       result = claimer == 'w' ? BlackWins : WhiteWins;
10840                       resultDetails = buf;
10841                 }
10842                 /* (Claiming a loss is accepted no questions asked!) */
10843             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
10844                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
10845                 result = GameUnfinished;
10846                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
10847             }
10848             /* [HGM] bare: don't allow bare King to win */
10849             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10850                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10851                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10852                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10853                && result != GameIsDrawn)
10854             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10855                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10856                         int p = (signed char)boards[forwardMostMove][i][j] - color;
10857                         if(p >= 0 && p <= (int)WhiteKing) k++;
10858                 }
10859                 if (appData.debugMode) {
10860                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10861                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10862                 }
10863                 if(k <= 1) {
10864                         result = GameIsDrawn;
10865                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10866                         resultDetails = buf;
10867                 }
10868             }
10869         }
10870
10871
10872         if(serverMoves != NULL && !loadFlag) { char c = '=';
10873             if(result==WhiteWins) c = '+';
10874             if(result==BlackWins) c = '-';
10875             if(resultDetails != NULL)
10876                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
10877         }
10878         if (resultDetails != NULL) {
10879             gameInfo.result = result;
10880             gameInfo.resultDetails = StrSave(resultDetails);
10881
10882             /* display last move only if game was not loaded from file */
10883             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10884                 DisplayMove(currentMove - 1);
10885
10886             if (forwardMostMove != 0) {
10887                 if (gameMode != PlayFromGameFile && gameMode != EditGame
10888                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10889                                                                 ) {
10890                     if (*appData.saveGameFile != NULLCHAR) {
10891                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
10892                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
10893                         else
10894                         SaveGameToFile(appData.saveGameFile, TRUE);
10895                     } else if (appData.autoSaveGames) {
10896                         if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
10897                     }
10898                     if (*appData.savePositionFile != NULLCHAR) {
10899                         SavePositionToFile(appData.savePositionFile);
10900                     }
10901                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
10902                 }
10903             }
10904
10905             /* Tell program how game ended in case it is learning */
10906             /* [HGM] Moved this to after saving the PGN, just in case */
10907             /* engine died and we got here through time loss. In that */
10908             /* case we will get a fatal error writing the pipe, which */
10909             /* would otherwise lose us the PGN.                       */
10910             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
10911             /* output during GameEnds should never be fatal anymore   */
10912             if (gameMode == MachinePlaysWhite ||
10913                 gameMode == MachinePlaysBlack ||
10914                 gameMode == TwoMachinesPlay ||
10915                 gameMode == IcsPlayingWhite ||
10916                 gameMode == IcsPlayingBlack ||
10917                 gameMode == BeginningOfGame) {
10918                 char buf[MSG_SIZ];
10919                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10920                         resultDetails);
10921                 if (first.pr != NoProc) {
10922                     SendToProgram(buf, &first);
10923                 }
10924                 if (second.pr != NoProc &&
10925                     gameMode == TwoMachinesPlay) {
10926                     SendToProgram(buf, &second);
10927                 }
10928             }
10929         }
10930
10931         if (appData.icsActive) {
10932             if (appData.quietPlay &&
10933                 (gameMode == IcsPlayingWhite ||
10934                  gameMode == IcsPlayingBlack)) {
10935                 SendToICS(ics_prefix);
10936                 SendToICS("set shout 1\n");
10937             }
10938             nextGameMode = IcsIdle;
10939             ics_user_moved = FALSE;
10940             /* clean up premove.  It's ugly when the game has ended and the
10941              * premove highlights are still on the board.
10942              */
10943             if (gotPremove) {
10944               gotPremove = FALSE;
10945               ClearPremoveHighlights();
10946               DrawPosition(FALSE, boards[currentMove]);
10947             }
10948             if (whosays == GE_ICS) {
10949                 switch (result) {
10950                 case WhiteWins:
10951                     if (gameMode == IcsPlayingWhite)
10952                         PlayIcsWinSound();
10953                     else if(gameMode == IcsPlayingBlack)
10954                         PlayIcsLossSound();
10955                     break;
10956                 case BlackWins:
10957                     if (gameMode == IcsPlayingBlack)
10958                         PlayIcsWinSound();
10959                     else if(gameMode == IcsPlayingWhite)
10960                         PlayIcsLossSound();
10961                     break;
10962                 case GameIsDrawn:
10963                     PlayIcsDrawSound();
10964                     break;
10965                 default:
10966                     PlayIcsUnfinishedSound();
10967                 }
10968             }
10969             if(appData.quitNext) { ExitEvent(0); return; }
10970         } else if (gameMode == EditGame ||
10971                    gameMode == PlayFromGameFile ||
10972                    gameMode == AnalyzeMode ||
10973                    gameMode == AnalyzeFile) {
10974             nextGameMode = gameMode;
10975         } else {
10976             nextGameMode = EndOfGame;
10977         }
10978         pausing = FALSE;
10979         ModeHighlight();
10980     } else {
10981         nextGameMode = gameMode;
10982     }
10983
10984     if (appData.noChessProgram) {
10985         gameMode = nextGameMode;
10986         ModeHighlight();
10987         endingGame = 0; /* [HGM] crash */
10988         return;
10989     }
10990
10991     if (first.reuse) {
10992         /* Put first chess program into idle state */
10993         if (first.pr != NoProc &&
10994             (gameMode == MachinePlaysWhite ||
10995              gameMode == MachinePlaysBlack ||
10996              gameMode == TwoMachinesPlay ||
10997              gameMode == IcsPlayingWhite ||
10998              gameMode == IcsPlayingBlack ||
10999              gameMode == BeginningOfGame)) {
11000             SendToProgram("force\n", &first);
11001             if (first.usePing) {
11002               char buf[MSG_SIZ];
11003               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11004               SendToProgram(buf, &first);
11005             }
11006         }
11007     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11008         /* Kill off first chess program */
11009         if (first.isr != NULL)
11010           RemoveInputSource(first.isr);
11011         first.isr = NULL;
11012
11013         if (first.pr != NoProc) {
11014             ExitAnalyzeMode();
11015             DoSleep( appData.delayBeforeQuit );
11016             SendToProgram("quit\n", &first);
11017             DoSleep( appData.delayAfterQuit );
11018             DestroyChildProcess(first.pr, first.useSigterm);
11019             first.reload = TRUE;
11020         }
11021         first.pr = NoProc;
11022     }
11023     if (second.reuse) {
11024         /* Put second chess program into idle state */
11025         if (second.pr != NoProc &&
11026             gameMode == TwoMachinesPlay) {
11027             SendToProgram("force\n", &second);
11028             if (second.usePing) {
11029               char buf[MSG_SIZ];
11030               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11031               SendToProgram(buf, &second);
11032             }
11033         }
11034     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11035         /* Kill off second chess program */
11036         if (second.isr != NULL)
11037           RemoveInputSource(second.isr);
11038         second.isr = NULL;
11039
11040         if (second.pr != NoProc) {
11041             DoSleep( appData.delayBeforeQuit );
11042             SendToProgram("quit\n", &second);
11043             DoSleep( appData.delayAfterQuit );
11044             DestroyChildProcess(second.pr, second.useSigterm);
11045             second.reload = TRUE;
11046         }
11047         second.pr = NoProc;
11048     }
11049
11050     if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11051         char resChar = '=';
11052         switch (result) {
11053         case WhiteWins:
11054           resChar = '+';
11055           if (first.twoMachinesColor[0] == 'w') {
11056             first.matchWins++;
11057           } else {
11058             second.matchWins++;
11059           }
11060           break;
11061         case BlackWins:
11062           resChar = '-';
11063           if (first.twoMachinesColor[0] == 'b') {
11064             first.matchWins++;
11065           } else {
11066             second.matchWins++;
11067           }
11068           break;
11069         case GameUnfinished:
11070           resChar = ' ';
11071         default:
11072           break;
11073         }
11074
11075         if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11076         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11077             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11078             ReserveGame(nextGame, resChar); // sets nextGame
11079             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11080             else ranking = strdup("busy"); //suppress popup when aborted but not finished
11081         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11082
11083         if (nextGame <= appData.matchGames && !abortMatch) {
11084             gameMode = nextGameMode;
11085             matchGame = nextGame; // this will be overruled in tourney mode!
11086             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11087             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11088             endingGame = 0; /* [HGM] crash */
11089             return;
11090         } else {
11091             gameMode = nextGameMode;
11092             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11093                      first.tidy, second.tidy,
11094                      first.matchWins, second.matchWins,
11095                      appData.matchGames - (first.matchWins + second.matchWins));
11096             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11097             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11098             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11099             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11100                 first.twoMachinesColor = "black\n";
11101                 second.twoMachinesColor = "white\n";
11102             } else {
11103                 first.twoMachinesColor = "white\n";
11104                 second.twoMachinesColor = "black\n";
11105             }
11106         }
11107     }
11108     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11109         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11110       ExitAnalyzeMode();
11111     gameMode = nextGameMode;
11112     ModeHighlight();
11113     endingGame = 0;  /* [HGM] crash */
11114     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11115         if(matchMode == TRUE) { // match through command line: exit with or without popup
11116             if(ranking) {
11117                 ToNrEvent(forwardMostMove);
11118                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11119                 else ExitEvent(0);
11120             } else DisplayFatalError(buf, 0, 0);
11121         } else { // match through menu; just stop, with or without popup
11122             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11123             ModeHighlight();
11124             if(ranking){
11125                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11126             } else DisplayNote(buf);
11127       }
11128       if(ranking) free(ranking);
11129     }
11130 }
11131
11132 /* Assumes program was just initialized (initString sent).
11133    Leaves program in force mode. */
11134 void
11135 FeedMovesToProgram (ChessProgramState *cps, int upto)
11136 {
11137     int i;
11138
11139     if (appData.debugMode)
11140       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11141               startedFromSetupPosition ? "position and " : "",
11142               backwardMostMove, upto, cps->which);
11143     if(currentlyInitializedVariant != gameInfo.variant) {
11144       char buf[MSG_SIZ];
11145         // [HGM] variantswitch: make engine aware of new variant
11146         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
11147                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11148         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11149         SendToProgram(buf, cps);
11150         currentlyInitializedVariant = gameInfo.variant;
11151     }
11152     SendToProgram("force\n", cps);
11153     if (startedFromSetupPosition) {
11154         SendBoard(cps, backwardMostMove);
11155     if (appData.debugMode) {
11156         fprintf(debugFP, "feedMoves\n");
11157     }
11158     }
11159     for (i = backwardMostMove; i < upto; i++) {
11160         SendMoveToProgram(i, cps);
11161     }
11162 }
11163
11164
11165 int
11166 ResurrectChessProgram ()
11167 {
11168      /* The chess program may have exited.
11169         If so, restart it and feed it all the moves made so far. */
11170     static int doInit = 0;
11171
11172     if (appData.noChessProgram) return 1;
11173
11174     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11175         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11176         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11177         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11178     } else {
11179         if (first.pr != NoProc) return 1;
11180         StartChessProgram(&first);
11181     }
11182     InitChessProgram(&first, FALSE);
11183     FeedMovesToProgram(&first, currentMove);
11184
11185     if (!first.sendTime) {
11186         /* can't tell gnuchess what its clock should read,
11187            so we bow to its notion. */
11188         ResetClocks();
11189         timeRemaining[0][currentMove] = whiteTimeRemaining;
11190         timeRemaining[1][currentMove] = blackTimeRemaining;
11191     }
11192
11193     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11194                 appData.icsEngineAnalyze) && first.analysisSupport) {
11195       SendToProgram("analyze\n", &first);
11196       first.analyzing = TRUE;
11197     }
11198     return 1;
11199 }
11200
11201 /*
11202  * Button procedures
11203  */
11204 void
11205 Reset (int redraw, int init)
11206 {
11207     int i;
11208
11209     if (appData.debugMode) {
11210         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11211                 redraw, init, gameMode);
11212     }
11213     CleanupTail(); // [HGM] vari: delete any stored variations
11214     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11215     pausing = pauseExamInvalid = FALSE;
11216     startedFromSetupPosition = blackPlaysFirst = FALSE;
11217     firstMove = TRUE;
11218     whiteFlag = blackFlag = FALSE;
11219     userOfferedDraw = FALSE;
11220     hintRequested = bookRequested = FALSE;
11221     first.maybeThinking = FALSE;
11222     second.maybeThinking = FALSE;
11223     first.bookSuspend = FALSE; // [HGM] book
11224     second.bookSuspend = FALSE;
11225     thinkOutput[0] = NULLCHAR;
11226     lastHint[0] = NULLCHAR;
11227     ClearGameInfo(&gameInfo);
11228     gameInfo.variant = StringToVariant(appData.variant);
11229     ics_user_moved = ics_clock_paused = FALSE;
11230     ics_getting_history = H_FALSE;
11231     ics_gamenum = -1;
11232     white_holding[0] = black_holding[0] = NULLCHAR;
11233     ClearProgramStats();
11234     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11235
11236     ResetFrontEnd();
11237     ClearHighlights();
11238     flipView = appData.flipView;
11239     ClearPremoveHighlights();
11240     gotPremove = FALSE;
11241     alarmSounded = FALSE;
11242
11243     GameEnds(EndOfFile, NULL, GE_PLAYER);
11244     if(appData.serverMovesName != NULL) {
11245         /* [HGM] prepare to make moves file for broadcasting */
11246         clock_t t = clock();
11247         if(serverMoves != NULL) fclose(serverMoves);
11248         serverMoves = fopen(appData.serverMovesName, "r");
11249         if(serverMoves != NULL) {
11250             fclose(serverMoves);
11251             /* delay 15 sec before overwriting, so all clients can see end */
11252             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11253         }
11254         serverMoves = fopen(appData.serverMovesName, "w");
11255     }
11256
11257     ExitAnalyzeMode();
11258     gameMode = BeginningOfGame;
11259     ModeHighlight();
11260     if(appData.icsActive) gameInfo.variant = VariantNormal;
11261     currentMove = forwardMostMove = backwardMostMove = 0;
11262     MarkTargetSquares(1);
11263     InitPosition(redraw);
11264     for (i = 0; i < MAX_MOVES; i++) {
11265         if (commentList[i] != NULL) {
11266             free(commentList[i]);
11267             commentList[i] = NULL;
11268         }
11269     }
11270     ResetClocks();
11271     timeRemaining[0][0] = whiteTimeRemaining;
11272     timeRemaining[1][0] = blackTimeRemaining;
11273
11274     if (first.pr == NoProc) {
11275         StartChessProgram(&first);
11276     }
11277     if (init) {
11278             InitChessProgram(&first, startedFromSetupPosition);
11279     }
11280     DisplayTitle("");
11281     DisplayMessage("", "");
11282     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11283     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11284     ClearMap();        // [HGM] exclude: invalidate map
11285 }
11286
11287 void
11288 AutoPlayGameLoop ()
11289 {
11290     for (;;) {
11291         if (!AutoPlayOneMove())
11292           return;
11293         if (matchMode || appData.timeDelay == 0)
11294           continue;
11295         if (appData.timeDelay < 0)
11296           return;
11297         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11298         break;
11299     }
11300 }
11301
11302 void
11303 AnalyzeNextGame()
11304 {
11305     ReloadGame(1); // next game
11306 }
11307
11308 int
11309 AutoPlayOneMove ()
11310 {
11311     int fromX, fromY, toX, toY;
11312
11313     if (appData.debugMode) {
11314       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11315     }
11316
11317     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11318       return FALSE;
11319
11320     if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
11321       pvInfoList[currentMove].depth = programStats.depth;
11322       pvInfoList[currentMove].score = programStats.score;
11323       pvInfoList[currentMove].time  = 0;
11324       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11325       else { // append analysis of final position as comment
11326         char buf[MSG_SIZ];
11327         snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
11328         AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
11329       }
11330       programStats.depth = 0;
11331     }
11332
11333     if (currentMove >= forwardMostMove) {
11334       if(gameMode == AnalyzeFile) {
11335           if(appData.loadGameIndex == -1) {
11336             GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
11337           ScheduleDelayedEvent(AnalyzeNextGame, 10);
11338           } else {
11339           ExitAnalyzeMode(); SendToProgram("force\n", &first);
11340         }
11341       }
11342 //      gameMode = EndOfGame;
11343 //      ModeHighlight();
11344
11345       /* [AS] Clear current move marker at the end of a game */
11346       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11347
11348       return FALSE;
11349     }
11350
11351     toX = moveList[currentMove][2] - AAA;
11352     toY = moveList[currentMove][3] - ONE;
11353
11354     if (moveList[currentMove][1] == '@') {
11355         if (appData.highlightLastMove) {
11356             SetHighlights(-1, -1, toX, toY);
11357         }
11358     } else {
11359         fromX = moveList[currentMove][0] - AAA;
11360         fromY = moveList[currentMove][1] - ONE;
11361
11362         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11363
11364         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11365
11366         if (appData.highlightLastMove) {
11367             SetHighlights(fromX, fromY, toX, toY);
11368         }
11369     }
11370     DisplayMove(currentMove);
11371     SendMoveToProgram(currentMove++, &first);
11372     DisplayBothClocks();
11373     DrawPosition(FALSE, boards[currentMove]);
11374     // [HGM] PV info: always display, routine tests if empty
11375     DisplayComment(currentMove - 1, commentList[currentMove]);
11376     return TRUE;
11377 }
11378
11379
11380 int
11381 LoadGameOneMove (ChessMove readAhead)
11382 {
11383     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11384     char promoChar = NULLCHAR;
11385     ChessMove moveType;
11386     char move[MSG_SIZ];
11387     char *p, *q;
11388
11389     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11390         gameMode != AnalyzeMode && gameMode != Training) {
11391         gameFileFP = NULL;
11392         return FALSE;
11393     }
11394
11395     yyboardindex = forwardMostMove;
11396     if (readAhead != EndOfFile) {
11397       moveType = readAhead;
11398     } else {
11399       if (gameFileFP == NULL)
11400           return FALSE;
11401       moveType = (ChessMove) Myylex();
11402     }
11403
11404     done = FALSE;
11405     switch (moveType) {
11406       case Comment:
11407         if (appData.debugMode)
11408           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11409         p = yy_text;
11410
11411         /* append the comment but don't display it */
11412         AppendComment(currentMove, p, FALSE);
11413         return TRUE;
11414
11415       case WhiteCapturesEnPassant:
11416       case BlackCapturesEnPassant:
11417       case WhitePromotion:
11418       case BlackPromotion:
11419       case WhiteNonPromotion:
11420       case BlackNonPromotion:
11421       case NormalMove:
11422       case WhiteKingSideCastle:
11423       case WhiteQueenSideCastle:
11424       case BlackKingSideCastle:
11425       case BlackQueenSideCastle:
11426       case WhiteKingSideCastleWild:
11427       case WhiteQueenSideCastleWild:
11428       case BlackKingSideCastleWild:
11429       case BlackQueenSideCastleWild:
11430       /* PUSH Fabien */
11431       case WhiteHSideCastleFR:
11432       case WhiteASideCastleFR:
11433       case BlackHSideCastleFR:
11434       case BlackASideCastleFR:
11435       /* POP Fabien */
11436         if (appData.debugMode)
11437           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11438         fromX = currentMoveString[0] - AAA;
11439         fromY = currentMoveString[1] - ONE;
11440         toX = currentMoveString[2] - AAA;
11441         toY = currentMoveString[3] - ONE;
11442         promoChar = currentMoveString[4];
11443         break;
11444
11445       case WhiteDrop:
11446       case BlackDrop:
11447         if (appData.debugMode)
11448           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11449         fromX = moveType == WhiteDrop ?
11450           (int) CharToPiece(ToUpper(currentMoveString[0])) :
11451         (int) CharToPiece(ToLower(currentMoveString[0]));
11452         fromY = DROP_RANK;
11453         toX = currentMoveString[2] - AAA;
11454         toY = currentMoveString[3] - ONE;
11455         break;
11456
11457       case WhiteWins:
11458       case BlackWins:
11459       case GameIsDrawn:
11460       case GameUnfinished:
11461         if (appData.debugMode)
11462           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11463         p = strchr(yy_text, '{');
11464         if (p == NULL) p = strchr(yy_text, '(');
11465         if (p == NULL) {
11466             p = yy_text;
11467             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11468         } else {
11469             q = strchr(p, *p == '{' ? '}' : ')');
11470             if (q != NULL) *q = NULLCHAR;
11471             p++;
11472         }
11473         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11474         GameEnds(moveType, p, GE_FILE);
11475         done = TRUE;
11476         if (cmailMsgLoaded) {
11477             ClearHighlights();
11478             flipView = WhiteOnMove(currentMove);
11479             if (moveType == GameUnfinished) flipView = !flipView;
11480             if (appData.debugMode)
11481               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11482         }
11483         break;
11484
11485       case EndOfFile:
11486         if (appData.debugMode)
11487           fprintf(debugFP, "Parser hit end of file\n");
11488         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11489           case MT_NONE:
11490           case MT_CHECK:
11491             break;
11492           case MT_CHECKMATE:
11493           case MT_STAINMATE:
11494             if (WhiteOnMove(currentMove)) {
11495                 GameEnds(BlackWins, "Black mates", GE_FILE);
11496             } else {
11497                 GameEnds(WhiteWins, "White mates", GE_FILE);
11498             }
11499             break;
11500           case MT_STALEMATE:
11501             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11502             break;
11503         }
11504         done = TRUE;
11505         break;
11506
11507       case MoveNumberOne:
11508         if (lastLoadGameStart == GNUChessGame) {
11509             /* GNUChessGames have numbers, but they aren't move numbers */
11510             if (appData.debugMode)
11511               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11512                       yy_text, (int) moveType);
11513             return LoadGameOneMove(EndOfFile); /* tail recursion */
11514         }
11515         /* else fall thru */
11516
11517       case XBoardGame:
11518       case GNUChessGame:
11519       case PGNTag:
11520         /* Reached start of next game in file */
11521         if (appData.debugMode)
11522           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11523         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11524           case MT_NONE:
11525           case MT_CHECK:
11526             break;
11527           case MT_CHECKMATE:
11528           case MT_STAINMATE:
11529             if (WhiteOnMove(currentMove)) {
11530                 GameEnds(BlackWins, "Black mates", GE_FILE);
11531             } else {
11532                 GameEnds(WhiteWins, "White mates", GE_FILE);
11533             }
11534             break;
11535           case MT_STALEMATE:
11536             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11537             break;
11538         }
11539         done = TRUE;
11540         break;
11541
11542       case PositionDiagram:     /* should not happen; ignore */
11543       case ElapsedTime:         /* ignore */
11544       case NAG:                 /* ignore */
11545         if (appData.debugMode)
11546           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11547                   yy_text, (int) moveType);
11548         return LoadGameOneMove(EndOfFile); /* tail recursion */
11549
11550       case IllegalMove:
11551         if (appData.testLegality) {
11552             if (appData.debugMode)
11553               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11554             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11555                     (forwardMostMove / 2) + 1,
11556                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11557             DisplayError(move, 0);
11558             done = TRUE;
11559         } else {
11560             if (appData.debugMode)
11561               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11562                       yy_text, currentMoveString);
11563             fromX = currentMoveString[0] - AAA;
11564             fromY = currentMoveString[1] - ONE;
11565             toX = currentMoveString[2] - AAA;
11566             toY = currentMoveString[3] - ONE;
11567             promoChar = currentMoveString[4];
11568         }
11569         break;
11570
11571       case AmbiguousMove:
11572         if (appData.debugMode)
11573           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11574         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11575                 (forwardMostMove / 2) + 1,
11576                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11577         DisplayError(move, 0);
11578         done = TRUE;
11579         break;
11580
11581       default:
11582       case ImpossibleMove:
11583         if (appData.debugMode)
11584           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11585         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11586                 (forwardMostMove / 2) + 1,
11587                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11588         DisplayError(move, 0);
11589         done = TRUE;
11590         break;
11591     }
11592
11593     if (done) {
11594         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11595             DrawPosition(FALSE, boards[currentMove]);
11596             DisplayBothClocks();
11597             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11598               DisplayComment(currentMove - 1, commentList[currentMove]);
11599         }
11600         (void) StopLoadGameTimer();
11601         gameFileFP = NULL;
11602         cmailOldMove = forwardMostMove;
11603         return FALSE;
11604     } else {
11605         /* currentMoveString is set as a side-effect of yylex */
11606
11607         thinkOutput[0] = NULLCHAR;
11608         MakeMove(fromX, fromY, toX, toY, promoChar);
11609         currentMove = forwardMostMove;
11610         return TRUE;
11611     }
11612 }
11613
11614 /* Load the nth game from the given file */
11615 int
11616 LoadGameFromFile (char *filename, int n, char *title, int useList)
11617 {
11618     FILE *f;
11619     char buf[MSG_SIZ];
11620
11621     if (strcmp(filename, "-") == 0) {
11622         f = stdin;
11623         title = "stdin";
11624     } else {
11625         f = fopen(filename, "rb");
11626         if (f == NULL) {
11627           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
11628             DisplayError(buf, errno);
11629             return FALSE;
11630         }
11631     }
11632     if (fseek(f, 0, 0) == -1) {
11633         /* f is not seekable; probably a pipe */
11634         useList = FALSE;
11635     }
11636     if (useList && n == 0) {
11637         int error = GameListBuild(f);
11638         if (error) {
11639             DisplayError(_("Cannot build game list"), error);
11640         } else if (!ListEmpty(&gameList) &&
11641                    ((ListGame *) gameList.tailPred)->number > 1) {
11642             GameListPopUp(f, title);
11643             return TRUE;
11644         }
11645         GameListDestroy();
11646         n = 1;
11647     }
11648     if (n == 0) n = 1;
11649     return LoadGame(f, n, title, FALSE);
11650 }
11651
11652
11653 void
11654 MakeRegisteredMove ()
11655 {
11656     int fromX, fromY, toX, toY;
11657     char promoChar;
11658     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11659         switch (cmailMoveType[lastLoadGameNumber - 1]) {
11660           case CMAIL_MOVE:
11661           case CMAIL_DRAW:
11662             if (appData.debugMode)
11663               fprintf(debugFP, "Restoring %s for game %d\n",
11664                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11665
11666             thinkOutput[0] = NULLCHAR;
11667             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11668             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11669             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11670             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11671             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11672             promoChar = cmailMove[lastLoadGameNumber - 1][4];
11673             MakeMove(fromX, fromY, toX, toY, promoChar);
11674             ShowMove(fromX, fromY, toX, toY);
11675
11676             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11677               case MT_NONE:
11678               case MT_CHECK:
11679                 break;
11680
11681               case MT_CHECKMATE:
11682               case MT_STAINMATE:
11683                 if (WhiteOnMove(currentMove)) {
11684                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
11685                 } else {
11686                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
11687                 }
11688                 break;
11689
11690               case MT_STALEMATE:
11691                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11692                 break;
11693             }
11694
11695             break;
11696
11697           case CMAIL_RESIGN:
11698             if (WhiteOnMove(currentMove)) {
11699                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11700             } else {
11701                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11702             }
11703             break;
11704
11705           case CMAIL_ACCEPT:
11706             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11707             break;
11708
11709           default:
11710             break;
11711         }
11712     }
11713
11714     return;
11715 }
11716
11717 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11718 int
11719 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11720 {
11721     int retVal;
11722
11723     if (gameNumber > nCmailGames) {
11724         DisplayError(_("No more games in this message"), 0);
11725         return FALSE;
11726     }
11727     if (f == lastLoadGameFP) {
11728         int offset = gameNumber - lastLoadGameNumber;
11729         if (offset == 0) {
11730             cmailMsg[0] = NULLCHAR;
11731             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11732                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11733                 nCmailMovesRegistered--;
11734             }
11735             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11736             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11737                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11738             }
11739         } else {
11740             if (! RegisterMove()) return FALSE;
11741         }
11742     }
11743
11744     retVal = LoadGame(f, gameNumber, title, useList);
11745
11746     /* Make move registered during previous look at this game, if any */
11747     MakeRegisteredMove();
11748
11749     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11750         commentList[currentMove]
11751           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11752         DisplayComment(currentMove - 1, commentList[currentMove]);
11753     }
11754
11755     return retVal;
11756 }
11757
11758 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11759 int
11760 ReloadGame (int offset)
11761 {
11762     int gameNumber = lastLoadGameNumber + offset;
11763     if (lastLoadGameFP == NULL) {
11764         DisplayError(_("No game has been loaded yet"), 0);
11765         return FALSE;
11766     }
11767     if (gameNumber <= 0) {
11768         DisplayError(_("Can't back up any further"), 0);
11769         return FALSE;
11770     }
11771     if (cmailMsgLoaded) {
11772         return CmailLoadGame(lastLoadGameFP, gameNumber,
11773                              lastLoadGameTitle, lastLoadGameUseList);
11774     } else {
11775         return LoadGame(lastLoadGameFP, gameNumber,
11776                         lastLoadGameTitle, lastLoadGameUseList);
11777     }
11778 }
11779
11780 int keys[EmptySquare+1];
11781
11782 int
11783 PositionMatches (Board b1, Board b2)
11784 {
11785     int r, f, sum=0;
11786     switch(appData.searchMode) {
11787         case 1: return CompareWithRights(b1, b2);
11788         case 2:
11789             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11790                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11791             }
11792             return TRUE;
11793         case 3:
11794             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11795               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11796                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11797             }
11798             return sum==0;
11799         case 4:
11800             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11801                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11802             }
11803             return sum==0;
11804     }
11805     return TRUE;
11806 }
11807
11808 #define Q_PROMO  4
11809 #define Q_EP     3
11810 #define Q_BCASTL 2
11811 #define Q_WCASTL 1
11812
11813 int pieceList[256], quickBoard[256];
11814 ChessSquare pieceType[256] = { EmptySquare };
11815 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11816 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11817 int soughtTotal, turn;
11818 Boolean epOK, flipSearch;
11819
11820 typedef struct {
11821     unsigned char piece, to;
11822 } Move;
11823
11824 #define DSIZE (250000)
11825
11826 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11827 Move *moveDatabase = initialSpace;
11828 unsigned int movePtr, dataSize = DSIZE;
11829
11830 int
11831 MakePieceList (Board board, int *counts)
11832 {
11833     int r, f, n=Q_PROMO, total=0;
11834     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11835     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11836         int sq = f + (r<<4);
11837         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
11838             quickBoard[sq] = ++n;
11839             pieceList[n] = sq;
11840             pieceType[n] = board[r][f];
11841             counts[board[r][f]]++;
11842             if(board[r][f] == WhiteKing) pieceList[1] = n; else
11843             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
11844             total++;
11845         }
11846     }
11847     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
11848     return total;
11849 }
11850
11851 void
11852 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
11853 {
11854     int sq = fromX + (fromY<<4);
11855     int piece = quickBoard[sq];
11856     quickBoard[sq] = 0;
11857     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
11858     if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11859         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
11860         moveDatabase[movePtr++].piece = Q_WCASTL;
11861         quickBoard[sq] = piece;
11862         piece = quickBoard[from]; quickBoard[from] = 0;
11863         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11864     } else
11865     if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11866         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
11867         moveDatabase[movePtr++].piece = Q_BCASTL;
11868         quickBoard[sq] = piece;
11869         piece = quickBoard[from]; quickBoard[from] = 0;
11870         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11871     } else
11872     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
11873         quickBoard[(fromY<<4)+toX] = 0;
11874         moveDatabase[movePtr].piece = Q_EP;
11875         moveDatabase[movePtr++].to = (fromY<<4)+toX;
11876         moveDatabase[movePtr].to = sq;
11877     } else
11878     if(promoPiece != pieceType[piece]) {
11879         moveDatabase[movePtr++].piece = Q_PROMO;
11880         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
11881     }
11882     moveDatabase[movePtr].piece = piece;
11883     quickBoard[sq] = piece;
11884     movePtr++;
11885 }
11886
11887 int
11888 PackGame (Board board)
11889 {
11890     Move *newSpace = NULL;
11891     moveDatabase[movePtr].piece = 0; // terminate previous game
11892     if(movePtr > dataSize) {
11893         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
11894         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
11895         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
11896         if(newSpace) {
11897             int i;
11898             Move *p = moveDatabase, *q = newSpace;
11899             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
11900             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
11901             moveDatabase = newSpace;
11902         } else { // calloc failed, we must be out of memory. Too bad...
11903             dataSize = 0; // prevent calloc events for all subsequent games
11904             return 0;     // and signal this one isn't cached
11905         }
11906     }
11907     movePtr++;
11908     MakePieceList(board, counts);
11909     return movePtr;
11910 }
11911
11912 int
11913 QuickCompare (Board board, int *minCounts, int *maxCounts)
11914 {   // compare according to search mode
11915     int r, f;
11916     switch(appData.searchMode)
11917     {
11918       case 1: // exact position match
11919         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
11920         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11921             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11922         }
11923         break;
11924       case 2: // can have extra material on empty squares
11925         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11926             if(board[r][f] == EmptySquare) continue;
11927             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11928         }
11929         break;
11930       case 3: // material with exact Pawn structure
11931         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11932             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
11933             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11934         } // fall through to material comparison
11935       case 4: // exact material
11936         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
11937         break;
11938       case 6: // material range with given imbalance
11939         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
11940         // fall through to range comparison
11941       case 5: // material range
11942         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
11943     }
11944     return TRUE;
11945 }
11946
11947 int
11948 QuickScan (Board board, Move *move)
11949 {   // reconstruct game,and compare all positions in it
11950     int cnt=0, stretch=0, total = MakePieceList(board, counts);
11951     do {
11952         int piece = move->piece;
11953         int to = move->to, from = pieceList[piece];
11954         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
11955           if(!piece) return -1;
11956           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
11957             piece = (++move)->piece;
11958             from = pieceList[piece];
11959             counts[pieceType[piece]]--;
11960             pieceType[piece] = (ChessSquare) move->to;
11961             counts[move->to]++;
11962           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
11963             counts[pieceType[quickBoard[to]]]--;
11964             quickBoard[to] = 0; total--;
11965             move++;
11966             continue;
11967           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
11968             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
11969             from  = pieceList[piece]; // so this must be King
11970             quickBoard[from] = 0;
11971             pieceList[piece] = to;
11972             from = pieceList[(++move)->piece]; // for FRC this has to be done here
11973             quickBoard[from] = 0; // rook
11974             quickBoard[to] = piece;
11975             to = move->to; piece = move->piece;
11976             goto aftercastle;
11977           }
11978         }
11979         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
11980         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
11981         quickBoard[from] = 0;
11982       aftercastle:
11983         quickBoard[to] = piece;
11984         pieceList[piece] = to;
11985         cnt++; turn ^= 3;
11986         if(QuickCompare(soughtBoard, minSought, maxSought) ||
11987            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
11988            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
11989                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
11990           ) {
11991             static int lastCounts[EmptySquare+1];
11992             int i;
11993             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
11994             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
11995         } else stretch = 0;
11996         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
11997         move++;
11998     } while(1);
11999 }
12000
12001 void
12002 InitSearch ()
12003 {
12004     int r, f;
12005     flipSearch = FALSE;
12006     CopyBoard(soughtBoard, boards[currentMove]);
12007     soughtTotal = MakePieceList(soughtBoard, maxSought);
12008     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12009     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12010     CopyBoard(reverseBoard, boards[currentMove]);
12011     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12012         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12013         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12014         reverseBoard[r][f] = piece;
12015     }
12016     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12017     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12018     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12019                  || (boards[currentMove][CASTLING][2] == NoRights ||
12020                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12021                  && (boards[currentMove][CASTLING][5] == NoRights ||
12022                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12023       ) {
12024         flipSearch = TRUE;
12025         CopyBoard(flipBoard, soughtBoard);
12026         CopyBoard(rotateBoard, reverseBoard);
12027         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12028             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
12029             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12030         }
12031     }
12032     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12033     if(appData.searchMode >= 5) {
12034         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12035         MakePieceList(soughtBoard, minSought);
12036         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12037     }
12038     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12039         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12040 }
12041
12042 GameInfo dummyInfo;
12043 static int creatingBook;
12044
12045 int
12046 GameContainsPosition (FILE *f, ListGame *lg)
12047 {
12048     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12049     int fromX, fromY, toX, toY;
12050     char promoChar;
12051     static int initDone=FALSE;
12052
12053     // weed out games based on numerical tag comparison
12054     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12055     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12056     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12057     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12058     if(!initDone) {
12059         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12060         initDone = TRUE;
12061     }
12062     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen);
12063     else CopyBoard(boards[scratch], initialPosition); // default start position
12064     if(lg->moves) {
12065         turn = btm + 1;
12066         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12067         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12068     }
12069     if(btm) plyNr++;
12070     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12071     fseek(f, lg->offset, 0);
12072     yynewfile(f);
12073     while(1) {
12074         yyboardindex = scratch;
12075         quickFlag = plyNr+1;
12076         next = Myylex();
12077         quickFlag = 0;
12078         switch(next) {
12079             case PGNTag:
12080                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12081             default:
12082                 continue;
12083
12084             case XBoardGame:
12085             case GNUChessGame:
12086                 if(plyNr) return -1; // after we have seen moves, this is for new game
12087               continue;
12088
12089             case AmbiguousMove: // we cannot reconstruct the game beyond these two
12090             case ImpossibleMove:
12091             case WhiteWins: // game ends here with these four
12092             case BlackWins:
12093             case GameIsDrawn:
12094             case GameUnfinished:
12095                 return -1;
12096
12097             case IllegalMove:
12098                 if(appData.testLegality) return -1;
12099             case WhiteCapturesEnPassant:
12100             case BlackCapturesEnPassant:
12101             case WhitePromotion:
12102             case BlackPromotion:
12103             case WhiteNonPromotion:
12104             case BlackNonPromotion:
12105             case NormalMove:
12106             case WhiteKingSideCastle:
12107             case WhiteQueenSideCastle:
12108             case BlackKingSideCastle:
12109             case BlackQueenSideCastle:
12110             case WhiteKingSideCastleWild:
12111             case WhiteQueenSideCastleWild:
12112             case BlackKingSideCastleWild:
12113             case BlackQueenSideCastleWild:
12114             case WhiteHSideCastleFR:
12115             case WhiteASideCastleFR:
12116             case BlackHSideCastleFR:
12117             case BlackASideCastleFR:
12118                 fromX = currentMoveString[0] - AAA;
12119                 fromY = currentMoveString[1] - ONE;
12120                 toX = currentMoveString[2] - AAA;
12121                 toY = currentMoveString[3] - ONE;
12122                 promoChar = currentMoveString[4];
12123                 break;
12124             case WhiteDrop:
12125             case BlackDrop:
12126                 fromX = next == WhiteDrop ?
12127                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
12128                   (int) CharToPiece(ToLower(currentMoveString[0]));
12129                 fromY = DROP_RANK;
12130                 toX = currentMoveString[2] - AAA;
12131                 toY = currentMoveString[3] - ONE;
12132                 promoChar = 0;
12133                 break;
12134         }
12135         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12136         plyNr++;
12137         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12138         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12139         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12140         if(appData.findMirror) {
12141             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12142             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12143         }
12144     }
12145 }
12146
12147 /* Load the nth game from open file f */
12148 int
12149 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12150 {
12151     ChessMove cm;
12152     char buf[MSG_SIZ];
12153     int gn = gameNumber;
12154     ListGame *lg = NULL;
12155     int numPGNTags = 0;
12156     int err, pos = -1;
12157     GameMode oldGameMode;
12158     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12159
12160     if (appData.debugMode)
12161         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12162
12163     if (gameMode == Training )
12164         SetTrainingModeOff();
12165
12166     oldGameMode = gameMode;
12167     if (gameMode != BeginningOfGame) {
12168       Reset(FALSE, TRUE);
12169     }
12170
12171     gameFileFP = f;
12172     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12173         fclose(lastLoadGameFP);
12174     }
12175
12176     if (useList) {
12177         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12178
12179         if (lg) {
12180             fseek(f, lg->offset, 0);
12181             GameListHighlight(gameNumber);
12182             pos = lg->position;
12183             gn = 1;
12184         }
12185         else {
12186             if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
12187               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12188             else
12189             DisplayError(_("Game number out of range"), 0);
12190             return FALSE;
12191         }
12192     } else {
12193         GameListDestroy();
12194         if (fseek(f, 0, 0) == -1) {
12195             if (f == lastLoadGameFP ?
12196                 gameNumber == lastLoadGameNumber + 1 :
12197                 gameNumber == 1) {
12198                 gn = 1;
12199             } else {
12200                 DisplayError(_("Can't seek on game file"), 0);
12201                 return FALSE;
12202             }
12203         }
12204     }
12205     lastLoadGameFP = f;
12206     lastLoadGameNumber = gameNumber;
12207     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12208     lastLoadGameUseList = useList;
12209
12210     yynewfile(f);
12211
12212     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12213       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12214                 lg->gameInfo.black);
12215             DisplayTitle(buf);
12216     } else if (*title != NULLCHAR) {
12217         if (gameNumber > 1) {
12218           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12219             DisplayTitle(buf);
12220         } else {
12221             DisplayTitle(title);
12222         }
12223     }
12224
12225     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12226         gameMode = PlayFromGameFile;
12227         ModeHighlight();
12228     }
12229
12230     currentMove = forwardMostMove = backwardMostMove = 0;
12231     CopyBoard(boards[0], initialPosition);
12232     StopClocks();
12233
12234     /*
12235      * Skip the first gn-1 games in the file.
12236      * Also skip over anything that precedes an identifiable
12237      * start of game marker, to avoid being confused by
12238      * garbage at the start of the file.  Currently
12239      * recognized start of game markers are the move number "1",
12240      * the pattern "gnuchess .* game", the pattern
12241      * "^[#;%] [^ ]* game file", and a PGN tag block.
12242      * A game that starts with one of the latter two patterns
12243      * will also have a move number 1, possibly
12244      * following a position diagram.
12245      * 5-4-02: Let's try being more lenient and allowing a game to
12246      * start with an unnumbered move.  Does that break anything?
12247      */
12248     cm = lastLoadGameStart = EndOfFile;
12249     while (gn > 0) {
12250         yyboardindex = forwardMostMove;
12251         cm = (ChessMove) Myylex();
12252         switch (cm) {
12253           case EndOfFile:
12254             if (cmailMsgLoaded) {
12255                 nCmailGames = CMAIL_MAX_GAMES - gn;
12256             } else {
12257                 Reset(TRUE, TRUE);
12258                 DisplayError(_("Game not found in file"), 0);
12259             }
12260             return FALSE;
12261
12262           case GNUChessGame:
12263           case XBoardGame:
12264             gn--;
12265             lastLoadGameStart = cm;
12266             break;
12267
12268           case MoveNumberOne:
12269             switch (lastLoadGameStart) {
12270               case GNUChessGame:
12271               case XBoardGame:
12272               case PGNTag:
12273                 break;
12274               case MoveNumberOne:
12275               case EndOfFile:
12276                 gn--;           /* count this game */
12277                 lastLoadGameStart = cm;
12278                 break;
12279               default:
12280                 /* impossible */
12281                 break;
12282             }
12283             break;
12284
12285           case PGNTag:
12286             switch (lastLoadGameStart) {
12287               case GNUChessGame:
12288               case PGNTag:
12289               case MoveNumberOne:
12290               case EndOfFile:
12291                 gn--;           /* count this game */
12292                 lastLoadGameStart = cm;
12293                 break;
12294               case XBoardGame:
12295                 lastLoadGameStart = cm; /* game counted already */
12296                 break;
12297               default:
12298                 /* impossible */
12299                 break;
12300             }
12301             if (gn > 0) {
12302                 do {
12303                     yyboardindex = forwardMostMove;
12304                     cm = (ChessMove) Myylex();
12305                 } while (cm == PGNTag || cm == Comment);
12306             }
12307             break;
12308
12309           case WhiteWins:
12310           case BlackWins:
12311           case GameIsDrawn:
12312             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
12313                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
12314                     != CMAIL_OLD_RESULT) {
12315                     nCmailResults ++ ;
12316                     cmailResult[  CMAIL_MAX_GAMES
12317                                 - gn - 1] = CMAIL_OLD_RESULT;
12318                 }
12319             }
12320             break;
12321
12322           case NormalMove:
12323             /* Only a NormalMove can be at the start of a game
12324              * without a position diagram. */
12325             if (lastLoadGameStart == EndOfFile ) {
12326               gn--;
12327               lastLoadGameStart = MoveNumberOne;
12328             }
12329             break;
12330
12331           default:
12332             break;
12333         }
12334     }
12335
12336     if (appData.debugMode)
12337       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
12338
12339     if (cm == XBoardGame) {
12340         /* Skip any header junk before position diagram and/or move 1 */
12341         for (;;) {
12342             yyboardindex = forwardMostMove;
12343             cm = (ChessMove) Myylex();
12344
12345             if (cm == EndOfFile ||
12346                 cm == GNUChessGame || cm == XBoardGame) {
12347                 /* Empty game; pretend end-of-file and handle later */
12348                 cm = EndOfFile;
12349                 break;
12350             }
12351
12352             if (cm == MoveNumberOne || cm == PositionDiagram ||
12353                 cm == PGNTag || cm == Comment)
12354               break;
12355         }
12356     } else if (cm == GNUChessGame) {
12357         if (gameInfo.event != NULL) {
12358             free(gameInfo.event);
12359         }
12360         gameInfo.event = StrSave(yy_text);
12361     }
12362
12363     startedFromSetupPosition = FALSE;
12364     while (cm == PGNTag) {
12365         if (appData.debugMode)
12366           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
12367         err = ParsePGNTag(yy_text, &gameInfo);
12368         if (!err) numPGNTags++;
12369
12370         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
12371         if(gameInfo.variant != oldVariant) {
12372             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
12373             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
12374             InitPosition(TRUE);
12375             oldVariant = gameInfo.variant;
12376             if (appData.debugMode)
12377               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
12378         }
12379
12380
12381         if (gameInfo.fen != NULL) {
12382           Board initial_position;
12383           startedFromSetupPosition = TRUE;
12384           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
12385             Reset(TRUE, TRUE);
12386             DisplayError(_("Bad FEN position in file"), 0);
12387             return FALSE;
12388           }
12389           CopyBoard(boards[0], initial_position);
12390           if (blackPlaysFirst) {
12391             currentMove = forwardMostMove = backwardMostMove = 1;
12392             CopyBoard(boards[1], initial_position);
12393             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12394             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12395             timeRemaining[0][1] = whiteTimeRemaining;
12396             timeRemaining[1][1] = blackTimeRemaining;
12397             if (commentList[0] != NULL) {
12398               commentList[1] = commentList[0];
12399               commentList[0] = NULL;
12400             }
12401           } else {
12402             currentMove = forwardMostMove = backwardMostMove = 0;
12403           }
12404           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12405           {   int i;
12406               initialRulePlies = FENrulePlies;
12407               for( i=0; i< nrCastlingRights; i++ )
12408                   initialRights[i] = initial_position[CASTLING][i];
12409           }
12410           yyboardindex = forwardMostMove;
12411           free(gameInfo.fen);
12412           gameInfo.fen = NULL;
12413         }
12414
12415         yyboardindex = forwardMostMove;
12416         cm = (ChessMove) Myylex();
12417
12418         /* Handle comments interspersed among the tags */
12419         while (cm == Comment) {
12420             char *p;
12421             if (appData.debugMode)
12422               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12423             p = yy_text;
12424             AppendComment(currentMove, p, FALSE);
12425             yyboardindex = forwardMostMove;
12426             cm = (ChessMove) Myylex();
12427         }
12428     }
12429
12430     /* don't rely on existence of Event tag since if game was
12431      * pasted from clipboard the Event tag may not exist
12432      */
12433     if (numPGNTags > 0){
12434         char *tags;
12435         if (gameInfo.variant == VariantNormal) {
12436           VariantClass v = StringToVariant(gameInfo.event);
12437           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12438           if(v < VariantShogi) gameInfo.variant = v;
12439         }
12440         if (!matchMode) {
12441           if( appData.autoDisplayTags ) {
12442             tags = PGNTags(&gameInfo);
12443             TagsPopUp(tags, CmailMsg());
12444             free(tags);
12445           }
12446         }
12447     } else {
12448         /* Make something up, but don't display it now */
12449         SetGameInfo();
12450         TagsPopDown();
12451     }
12452
12453     if (cm == PositionDiagram) {
12454         int i, j;
12455         char *p;
12456         Board initial_position;
12457
12458         if (appData.debugMode)
12459           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12460
12461         if (!startedFromSetupPosition) {
12462             p = yy_text;
12463             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12464               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12465                 switch (*p) {
12466                   case '{':
12467                   case '[':
12468                   case '-':
12469                   case ' ':
12470                   case '\t':
12471                   case '\n':
12472                   case '\r':
12473                     break;
12474                   default:
12475                     initial_position[i][j++] = CharToPiece(*p);
12476                     break;
12477                 }
12478             while (*p == ' ' || *p == '\t' ||
12479                    *p == '\n' || *p == '\r') p++;
12480
12481             if (strncmp(p, "black", strlen("black"))==0)
12482               blackPlaysFirst = TRUE;
12483             else
12484               blackPlaysFirst = FALSE;
12485             startedFromSetupPosition = TRUE;
12486
12487             CopyBoard(boards[0], initial_position);
12488             if (blackPlaysFirst) {
12489                 currentMove = forwardMostMove = backwardMostMove = 1;
12490                 CopyBoard(boards[1], initial_position);
12491                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12492                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12493                 timeRemaining[0][1] = whiteTimeRemaining;
12494                 timeRemaining[1][1] = blackTimeRemaining;
12495                 if (commentList[0] != NULL) {
12496                     commentList[1] = commentList[0];
12497                     commentList[0] = NULL;
12498                 }
12499             } else {
12500                 currentMove = forwardMostMove = backwardMostMove = 0;
12501             }
12502         }
12503         yyboardindex = forwardMostMove;
12504         cm = (ChessMove) Myylex();
12505     }
12506
12507   if(!creatingBook) {
12508     if (first.pr == NoProc) {
12509         StartChessProgram(&first);
12510     }
12511     InitChessProgram(&first, FALSE);
12512     SendToProgram("force\n", &first);
12513     if (startedFromSetupPosition) {
12514         SendBoard(&first, forwardMostMove);
12515     if (appData.debugMode) {
12516         fprintf(debugFP, "Load Game\n");
12517     }
12518         DisplayBothClocks();
12519     }
12520   }
12521
12522     /* [HGM] server: flag to write setup moves in broadcast file as one */
12523     loadFlag = appData.suppressLoadMoves;
12524
12525     while (cm == Comment) {
12526         char *p;
12527         if (appData.debugMode)
12528           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12529         p = yy_text;
12530         AppendComment(currentMove, p, FALSE);
12531         yyboardindex = forwardMostMove;
12532         cm = (ChessMove) Myylex();
12533     }
12534
12535     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12536         cm == WhiteWins || cm == BlackWins ||
12537         cm == GameIsDrawn || cm == GameUnfinished) {
12538         DisplayMessage("", _("No moves in game"));
12539         if (cmailMsgLoaded) {
12540             if (appData.debugMode)
12541               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12542             ClearHighlights();
12543             flipView = FALSE;
12544         }
12545         DrawPosition(FALSE, boards[currentMove]);
12546         DisplayBothClocks();
12547         gameMode = EditGame;
12548         ModeHighlight();
12549         gameFileFP = NULL;
12550         cmailOldMove = 0;
12551         return TRUE;
12552     }
12553
12554     // [HGM] PV info: routine tests if comment empty
12555     if (!matchMode && (pausing || appData.timeDelay != 0)) {
12556         DisplayComment(currentMove - 1, commentList[currentMove]);
12557     }
12558     if (!matchMode && appData.timeDelay != 0)
12559       DrawPosition(FALSE, boards[currentMove]);
12560
12561     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12562       programStats.ok_to_send = 1;
12563     }
12564
12565     /* if the first token after the PGN tags is a move
12566      * and not move number 1, retrieve it from the parser
12567      */
12568     if (cm != MoveNumberOne)
12569         LoadGameOneMove(cm);
12570
12571     /* load the remaining moves from the file */
12572     while (LoadGameOneMove(EndOfFile)) {
12573       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12574       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12575     }
12576
12577     /* rewind to the start of the game */
12578     currentMove = backwardMostMove;
12579
12580     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12581
12582     if (oldGameMode == AnalyzeFile) {
12583       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
12584       AnalyzeFileEvent();
12585     } else
12586     if (oldGameMode == AnalyzeMode) {
12587       AnalyzeFileEvent();
12588     }
12589
12590     if(creatingBook) return TRUE;
12591     if (!matchMode && pos > 0) {
12592         ToNrEvent(pos); // [HGM] no autoplay if selected on position
12593     } else
12594     if (matchMode || appData.timeDelay == 0) {
12595       ToEndEvent();
12596     } else if (appData.timeDelay > 0) {
12597       AutoPlayGameLoop();
12598     }
12599
12600     if (appData.debugMode)
12601         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
12602
12603     loadFlag = 0; /* [HGM] true game starts */
12604     return TRUE;
12605 }
12606
12607 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
12608 int
12609 ReloadPosition (int offset)
12610 {
12611     int positionNumber = lastLoadPositionNumber + offset;
12612     if (lastLoadPositionFP == NULL) {
12613         DisplayError(_("No position has been loaded yet"), 0);
12614         return FALSE;
12615     }
12616     if (positionNumber <= 0) {
12617         DisplayError(_("Can't back up any further"), 0);
12618         return FALSE;
12619     }
12620     return LoadPosition(lastLoadPositionFP, positionNumber,
12621                         lastLoadPositionTitle);
12622 }
12623
12624 /* Load the nth position from the given file */
12625 int
12626 LoadPositionFromFile (char *filename, int n, char *title)
12627 {
12628     FILE *f;
12629     char buf[MSG_SIZ];
12630
12631     if (strcmp(filename, "-") == 0) {
12632         return LoadPosition(stdin, n, "stdin");
12633     } else {
12634         f = fopen(filename, "rb");
12635         if (f == NULL) {
12636             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12637             DisplayError(buf, errno);
12638             return FALSE;
12639         } else {
12640             return LoadPosition(f, n, title);
12641         }
12642     }
12643 }
12644
12645 /* Load the nth position from the given open file, and close it */
12646 int
12647 LoadPosition (FILE *f, int positionNumber, char *title)
12648 {
12649     char *p, line[MSG_SIZ];
12650     Board initial_position;
12651     int i, j, fenMode, pn;
12652
12653     if (gameMode == Training )
12654         SetTrainingModeOff();
12655
12656     if (gameMode != BeginningOfGame) {
12657         Reset(FALSE, TRUE);
12658     }
12659     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12660         fclose(lastLoadPositionFP);
12661     }
12662     if (positionNumber == 0) positionNumber = 1;
12663     lastLoadPositionFP = f;
12664     lastLoadPositionNumber = positionNumber;
12665     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12666     if (first.pr == NoProc && !appData.noChessProgram) {
12667       StartChessProgram(&first);
12668       InitChessProgram(&first, FALSE);
12669     }
12670     pn = positionNumber;
12671     if (positionNumber < 0) {
12672         /* Negative position number means to seek to that byte offset */
12673         if (fseek(f, -positionNumber, 0) == -1) {
12674             DisplayError(_("Can't seek on position file"), 0);
12675             return FALSE;
12676         };
12677         pn = 1;
12678     } else {
12679         if (fseek(f, 0, 0) == -1) {
12680             if (f == lastLoadPositionFP ?
12681                 positionNumber == lastLoadPositionNumber + 1 :
12682                 positionNumber == 1) {
12683                 pn = 1;
12684             } else {
12685                 DisplayError(_("Can't seek on position file"), 0);
12686                 return FALSE;
12687             }
12688         }
12689     }
12690     /* See if this file is FEN or old-style xboard */
12691     if (fgets(line, MSG_SIZ, f) == NULL) {
12692         DisplayError(_("Position not found in file"), 0);
12693         return FALSE;
12694     }
12695     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12696     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12697
12698     if (pn >= 2) {
12699         if (fenMode || line[0] == '#') pn--;
12700         while (pn > 0) {
12701             /* skip positions before number pn */
12702             if (fgets(line, MSG_SIZ, f) == NULL) {
12703                 Reset(TRUE, TRUE);
12704                 DisplayError(_("Position not found in file"), 0);
12705                 return FALSE;
12706             }
12707             if (fenMode || line[0] == '#') pn--;
12708         }
12709     }
12710
12711     if (fenMode) {
12712         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
12713             DisplayError(_("Bad FEN position in file"), 0);
12714             return FALSE;
12715         }
12716     } else {
12717         (void) fgets(line, MSG_SIZ, f);
12718         (void) fgets(line, MSG_SIZ, f);
12719
12720         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12721             (void) fgets(line, MSG_SIZ, f);
12722             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12723                 if (*p == ' ')
12724                   continue;
12725                 initial_position[i][j++] = CharToPiece(*p);
12726             }
12727         }
12728
12729         blackPlaysFirst = FALSE;
12730         if (!feof(f)) {
12731             (void) fgets(line, MSG_SIZ, f);
12732             if (strncmp(line, "black", strlen("black"))==0)
12733               blackPlaysFirst = TRUE;
12734         }
12735     }
12736     startedFromSetupPosition = TRUE;
12737
12738     CopyBoard(boards[0], initial_position);
12739     if (blackPlaysFirst) {
12740         currentMove = forwardMostMove = backwardMostMove = 1;
12741         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12742         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12743         CopyBoard(boards[1], initial_position);
12744         DisplayMessage("", _("Black to play"));
12745     } else {
12746         currentMove = forwardMostMove = backwardMostMove = 0;
12747         DisplayMessage("", _("White to play"));
12748     }
12749     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12750     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12751         SendToProgram("force\n", &first);
12752         SendBoard(&first, forwardMostMove);
12753     }
12754     if (appData.debugMode) {
12755 int i, j;
12756   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12757   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12758         fprintf(debugFP, "Load Position\n");
12759     }
12760
12761     if (positionNumber > 1) {
12762       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12763         DisplayTitle(line);
12764     } else {
12765         DisplayTitle(title);
12766     }
12767     gameMode = EditGame;
12768     ModeHighlight();
12769     ResetClocks();
12770     timeRemaining[0][1] = whiteTimeRemaining;
12771     timeRemaining[1][1] = blackTimeRemaining;
12772     DrawPosition(FALSE, boards[currentMove]);
12773
12774     return TRUE;
12775 }
12776
12777
12778 void
12779 CopyPlayerNameIntoFileName (char **dest, char *src)
12780 {
12781     while (*src != NULLCHAR && *src != ',') {
12782         if (*src == ' ') {
12783             *(*dest)++ = '_';
12784             src++;
12785         } else {
12786             *(*dest)++ = *src++;
12787         }
12788     }
12789 }
12790
12791 char *
12792 DefaultFileName (char *ext)
12793 {
12794     static char def[MSG_SIZ];
12795     char *p;
12796
12797     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12798         p = def;
12799         CopyPlayerNameIntoFileName(&p, gameInfo.white);
12800         *p++ = '-';
12801         CopyPlayerNameIntoFileName(&p, gameInfo.black);
12802         *p++ = '.';
12803         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12804     } else {
12805         def[0] = NULLCHAR;
12806     }
12807     return def;
12808 }
12809
12810 /* Save the current game to the given file */
12811 int
12812 SaveGameToFile (char *filename, int append)
12813 {
12814     FILE *f;
12815     char buf[MSG_SIZ];
12816     int result, i, t,tot=0;
12817
12818     if (strcmp(filename, "-") == 0) {
12819         return SaveGame(stdout, 0, NULL);
12820     } else {
12821         for(i=0; i<10; i++) { // upto 10 tries
12822              f = fopen(filename, append ? "a" : "w");
12823              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12824              if(f || errno != 13) break;
12825              DoSleep(t = 5 + random()%11); // wait 5-15 msec
12826              tot += t;
12827         }
12828         if (f == NULL) {
12829             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12830             DisplayError(buf, errno);
12831             return FALSE;
12832         } else {
12833             safeStrCpy(buf, lastMsg, MSG_SIZ);
12834             DisplayMessage(_("Waiting for access to save file"), "");
12835             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
12836             DisplayMessage(_("Saving game"), "");
12837             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
12838             result = SaveGame(f, 0, NULL);
12839             DisplayMessage(buf, "");
12840             return result;
12841         }
12842     }
12843 }
12844
12845 char *
12846 SavePart (char *str)
12847 {
12848     static char buf[MSG_SIZ];
12849     char *p;
12850
12851     p = strchr(str, ' ');
12852     if (p == NULL) return str;
12853     strncpy(buf, str, p - str);
12854     buf[p - str] = NULLCHAR;
12855     return buf;
12856 }
12857
12858 #define PGN_MAX_LINE 75
12859
12860 #define PGN_SIDE_WHITE  0
12861 #define PGN_SIDE_BLACK  1
12862
12863 static int
12864 FindFirstMoveOutOfBook (int side)
12865 {
12866     int result = -1;
12867
12868     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12869         int index = backwardMostMove;
12870         int has_book_hit = 0;
12871
12872         if( (index % 2) != side ) {
12873             index++;
12874         }
12875
12876         while( index < forwardMostMove ) {
12877             /* Check to see if engine is in book */
12878             int depth = pvInfoList[index].depth;
12879             int score = pvInfoList[index].score;
12880             int in_book = 0;
12881
12882             if( depth <= 2 ) {
12883                 in_book = 1;
12884             }
12885             else if( score == 0 && depth == 63 ) {
12886                 in_book = 1; /* Zappa */
12887             }
12888             else if( score == 2 && depth == 99 ) {
12889                 in_book = 1; /* Abrok */
12890             }
12891
12892             has_book_hit += in_book;
12893
12894             if( ! in_book ) {
12895                 result = index;
12896
12897                 break;
12898             }
12899
12900             index += 2;
12901         }
12902     }
12903
12904     return result;
12905 }
12906
12907 void
12908 GetOutOfBookInfo (char * buf)
12909 {
12910     int oob[2];
12911     int i;
12912     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12913
12914     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12915     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12916
12917     *buf = '\0';
12918
12919     if( oob[0] >= 0 || oob[1] >= 0 ) {
12920         for( i=0; i<2; i++ ) {
12921             int idx = oob[i];
12922
12923             if( idx >= 0 ) {
12924                 if( i > 0 && oob[0] >= 0 ) {
12925                     strcat( buf, "   " );
12926                 }
12927
12928                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12929                 sprintf( buf+strlen(buf), "%s%.2f",
12930                     pvInfoList[idx].score >= 0 ? "+" : "",
12931                     pvInfoList[idx].score / 100.0 );
12932             }
12933         }
12934     }
12935 }
12936
12937 /* Save game in PGN style and close the file */
12938 int
12939 SaveGamePGN (FILE *f)
12940 {
12941     int i, offset, linelen, newblock;
12942 //    char *movetext;
12943     char numtext[32];
12944     int movelen, numlen, blank;
12945     char move_buffer[100]; /* [AS] Buffer for move+PV info */
12946
12947     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12948
12949     PrintPGNTags(f, &gameInfo);
12950
12951     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
12952
12953     if (backwardMostMove > 0 || startedFromSetupPosition) {
12954         char *fen = PositionToFEN(backwardMostMove, NULL, 1);
12955         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12956         fprintf(f, "\n{--------------\n");
12957         PrintPosition(f, backwardMostMove);
12958         fprintf(f, "--------------}\n");
12959         free(fen);
12960     }
12961     else {
12962         /* [AS] Out of book annotation */
12963         if( appData.saveOutOfBookInfo ) {
12964             char buf[64];
12965
12966             GetOutOfBookInfo( buf );
12967
12968             if( buf[0] != '\0' ) {
12969                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12970             }
12971         }
12972
12973         fprintf(f, "\n");
12974     }
12975
12976     i = backwardMostMove;
12977     linelen = 0;
12978     newblock = TRUE;
12979
12980     while (i < forwardMostMove) {
12981         /* Print comments preceding this move */
12982         if (commentList[i] != NULL) {
12983             if (linelen > 0) fprintf(f, "\n");
12984             fprintf(f, "%s", commentList[i]);
12985             linelen = 0;
12986             newblock = TRUE;
12987         }
12988
12989         /* Format move number */
12990         if ((i % 2) == 0)
12991           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
12992         else
12993           if (newblock)
12994             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
12995           else
12996             numtext[0] = NULLCHAR;
12997
12998         numlen = strlen(numtext);
12999         newblock = FALSE;
13000
13001         /* Print move number */
13002         blank = linelen > 0 && numlen > 0;
13003         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13004             fprintf(f, "\n");
13005             linelen = 0;
13006             blank = 0;
13007         }
13008         if (blank) {
13009             fprintf(f, " ");
13010             linelen++;
13011         }
13012         fprintf(f, "%s", numtext);
13013         linelen += numlen;
13014
13015         /* Get move */
13016         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13017         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13018
13019         /* Print move */
13020         blank = linelen > 0 && movelen > 0;
13021         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13022             fprintf(f, "\n");
13023             linelen = 0;
13024             blank = 0;
13025         }
13026         if (blank) {
13027             fprintf(f, " ");
13028             linelen++;
13029         }
13030         fprintf(f, "%s", move_buffer);
13031         linelen += movelen;
13032
13033         /* [AS] Add PV info if present */
13034         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13035             /* [HGM] add time */
13036             char buf[MSG_SIZ]; int seconds;
13037
13038             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13039
13040             if( seconds <= 0)
13041               buf[0] = 0;
13042             else
13043               if( seconds < 30 )
13044                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13045               else
13046                 {
13047                   seconds = (seconds + 4)/10; // round to full seconds
13048                   if( seconds < 60 )
13049                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
13050                   else
13051                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
13052                 }
13053
13054             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
13055                       pvInfoList[i].score >= 0 ? "+" : "",
13056                       pvInfoList[i].score / 100.0,
13057                       pvInfoList[i].depth,
13058                       buf );
13059
13060             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
13061
13062             /* Print score/depth */
13063             blank = linelen > 0 && movelen > 0;
13064             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13065                 fprintf(f, "\n");
13066                 linelen = 0;
13067                 blank = 0;
13068             }
13069             if (blank) {
13070                 fprintf(f, " ");
13071                 linelen++;
13072             }
13073             fprintf(f, "%s", move_buffer);
13074             linelen += movelen;
13075         }
13076
13077         i++;
13078     }
13079
13080     /* Start a new line */
13081     if (linelen > 0) fprintf(f, "\n");
13082
13083     /* Print comments after last move */
13084     if (commentList[i] != NULL) {
13085         fprintf(f, "%s\n", commentList[i]);
13086     }
13087
13088     /* Print result */
13089     if (gameInfo.resultDetails != NULL &&
13090         gameInfo.resultDetails[0] != NULLCHAR) {
13091         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
13092                 PGNResult(gameInfo.result));
13093     } else {
13094         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13095     }
13096
13097     fclose(f);
13098     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13099     return TRUE;
13100 }
13101
13102 /* Save game in old style and close the file */
13103 int
13104 SaveGameOldStyle (FILE *f)
13105 {
13106     int i, offset;
13107     time_t tm;
13108
13109     tm = time((time_t *) NULL);
13110
13111     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
13112     PrintOpponents(f);
13113
13114     if (backwardMostMove > 0 || startedFromSetupPosition) {
13115         fprintf(f, "\n[--------------\n");
13116         PrintPosition(f, backwardMostMove);
13117         fprintf(f, "--------------]\n");
13118     } else {
13119         fprintf(f, "\n");
13120     }
13121
13122     i = backwardMostMove;
13123     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13124
13125     while (i < forwardMostMove) {
13126         if (commentList[i] != NULL) {
13127             fprintf(f, "[%s]\n", commentList[i]);
13128         }
13129
13130         if ((i % 2) == 1) {
13131             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
13132             i++;
13133         } else {
13134             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
13135             i++;
13136             if (commentList[i] != NULL) {
13137                 fprintf(f, "\n");
13138                 continue;
13139             }
13140             if (i >= forwardMostMove) {
13141                 fprintf(f, "\n");
13142                 break;
13143             }
13144             fprintf(f, "%s\n", parseList[i]);
13145             i++;
13146         }
13147     }
13148
13149     if (commentList[i] != NULL) {
13150         fprintf(f, "[%s]\n", commentList[i]);
13151     }
13152
13153     /* This isn't really the old style, but it's close enough */
13154     if (gameInfo.resultDetails != NULL &&
13155         gameInfo.resultDetails[0] != NULLCHAR) {
13156         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13157                 gameInfo.resultDetails);
13158     } else {
13159         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13160     }
13161
13162     fclose(f);
13163     return TRUE;
13164 }
13165
13166 /* Save the current game to open file f and close the file */
13167 int
13168 SaveGame (FILE *f, int dummy, char *dummy2)
13169 {
13170     if (gameMode == EditPosition) EditPositionDone(TRUE);
13171     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13172     if (appData.oldSaveStyle)
13173       return SaveGameOldStyle(f);
13174     else
13175       return SaveGamePGN(f);
13176 }
13177
13178 /* Save the current position to the given file */
13179 int
13180 SavePositionToFile (char *filename)
13181 {
13182     FILE *f;
13183     char buf[MSG_SIZ];
13184
13185     if (strcmp(filename, "-") == 0) {
13186         return SavePosition(stdout, 0, NULL);
13187     } else {
13188         f = fopen(filename, "a");
13189         if (f == NULL) {
13190             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13191             DisplayError(buf, errno);
13192             return FALSE;
13193         } else {
13194             safeStrCpy(buf, lastMsg, MSG_SIZ);
13195             DisplayMessage(_("Waiting for access to save file"), "");
13196             flock(fileno(f), LOCK_EX); // [HGM] lock
13197             DisplayMessage(_("Saving position"), "");
13198             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
13199             SavePosition(f, 0, NULL);
13200             DisplayMessage(buf, "");
13201             return TRUE;
13202         }
13203     }
13204 }
13205
13206 /* Save the current position to the given open file and close the file */
13207 int
13208 SavePosition (FILE *f, int dummy, char *dummy2)
13209 {
13210     time_t tm;
13211     char *fen;
13212
13213     if (gameMode == EditPosition) EditPositionDone(TRUE);
13214     if (appData.oldSaveStyle) {
13215         tm = time((time_t *) NULL);
13216
13217         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
13218         PrintOpponents(f);
13219         fprintf(f, "[--------------\n");
13220         PrintPosition(f, currentMove);
13221         fprintf(f, "--------------]\n");
13222     } else {
13223         fen = PositionToFEN(currentMove, NULL, 1);
13224         fprintf(f, "%s\n", fen);
13225         free(fen);
13226     }
13227     fclose(f);
13228     return TRUE;
13229 }
13230
13231 void
13232 ReloadCmailMsgEvent (int unregister)
13233 {
13234 #if !WIN32
13235     static char *inFilename = NULL;
13236     static char *outFilename;
13237     int i;
13238     struct stat inbuf, outbuf;
13239     int status;
13240
13241     /* Any registered moves are unregistered if unregister is set, */
13242     /* i.e. invoked by the signal handler */
13243     if (unregister) {
13244         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13245             cmailMoveRegistered[i] = FALSE;
13246             if (cmailCommentList[i] != NULL) {
13247                 free(cmailCommentList[i]);
13248                 cmailCommentList[i] = NULL;
13249             }
13250         }
13251         nCmailMovesRegistered = 0;
13252     }
13253
13254     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13255         cmailResult[i] = CMAIL_NOT_RESULT;
13256     }
13257     nCmailResults = 0;
13258
13259     if (inFilename == NULL) {
13260         /* Because the filenames are static they only get malloced once  */
13261         /* and they never get freed                                      */
13262         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
13263         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
13264
13265         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
13266         sprintf(outFilename, "%s.out", appData.cmailGameName);
13267     }
13268
13269     status = stat(outFilename, &outbuf);
13270     if (status < 0) {
13271         cmailMailedMove = FALSE;
13272     } else {
13273         status = stat(inFilename, &inbuf);
13274         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
13275     }
13276
13277     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
13278        counts the games, notes how each one terminated, etc.
13279
13280        It would be nice to remove this kludge and instead gather all
13281        the information while building the game list.  (And to keep it
13282        in the game list nodes instead of having a bunch of fixed-size
13283        parallel arrays.)  Note this will require getting each game's
13284        termination from the PGN tags, as the game list builder does
13285        not process the game moves.  --mann
13286        */
13287     cmailMsgLoaded = TRUE;
13288     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
13289
13290     /* Load first game in the file or popup game menu */
13291     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
13292
13293 #endif /* !WIN32 */
13294     return;
13295 }
13296
13297 int
13298 RegisterMove ()
13299 {
13300     FILE *f;
13301     char string[MSG_SIZ];
13302
13303     if (   cmailMailedMove
13304         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
13305         return TRUE;            /* Allow free viewing  */
13306     }
13307
13308     /* Unregister move to ensure that we don't leave RegisterMove        */
13309     /* with the move registered when the conditions for registering no   */
13310     /* longer hold                                                       */
13311     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
13312         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
13313         nCmailMovesRegistered --;
13314
13315         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
13316           {
13317               free(cmailCommentList[lastLoadGameNumber - 1]);
13318               cmailCommentList[lastLoadGameNumber - 1] = NULL;
13319           }
13320     }
13321
13322     if (cmailOldMove == -1) {
13323         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
13324         return FALSE;
13325     }
13326
13327     if (currentMove > cmailOldMove + 1) {
13328         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
13329         return FALSE;
13330     }
13331
13332     if (currentMove < cmailOldMove) {
13333         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
13334         return FALSE;
13335     }
13336
13337     if (forwardMostMove > currentMove) {
13338         /* Silently truncate extra moves */
13339         TruncateGame();
13340     }
13341
13342     if (   (currentMove == cmailOldMove + 1)
13343         || (   (currentMove == cmailOldMove)
13344             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
13345                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
13346         if (gameInfo.result != GameUnfinished) {
13347             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
13348         }
13349
13350         if (commentList[currentMove] != NULL) {
13351             cmailCommentList[lastLoadGameNumber - 1]
13352               = StrSave(commentList[currentMove]);
13353         }
13354         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
13355
13356         if (appData.debugMode)
13357           fprintf(debugFP, "Saving %s for game %d\n",
13358                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
13359
13360         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
13361
13362         f = fopen(string, "w");
13363         if (appData.oldSaveStyle) {
13364             SaveGameOldStyle(f); /* also closes the file */
13365
13366             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
13367             f = fopen(string, "w");
13368             SavePosition(f, 0, NULL); /* also closes the file */
13369         } else {
13370             fprintf(f, "{--------------\n");
13371             PrintPosition(f, currentMove);
13372             fprintf(f, "--------------}\n\n");
13373
13374             SaveGame(f, 0, NULL); /* also closes the file*/
13375         }
13376
13377         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
13378         nCmailMovesRegistered ++;
13379     } else if (nCmailGames == 1) {
13380         DisplayError(_("You have not made a move yet"), 0);
13381         return FALSE;
13382     }
13383
13384     return TRUE;
13385 }
13386
13387 void
13388 MailMoveEvent ()
13389 {
13390 #if !WIN32
13391     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
13392     FILE *commandOutput;
13393     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
13394     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
13395     int nBuffers;
13396     int i;
13397     int archived;
13398     char *arcDir;
13399
13400     if (! cmailMsgLoaded) {
13401         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
13402         return;
13403     }
13404
13405     if (nCmailGames == nCmailResults) {
13406         DisplayError(_("No unfinished games"), 0);
13407         return;
13408     }
13409
13410 #if CMAIL_PROHIBIT_REMAIL
13411     if (cmailMailedMove) {
13412       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);
13413         DisplayError(msg, 0);
13414         return;
13415     }
13416 #endif
13417
13418     if (! (cmailMailedMove || RegisterMove())) return;
13419
13420     if (   cmailMailedMove
13421         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13422       snprintf(string, MSG_SIZ, partCommandString,
13423                appData.debugMode ? " -v" : "", appData.cmailGameName);
13424         commandOutput = popen(string, "r");
13425
13426         if (commandOutput == NULL) {
13427             DisplayError(_("Failed to invoke cmail"), 0);
13428         } else {
13429             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13430                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13431             }
13432             if (nBuffers > 1) {
13433                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13434                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13435                 nBytes = MSG_SIZ - 1;
13436             } else {
13437                 (void) memcpy(msg, buffer, nBytes);
13438             }
13439             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13440
13441             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13442                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
13443
13444                 archived = TRUE;
13445                 for (i = 0; i < nCmailGames; i ++) {
13446                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
13447                         archived = FALSE;
13448                     }
13449                 }
13450                 if (   archived
13451                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13452                         != NULL)) {
13453                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13454                            arcDir,
13455                            appData.cmailGameName,
13456                            gameInfo.date);
13457                     LoadGameFromFile(buffer, 1, buffer, FALSE);
13458                     cmailMsgLoaded = FALSE;
13459                 }
13460             }
13461
13462             DisplayInformation(msg);
13463             pclose(commandOutput);
13464         }
13465     } else {
13466         if ((*cmailMsg) != '\0') {
13467             DisplayInformation(cmailMsg);
13468         }
13469     }
13470
13471     return;
13472 #endif /* !WIN32 */
13473 }
13474
13475 char *
13476 CmailMsg ()
13477 {
13478 #if WIN32
13479     return NULL;
13480 #else
13481     int  prependComma = 0;
13482     char number[5];
13483     char string[MSG_SIZ];       /* Space for game-list */
13484     int  i;
13485
13486     if (!cmailMsgLoaded) return "";
13487
13488     if (cmailMailedMove) {
13489       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13490     } else {
13491         /* Create a list of games left */
13492       snprintf(string, MSG_SIZ, "[");
13493         for (i = 0; i < nCmailGames; i ++) {
13494             if (! (   cmailMoveRegistered[i]
13495                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13496                 if (prependComma) {
13497                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13498                 } else {
13499                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13500                     prependComma = 1;
13501                 }
13502
13503                 strcat(string, number);
13504             }
13505         }
13506         strcat(string, "]");
13507
13508         if (nCmailMovesRegistered + nCmailResults == 0) {
13509             switch (nCmailGames) {
13510               case 1:
13511                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13512                 break;
13513
13514               case 2:
13515                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13516                 break;
13517
13518               default:
13519                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13520                          nCmailGames);
13521                 break;
13522             }
13523         } else {
13524             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13525               case 1:
13526                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13527                          string);
13528                 break;
13529
13530               case 0:
13531                 if (nCmailResults == nCmailGames) {
13532                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13533                 } else {
13534                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13535                 }
13536                 break;
13537
13538               default:
13539                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13540                          string);
13541             }
13542         }
13543     }
13544     return cmailMsg;
13545 #endif /* WIN32 */
13546 }
13547
13548 void
13549 ResetGameEvent ()
13550 {
13551     if (gameMode == Training)
13552       SetTrainingModeOff();
13553
13554     Reset(TRUE, TRUE);
13555     cmailMsgLoaded = FALSE;
13556     if (appData.icsActive) {
13557       SendToICS(ics_prefix);
13558       SendToICS("refresh\n");
13559     }
13560 }
13561
13562 void
13563 ExitEvent (int status)
13564 {
13565     exiting++;
13566     if (exiting > 2) {
13567       /* Give up on clean exit */
13568       exit(status);
13569     }
13570     if (exiting > 1) {
13571       /* Keep trying for clean exit */
13572       return;
13573     }
13574
13575     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
13576
13577     if (telnetISR != NULL) {
13578       RemoveInputSource(telnetISR);
13579     }
13580     if (icsPR != NoProc) {
13581       DestroyChildProcess(icsPR, TRUE);
13582     }
13583
13584     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
13585     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
13586
13587     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
13588     /* make sure this other one finishes before killing it!                  */
13589     if(endingGame) { int count = 0;
13590         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
13591         while(endingGame && count++ < 10) DoSleep(1);
13592         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
13593     }
13594
13595     /* Kill off chess programs */
13596     if (first.pr != NoProc) {
13597         ExitAnalyzeMode();
13598
13599         DoSleep( appData.delayBeforeQuit );
13600         SendToProgram("quit\n", &first);
13601         DoSleep( appData.delayAfterQuit );
13602         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
13603     }
13604     if (second.pr != NoProc) {
13605         DoSleep( appData.delayBeforeQuit );
13606         SendToProgram("quit\n", &second);
13607         DoSleep( appData.delayAfterQuit );
13608         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
13609     }
13610     if (first.isr != NULL) {
13611         RemoveInputSource(first.isr);
13612     }
13613     if (second.isr != NULL) {
13614         RemoveInputSource(second.isr);
13615     }
13616
13617     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13618     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13619
13620     ShutDownFrontEnd();
13621     exit(status);
13622 }
13623
13624 void
13625 PauseEngine (ChessProgramState *cps)
13626 {
13627     SendToProgram("pause\n", cps);
13628     cps->pause = 2;
13629 }
13630
13631 void
13632 UnPauseEngine (ChessProgramState *cps)
13633 {
13634     SendToProgram("resume\n", cps);
13635     cps->pause = 1;
13636 }
13637
13638 void
13639 PauseEvent ()
13640 {
13641     if (appData.debugMode)
13642         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13643     if (pausing) {
13644         pausing = FALSE;
13645         ModeHighlight();
13646         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
13647             StartClocks();
13648             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
13649                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
13650                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
13651             }
13652             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
13653             HandleMachineMove(stashedInputMove, stalledEngine);
13654             stalledEngine = NULL;
13655             return;
13656         }
13657         if (gameMode == MachinePlaysWhite ||
13658             gameMode == TwoMachinesPlay   ||
13659             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
13660             if(first.pause)  UnPauseEngine(&first);
13661             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
13662             if(second.pause) UnPauseEngine(&second);
13663             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
13664             StartClocks();
13665         } else {
13666             DisplayBothClocks();
13667         }
13668         if (gameMode == PlayFromGameFile) {
13669             if (appData.timeDelay >= 0)
13670                 AutoPlayGameLoop();
13671         } else if (gameMode == IcsExamining && pauseExamInvalid) {
13672             Reset(FALSE, TRUE);
13673             SendToICS(ics_prefix);
13674             SendToICS("refresh\n");
13675         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
13676             ForwardInner(forwardMostMove);
13677         }
13678         pauseExamInvalid = FALSE;
13679     } else {
13680         switch (gameMode) {
13681           default:
13682             return;
13683           case IcsExamining:
13684             pauseExamForwardMostMove = forwardMostMove;
13685             pauseExamInvalid = FALSE;
13686             /* fall through */
13687           case IcsObserving:
13688           case IcsPlayingWhite:
13689           case IcsPlayingBlack:
13690             pausing = TRUE;
13691             ModeHighlight();
13692             return;
13693           case PlayFromGameFile:
13694             (void) StopLoadGameTimer();
13695             pausing = TRUE;
13696             ModeHighlight();
13697             break;
13698           case BeginningOfGame:
13699             if (appData.icsActive) return;
13700             /* else fall through */
13701           case MachinePlaysWhite:
13702           case MachinePlaysBlack:
13703           case TwoMachinesPlay:
13704             if (forwardMostMove == 0)
13705               return;           /* don't pause if no one has moved */
13706             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
13707                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
13708                 if(onMove->pause) {           // thinking engine can be paused
13709                     PauseEngine(onMove);      // do it
13710                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
13711                         PauseEngine(onMove->other);
13712                     else
13713                         SendToProgram("easy\n", onMove->other);
13714                     StopClocks();
13715                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
13716             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
13717                 if(first.pause) {
13718                     PauseEngine(&first);
13719                     StopClocks();
13720                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
13721             } else { // human on move, pause pondering by either method
13722                 if(first.pause)
13723                     PauseEngine(&first);
13724                 else if(appData.ponderNextMove)
13725                     SendToProgram("easy\n", &first);
13726                 StopClocks();
13727             }
13728             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
13729           case AnalyzeMode:
13730             pausing = TRUE;
13731             ModeHighlight();
13732             break;
13733         }
13734     }
13735 }
13736
13737 void
13738 EditCommentEvent ()
13739 {
13740     char title[MSG_SIZ];
13741
13742     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13743       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13744     } else {
13745       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13746                WhiteOnMove(currentMove - 1) ? " " : ".. ",
13747                parseList[currentMove - 1]);
13748     }
13749
13750     EditCommentPopUp(currentMove, title, commentList[currentMove]);
13751 }
13752
13753
13754 void
13755 EditTagsEvent ()
13756 {
13757     char *tags = PGNTags(&gameInfo);
13758     bookUp = FALSE;
13759     EditTagsPopUp(tags, NULL);
13760     free(tags);
13761 }
13762
13763 void
13764 ToggleSecond ()
13765 {
13766   if(second.analyzing) {
13767     SendToProgram("exit\n", &second);
13768     second.analyzing = FALSE;
13769   } else {
13770     if (second.pr == NoProc) StartChessProgram(&second);
13771     InitChessProgram(&second, FALSE);
13772     FeedMovesToProgram(&second, currentMove);
13773
13774     SendToProgram("analyze\n", &second);
13775     second.analyzing = TRUE;
13776   }
13777 }
13778
13779 /* Toggle ShowThinking */
13780 void
13781 ToggleShowThinking()
13782 {
13783   appData.showThinking = !appData.showThinking;
13784   ShowThinkingEvent();
13785 }
13786
13787 int
13788 AnalyzeModeEvent ()
13789 {
13790     char buf[MSG_SIZ];
13791
13792     if (!first.analysisSupport) {
13793       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
13794       DisplayError(buf, 0);
13795       return 0;
13796     }
13797     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
13798     if (appData.icsActive) {
13799         if (gameMode != IcsObserving) {
13800           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
13801             DisplayError(buf, 0);
13802             /* secure check */
13803             if (appData.icsEngineAnalyze) {
13804                 if (appData.debugMode)
13805                     fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
13806                 ExitAnalyzeMode();
13807                 ModeHighlight();
13808             }
13809             return 0;
13810         }
13811         /* if enable, user wants to disable icsEngineAnalyze */
13812         if (appData.icsEngineAnalyze) {
13813                 ExitAnalyzeMode();
13814                 ModeHighlight();
13815                 return 0;
13816         }
13817         appData.icsEngineAnalyze = TRUE;
13818         if (appData.debugMode)
13819             fprintf(debugFP, "ICS engine analyze starting... \n");
13820     }
13821
13822     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
13823     if (appData.noChessProgram || gameMode == AnalyzeMode)
13824       return 0;
13825
13826     if (gameMode != AnalyzeFile) {
13827         if (!appData.icsEngineAnalyze) {
13828                EditGameEvent();
13829                if (gameMode != EditGame) return 0;
13830         }
13831         if (!appData.showThinking) ToggleShowThinking();
13832         ResurrectChessProgram();
13833         SendToProgram("analyze\n", &first);
13834         first.analyzing = TRUE;
13835         /*first.maybeThinking = TRUE;*/
13836         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13837         EngineOutputPopUp();
13838     }
13839     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
13840     pausing = FALSE;
13841     ModeHighlight();
13842     SetGameInfo();
13843
13844     StartAnalysisClock();
13845     GetTimeMark(&lastNodeCountTime);
13846     lastNodeCount = 0;
13847     return 1;
13848 }
13849
13850 void
13851 AnalyzeFileEvent ()
13852 {
13853     if (appData.noChessProgram || gameMode == AnalyzeFile)
13854       return;
13855
13856     if (!first.analysisSupport) {
13857       char buf[MSG_SIZ];
13858       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
13859       DisplayError(buf, 0);
13860       return;
13861     }
13862
13863     if (gameMode != AnalyzeMode) {
13864         keepInfo = 1; // mere annotating should not alter PGN tags
13865         EditGameEvent();
13866         keepInfo = 0;
13867         if (gameMode != EditGame) return;
13868         if (!appData.showThinking) ToggleShowThinking();
13869         ResurrectChessProgram();
13870         SendToProgram("analyze\n", &first);
13871         first.analyzing = TRUE;
13872         /*first.maybeThinking = TRUE;*/
13873         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13874         EngineOutputPopUp();
13875     }
13876     gameMode = AnalyzeFile;
13877     pausing = FALSE;
13878     ModeHighlight();
13879
13880     StartAnalysisClock();
13881     GetTimeMark(&lastNodeCountTime);
13882     lastNodeCount = 0;
13883     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
13884     AnalysisPeriodicEvent(1);
13885 }
13886
13887 void
13888 MachineWhiteEvent ()
13889 {
13890     char buf[MSG_SIZ];
13891     char *bookHit = NULL;
13892
13893     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
13894       return;
13895
13896
13897     if (gameMode == PlayFromGameFile ||
13898         gameMode == TwoMachinesPlay  ||
13899         gameMode == Training         ||
13900         gameMode == AnalyzeMode      ||
13901         gameMode == EndOfGame)
13902         EditGameEvent();
13903
13904     if (gameMode == EditPosition)
13905         EditPositionDone(TRUE);
13906
13907     if (!WhiteOnMove(currentMove)) {
13908         DisplayError(_("It is not White's turn"), 0);
13909         return;
13910     }
13911
13912     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13913       ExitAnalyzeMode();
13914
13915     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13916         gameMode == AnalyzeFile)
13917         TruncateGame();
13918
13919     ResurrectChessProgram();    /* in case it isn't running */
13920     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
13921         gameMode = MachinePlaysWhite;
13922         ResetClocks();
13923     } else
13924     gameMode = MachinePlaysWhite;
13925     pausing = FALSE;
13926     ModeHighlight();
13927     SetGameInfo();
13928     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13929     DisplayTitle(buf);
13930     if (first.sendName) {
13931       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
13932       SendToProgram(buf, &first);
13933     }
13934     if (first.sendTime) {
13935       if (first.useColors) {
13936         SendToProgram("black\n", &first); /*gnu kludge*/
13937       }
13938       SendTimeRemaining(&first, TRUE);
13939     }
13940     if (first.useColors) {
13941       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
13942     }
13943     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13944     SetMachineThinkingEnables();
13945     first.maybeThinking = TRUE;
13946     StartClocks();
13947     firstMove = FALSE;
13948
13949     if (appData.autoFlipView && !flipView) {
13950       flipView = !flipView;
13951       DrawPosition(FALSE, NULL);
13952       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13953     }
13954
13955     if(bookHit) { // [HGM] book: simulate book reply
13956         static char bookMove[MSG_SIZ]; // a bit generous?
13957
13958         programStats.nodes = programStats.depth = programStats.time =
13959         programStats.score = programStats.got_only_move = 0;
13960         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13961
13962         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13963         strcat(bookMove, bookHit);
13964         HandleMachineMove(bookMove, &first);
13965     }
13966 }
13967
13968 void
13969 MachineBlackEvent ()
13970 {
13971   char buf[MSG_SIZ];
13972   char *bookHit = NULL;
13973
13974     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
13975         return;
13976
13977
13978     if (gameMode == PlayFromGameFile ||
13979         gameMode == TwoMachinesPlay  ||
13980         gameMode == Training         ||
13981         gameMode == AnalyzeMode      ||
13982         gameMode == EndOfGame)
13983         EditGameEvent();
13984
13985     if (gameMode == EditPosition)
13986         EditPositionDone(TRUE);
13987
13988     if (WhiteOnMove(currentMove)) {
13989         DisplayError(_("It is not Black's turn"), 0);
13990         return;
13991     }
13992
13993     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13994       ExitAnalyzeMode();
13995
13996     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13997         gameMode == AnalyzeFile)
13998         TruncateGame();
13999
14000     ResurrectChessProgram();    /* in case it isn't running */
14001     gameMode = MachinePlaysBlack;
14002     pausing = FALSE;
14003     ModeHighlight();
14004     SetGameInfo();
14005     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14006     DisplayTitle(buf);
14007     if (first.sendName) {
14008       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14009       SendToProgram(buf, &first);
14010     }
14011     if (first.sendTime) {
14012       if (first.useColors) {
14013         SendToProgram("white\n", &first); /*gnu kludge*/
14014       }
14015       SendTimeRemaining(&first, FALSE);
14016     }
14017     if (first.useColors) {
14018       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14019     }
14020     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14021     SetMachineThinkingEnables();
14022     first.maybeThinking = TRUE;
14023     StartClocks();
14024
14025     if (appData.autoFlipView && flipView) {
14026       flipView = !flipView;
14027       DrawPosition(FALSE, NULL);
14028       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14029     }
14030     if(bookHit) { // [HGM] book: simulate book reply
14031         static char bookMove[MSG_SIZ]; // a bit generous?
14032
14033         programStats.nodes = programStats.depth = programStats.time =
14034         programStats.score = programStats.got_only_move = 0;
14035         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14036
14037         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14038         strcat(bookMove, bookHit);
14039         HandleMachineMove(bookMove, &first);
14040     }
14041 }
14042
14043
14044 void
14045 DisplayTwoMachinesTitle ()
14046 {
14047     char buf[MSG_SIZ];
14048     if (appData.matchGames > 0) {
14049         if(appData.tourneyFile[0]) {
14050           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14051                    gameInfo.white, _("vs."), gameInfo.black,
14052                    nextGame+1, appData.matchGames+1,
14053                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14054         } else
14055         if (first.twoMachinesColor[0] == 'w') {
14056           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14057                    gameInfo.white, _("vs."),  gameInfo.black,
14058                    first.matchWins, second.matchWins,
14059                    matchGame - 1 - (first.matchWins + second.matchWins));
14060         } else {
14061           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14062                    gameInfo.white, _("vs."), gameInfo.black,
14063                    second.matchWins, first.matchWins,
14064                    matchGame - 1 - (first.matchWins + second.matchWins));
14065         }
14066     } else {
14067       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14068     }
14069     DisplayTitle(buf);
14070 }
14071
14072 void
14073 SettingsMenuIfReady ()
14074 {
14075   if (second.lastPing != second.lastPong) {
14076     DisplayMessage("", _("Waiting for second chess program"));
14077     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14078     return;
14079   }
14080   ThawUI();
14081   DisplayMessage("", "");
14082   SettingsPopUp(&second);
14083 }
14084
14085 int
14086 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
14087 {
14088     char buf[MSG_SIZ];
14089     if (cps->pr == NoProc) {
14090         StartChessProgram(cps);
14091         if (cps->protocolVersion == 1) {
14092           retry();
14093           ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
14094         } else {
14095           /* kludge: allow timeout for initial "feature" command */
14096           if(retry != TwoMachinesEventIfReady) FreezeUI();
14097           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
14098           DisplayMessage("", buf);
14099           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
14100         }
14101         return 1;
14102     }
14103     return 0;
14104 }
14105
14106 void
14107 TwoMachinesEvent P((void))
14108 {
14109     int i;
14110     char buf[MSG_SIZ];
14111     ChessProgramState *onmove;
14112     char *bookHit = NULL;
14113     static int stalling = 0;
14114     TimeMark now;
14115     long wait;
14116
14117     if (appData.noChessProgram) return;
14118
14119     switch (gameMode) {
14120       case TwoMachinesPlay:
14121         return;
14122       case MachinePlaysWhite:
14123       case MachinePlaysBlack:
14124         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14125             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
14126             return;
14127         }
14128         /* fall through */
14129       case BeginningOfGame:
14130       case PlayFromGameFile:
14131       case EndOfGame:
14132         EditGameEvent();
14133         if (gameMode != EditGame) return;
14134         break;
14135       case EditPosition:
14136         EditPositionDone(TRUE);
14137         break;
14138       case AnalyzeMode:
14139       case AnalyzeFile:
14140         ExitAnalyzeMode();
14141         break;
14142       case EditGame:
14143       default:
14144         break;
14145     }
14146
14147 //    forwardMostMove = currentMove;
14148     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
14149     startingEngine = TRUE;
14150
14151     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
14152
14153     if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14154     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14155       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14156       return;
14157     }
14158     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14159
14160     if(second.protocolVersion >= 2 && !strstr(second.variants, VariantName(gameInfo.variant))) {
14161         startingEngine = FALSE;
14162         DisplayError("second engine does not play this", 0);
14163         return;
14164     }
14165
14166     if(!stalling) {
14167       InitChessProgram(&second, FALSE); // unbalances ping of second engine
14168       SendToProgram("force\n", &second);
14169       stalling = 1;
14170       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14171       return;
14172     }
14173     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14174     if(appData.matchPause>10000 || appData.matchPause<10)
14175                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14176     wait = SubtractTimeMarks(&now, &pauseStart);
14177     if(wait < appData.matchPause) {
14178         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14179         return;
14180     }
14181     // we are now committed to starting the game
14182     stalling = 0;
14183     DisplayMessage("", "");
14184     if (startedFromSetupPosition) {
14185         SendBoard(&second, backwardMostMove);
14186     if (appData.debugMode) {
14187         fprintf(debugFP, "Two Machines\n");
14188     }
14189     }
14190     for (i = backwardMostMove; i < forwardMostMove; i++) {
14191         SendMoveToProgram(i, &second);
14192     }
14193
14194     gameMode = TwoMachinesPlay;
14195     pausing = startingEngine = FALSE;
14196     ModeHighlight(); // [HGM] logo: this triggers display update of logos
14197     SetGameInfo();
14198     DisplayTwoMachinesTitle();
14199     firstMove = TRUE;
14200     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14201         onmove = &first;
14202     } else {
14203         onmove = &second;
14204     }
14205     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14206     SendToProgram(first.computerString, &first);
14207     if (first.sendName) {
14208       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14209       SendToProgram(buf, &first);
14210     }
14211     SendToProgram(second.computerString, &second);
14212     if (second.sendName) {
14213       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
14214       SendToProgram(buf, &second);
14215     }
14216
14217     ResetClocks();
14218     if (!first.sendTime || !second.sendTime) {
14219         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14220         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14221     }
14222     if (onmove->sendTime) {
14223       if (onmove->useColors) {
14224         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
14225       }
14226       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
14227     }
14228     if (onmove->useColors) {
14229       SendToProgram(onmove->twoMachinesColor, onmove);
14230     }
14231     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
14232 //    SendToProgram("go\n", onmove);
14233     onmove->maybeThinking = TRUE;
14234     SetMachineThinkingEnables();
14235
14236     StartClocks();
14237
14238     if(bookHit) { // [HGM] book: simulate book reply
14239         static char bookMove[MSG_SIZ]; // a bit generous?
14240
14241         programStats.nodes = programStats.depth = programStats.time =
14242         programStats.score = programStats.got_only_move = 0;
14243         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14244
14245         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14246         strcat(bookMove, bookHit);
14247         savedMessage = bookMove; // args for deferred call
14248         savedState = onmove;
14249         ScheduleDelayedEvent(DeferredBookMove, 1);
14250     }
14251 }
14252
14253 void
14254 TrainingEvent ()
14255 {
14256     if (gameMode == Training) {
14257       SetTrainingModeOff();
14258       gameMode = PlayFromGameFile;
14259       DisplayMessage("", _("Training mode off"));
14260     } else {
14261       gameMode = Training;
14262       animateTraining = appData.animate;
14263
14264       /* make sure we are not already at the end of the game */
14265       if (currentMove < forwardMostMove) {
14266         SetTrainingModeOn();
14267         DisplayMessage("", _("Training mode on"));
14268       } else {
14269         gameMode = PlayFromGameFile;
14270         DisplayError(_("Already at end of game"), 0);
14271       }
14272     }
14273     ModeHighlight();
14274 }
14275
14276 void
14277 IcsClientEvent ()
14278 {
14279     if (!appData.icsActive) return;
14280     switch (gameMode) {
14281       case IcsPlayingWhite:
14282       case IcsPlayingBlack:
14283       case IcsObserving:
14284       case IcsIdle:
14285       case BeginningOfGame:
14286       case IcsExamining:
14287         return;
14288
14289       case EditGame:
14290         break;
14291
14292       case EditPosition:
14293         EditPositionDone(TRUE);
14294         break;
14295
14296       case AnalyzeMode:
14297       case AnalyzeFile:
14298         ExitAnalyzeMode();
14299         break;
14300
14301       default:
14302         EditGameEvent();
14303         break;
14304     }
14305
14306     gameMode = IcsIdle;
14307     ModeHighlight();
14308     return;
14309 }
14310
14311 void
14312 EditGameEvent ()
14313 {
14314     int i;
14315
14316     switch (gameMode) {
14317       case Training:
14318         SetTrainingModeOff();
14319         break;
14320       case MachinePlaysWhite:
14321       case MachinePlaysBlack:
14322       case BeginningOfGame:
14323         SendToProgram("force\n", &first);
14324         SetUserThinkingEnables();
14325         break;
14326       case PlayFromGameFile:
14327         (void) StopLoadGameTimer();
14328         if (gameFileFP != NULL) {
14329             gameFileFP = NULL;
14330         }
14331         break;
14332       case EditPosition:
14333         EditPositionDone(TRUE);
14334         break;
14335       case AnalyzeMode:
14336       case AnalyzeFile:
14337         ExitAnalyzeMode();
14338         SendToProgram("force\n", &first);
14339         break;
14340       case TwoMachinesPlay:
14341         GameEnds(EndOfFile, NULL, GE_PLAYER);
14342         ResurrectChessProgram();
14343         SetUserThinkingEnables();
14344         break;
14345       case EndOfGame:
14346         ResurrectChessProgram();
14347         break;
14348       case IcsPlayingBlack:
14349       case IcsPlayingWhite:
14350         DisplayError(_("Warning: You are still playing a game"), 0);
14351         break;
14352       case IcsObserving:
14353         DisplayError(_("Warning: You are still observing a game"), 0);
14354         break;
14355       case IcsExamining:
14356         DisplayError(_("Warning: You are still examining a game"), 0);
14357         break;
14358       case IcsIdle:
14359         break;
14360       case EditGame:
14361       default:
14362         return;
14363     }
14364
14365     pausing = FALSE;
14366     StopClocks();
14367     first.offeredDraw = second.offeredDraw = 0;
14368
14369     if (gameMode == PlayFromGameFile) {
14370         whiteTimeRemaining = timeRemaining[0][currentMove];
14371         blackTimeRemaining = timeRemaining[1][currentMove];
14372         DisplayTitle("");
14373     }
14374
14375     if (gameMode == MachinePlaysWhite ||
14376         gameMode == MachinePlaysBlack ||
14377         gameMode == TwoMachinesPlay ||
14378         gameMode == EndOfGame) {
14379         i = forwardMostMove;
14380         while (i > currentMove) {
14381             SendToProgram("undo\n", &first);
14382             i--;
14383         }
14384         if(!adjustedClock) {
14385         whiteTimeRemaining = timeRemaining[0][currentMove];
14386         blackTimeRemaining = timeRemaining[1][currentMove];
14387         DisplayBothClocks();
14388         }
14389         if (whiteFlag || blackFlag) {
14390             whiteFlag = blackFlag = 0;
14391         }
14392         DisplayTitle("");
14393     }
14394
14395     gameMode = EditGame;
14396     ModeHighlight();
14397     SetGameInfo();
14398 }
14399
14400
14401 void
14402 EditPositionEvent ()
14403 {
14404     if (gameMode == EditPosition) {
14405         EditGameEvent();
14406         return;
14407     }
14408
14409     EditGameEvent();
14410     if (gameMode != EditGame) return;
14411
14412     gameMode = EditPosition;
14413     ModeHighlight();
14414     SetGameInfo();
14415     if (currentMove > 0)
14416       CopyBoard(boards[0], boards[currentMove]);
14417
14418     blackPlaysFirst = !WhiteOnMove(currentMove);
14419     ResetClocks();
14420     currentMove = forwardMostMove = backwardMostMove = 0;
14421     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14422     DisplayMove(-1);
14423     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
14424 }
14425
14426 void
14427 ExitAnalyzeMode ()
14428 {
14429     /* [DM] icsEngineAnalyze - possible call from other functions */
14430     if (appData.icsEngineAnalyze) {
14431         appData.icsEngineAnalyze = FALSE;
14432
14433         DisplayMessage("",_("Close ICS engine analyze..."));
14434     }
14435     if (first.analysisSupport && first.analyzing) {
14436       SendToBoth("exit\n");
14437       first.analyzing = second.analyzing = FALSE;
14438     }
14439     thinkOutput[0] = NULLCHAR;
14440 }
14441
14442 void
14443 EditPositionDone (Boolean fakeRights)
14444 {
14445     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
14446
14447     startedFromSetupPosition = TRUE;
14448     InitChessProgram(&first, FALSE);
14449     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
14450       boards[0][EP_STATUS] = EP_NONE;
14451       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
14452       if(boards[0][0][BOARD_WIDTH>>1] == king) {
14453         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
14454         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
14455       } else boards[0][CASTLING][2] = NoRights;
14456       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
14457         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
14458         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
14459       } else boards[0][CASTLING][5] = NoRights;
14460       if(gameInfo.variant == VariantSChess) {
14461         int i;
14462         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
14463           boards[0][VIRGIN][i] = 0;
14464           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
14465           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
14466         }
14467       }
14468     }
14469     SendToProgram("force\n", &first);
14470     if (blackPlaysFirst) {
14471         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
14472         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
14473         currentMove = forwardMostMove = backwardMostMove = 1;
14474         CopyBoard(boards[1], boards[0]);
14475     } else {
14476         currentMove = forwardMostMove = backwardMostMove = 0;
14477     }
14478     SendBoard(&first, forwardMostMove);
14479     if (appData.debugMode) {
14480         fprintf(debugFP, "EditPosDone\n");
14481     }
14482     DisplayTitle("");
14483     DisplayMessage("", "");
14484     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14485     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14486     gameMode = EditGame;
14487     ModeHighlight();
14488     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14489     ClearHighlights(); /* [AS] */
14490 }
14491
14492 /* Pause for `ms' milliseconds */
14493 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14494 void
14495 TimeDelay (long ms)
14496 {
14497     TimeMark m1, m2;
14498
14499     GetTimeMark(&m1);
14500     do {
14501         GetTimeMark(&m2);
14502     } while (SubtractTimeMarks(&m2, &m1) < ms);
14503 }
14504
14505 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14506 void
14507 SendMultiLineToICS (char *buf)
14508 {
14509     char temp[MSG_SIZ+1], *p;
14510     int len;
14511
14512     len = strlen(buf);
14513     if (len > MSG_SIZ)
14514       len = MSG_SIZ;
14515
14516     strncpy(temp, buf, len);
14517     temp[len] = 0;
14518
14519     p = temp;
14520     while (*p) {
14521         if (*p == '\n' || *p == '\r')
14522           *p = ' ';
14523         ++p;
14524     }
14525
14526     strcat(temp, "\n");
14527     SendToICS(temp);
14528     SendToPlayer(temp, strlen(temp));
14529 }
14530
14531 void
14532 SetWhiteToPlayEvent ()
14533 {
14534     if (gameMode == EditPosition) {
14535         blackPlaysFirst = FALSE;
14536         DisplayBothClocks();    /* works because currentMove is 0 */
14537     } else if (gameMode == IcsExamining) {
14538         SendToICS(ics_prefix);
14539         SendToICS("tomove white\n");
14540     }
14541 }
14542
14543 void
14544 SetBlackToPlayEvent ()
14545 {
14546     if (gameMode == EditPosition) {
14547         blackPlaysFirst = TRUE;
14548         currentMove = 1;        /* kludge */
14549         DisplayBothClocks();
14550         currentMove = 0;
14551     } else if (gameMode == IcsExamining) {
14552         SendToICS(ics_prefix);
14553         SendToICS("tomove black\n");
14554     }
14555 }
14556
14557 void
14558 EditPositionMenuEvent (ChessSquare selection, int x, int y)
14559 {
14560     char buf[MSG_SIZ];
14561     ChessSquare piece = boards[0][y][x];
14562
14563     if (gameMode != EditPosition && gameMode != IcsExamining) return;
14564
14565     switch (selection) {
14566       case ClearBoard:
14567         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
14568             SendToICS(ics_prefix);
14569             SendToICS("bsetup clear\n");
14570         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
14571             SendToICS(ics_prefix);
14572             SendToICS("clearboard\n");
14573         } else {
14574             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
14575                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
14576                 for (y = 0; y < BOARD_HEIGHT; y++) {
14577                     if (gameMode == IcsExamining) {
14578                         if (boards[currentMove][y][x] != EmptySquare) {
14579                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
14580                                     AAA + x, ONE + y);
14581                             SendToICS(buf);
14582                         }
14583                     } else {
14584                         boards[0][y][x] = p;
14585                     }
14586                 }
14587             }
14588         }
14589         if (gameMode == EditPosition) {
14590             DrawPosition(FALSE, boards[0]);
14591         }
14592         break;
14593
14594       case WhitePlay:
14595         SetWhiteToPlayEvent();
14596         break;
14597
14598       case BlackPlay:
14599         SetBlackToPlayEvent();
14600         break;
14601
14602       case EmptySquare:
14603         if (gameMode == IcsExamining) {
14604             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14605             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
14606             SendToICS(buf);
14607         } else {
14608             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14609                 if(x == BOARD_LEFT-2) {
14610                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
14611                     boards[0][y][1] = 0;
14612                 } else
14613                 if(x == BOARD_RGHT+1) {
14614                     if(y >= gameInfo.holdingsSize) break;
14615                     boards[0][y][BOARD_WIDTH-2] = 0;
14616                 } else break;
14617             }
14618             boards[0][y][x] = EmptySquare;
14619             DrawPosition(FALSE, boards[0]);
14620         }
14621         break;
14622
14623       case PromotePiece:
14624         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
14625            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
14626             selection = (ChessSquare) (PROMOTED piece);
14627         } else if(piece == EmptySquare) selection = WhiteSilver;
14628         else selection = (ChessSquare)((int)piece - 1);
14629         goto defaultlabel;
14630
14631       case DemotePiece:
14632         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
14633            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
14634             selection = (ChessSquare) (DEMOTED piece);
14635         } else if(piece == EmptySquare) selection = BlackSilver;
14636         else selection = (ChessSquare)((int)piece + 1);
14637         goto defaultlabel;
14638
14639       case WhiteQueen:
14640       case BlackQueen:
14641         if(gameInfo.variant == VariantShatranj ||
14642            gameInfo.variant == VariantXiangqi  ||
14643            gameInfo.variant == VariantCourier  ||
14644            gameInfo.variant == VariantASEAN    ||
14645            gameInfo.variant == VariantMakruk     )
14646             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
14647         goto defaultlabel;
14648
14649       case WhiteKing:
14650       case BlackKing:
14651         if(gameInfo.variant == VariantXiangqi)
14652             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
14653         if(gameInfo.variant == VariantKnightmate)
14654             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
14655       default:
14656         defaultlabel:
14657         if (gameMode == IcsExamining) {
14658             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14659             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
14660                      PieceToChar(selection), AAA + x, ONE + y);
14661             SendToICS(buf);
14662         } else {
14663             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14664                 int n;
14665                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
14666                     n = PieceToNumber(selection - BlackPawn);
14667                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
14668                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
14669                     boards[0][BOARD_HEIGHT-1-n][1]++;
14670                 } else
14671                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
14672                     n = PieceToNumber(selection);
14673                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
14674                     boards[0][n][BOARD_WIDTH-1] = selection;
14675                     boards[0][n][BOARD_WIDTH-2]++;
14676                 }
14677             } else
14678             boards[0][y][x] = selection;
14679             DrawPosition(TRUE, boards[0]);
14680             ClearHighlights();
14681             fromX = fromY = -1;
14682         }
14683         break;
14684     }
14685 }
14686
14687
14688 void
14689 DropMenuEvent (ChessSquare selection, int x, int y)
14690 {
14691     ChessMove moveType;
14692
14693     switch (gameMode) {
14694       case IcsPlayingWhite:
14695       case MachinePlaysBlack:
14696         if (!WhiteOnMove(currentMove)) {
14697             DisplayMoveError(_("It is Black's turn"));
14698             return;
14699         }
14700         moveType = WhiteDrop;
14701         break;
14702       case IcsPlayingBlack:
14703       case MachinePlaysWhite:
14704         if (WhiteOnMove(currentMove)) {
14705             DisplayMoveError(_("It is White's turn"));
14706             return;
14707         }
14708         moveType = BlackDrop;
14709         break;
14710       case EditGame:
14711         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
14712         break;
14713       default:
14714         return;
14715     }
14716
14717     if (moveType == BlackDrop && selection < BlackPawn) {
14718       selection = (ChessSquare) ((int) selection
14719                                  + (int) BlackPawn - (int) WhitePawn);
14720     }
14721     if (boards[currentMove][y][x] != EmptySquare) {
14722         DisplayMoveError(_("That square is occupied"));
14723         return;
14724     }
14725
14726     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
14727 }
14728
14729 void
14730 AcceptEvent ()
14731 {
14732     /* Accept a pending offer of any kind from opponent */
14733
14734     if (appData.icsActive) {
14735         SendToICS(ics_prefix);
14736         SendToICS("accept\n");
14737     } else if (cmailMsgLoaded) {
14738         if (currentMove == cmailOldMove &&
14739             commentList[cmailOldMove] != NULL &&
14740             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14741                    "Black offers a draw" : "White offers a draw")) {
14742             TruncateGame();
14743             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14744             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14745         } else {
14746             DisplayError(_("There is no pending offer on this move"), 0);
14747             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14748         }
14749     } else {
14750         /* Not used for offers from chess program */
14751     }
14752 }
14753
14754 void
14755 DeclineEvent ()
14756 {
14757     /* Decline a pending offer of any kind from opponent */
14758
14759     if (appData.icsActive) {
14760         SendToICS(ics_prefix);
14761         SendToICS("decline\n");
14762     } else if (cmailMsgLoaded) {
14763         if (currentMove == cmailOldMove &&
14764             commentList[cmailOldMove] != NULL &&
14765             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14766                    "Black offers a draw" : "White offers a draw")) {
14767 #ifdef NOTDEF
14768             AppendComment(cmailOldMove, "Draw declined", TRUE);
14769             DisplayComment(cmailOldMove - 1, "Draw declined");
14770 #endif /*NOTDEF*/
14771         } else {
14772             DisplayError(_("There is no pending offer on this move"), 0);
14773         }
14774     } else {
14775         /* Not used for offers from chess program */
14776     }
14777 }
14778
14779 void
14780 RematchEvent ()
14781 {
14782     /* Issue ICS rematch command */
14783     if (appData.icsActive) {
14784         SendToICS(ics_prefix);
14785         SendToICS("rematch\n");
14786     }
14787 }
14788
14789 void
14790 CallFlagEvent ()
14791 {
14792     /* Call your opponent's flag (claim a win on time) */
14793     if (appData.icsActive) {
14794         SendToICS(ics_prefix);
14795         SendToICS("flag\n");
14796     } else {
14797         switch (gameMode) {
14798           default:
14799             return;
14800           case MachinePlaysWhite:
14801             if (whiteFlag) {
14802                 if (blackFlag)
14803                   GameEnds(GameIsDrawn, "Both players ran out of time",
14804                            GE_PLAYER);
14805                 else
14806                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
14807             } else {
14808                 DisplayError(_("Your opponent is not out of time"), 0);
14809             }
14810             break;
14811           case MachinePlaysBlack:
14812             if (blackFlag) {
14813                 if (whiteFlag)
14814                   GameEnds(GameIsDrawn, "Both players ran out of time",
14815                            GE_PLAYER);
14816                 else
14817                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
14818             } else {
14819                 DisplayError(_("Your opponent is not out of time"), 0);
14820             }
14821             break;
14822         }
14823     }
14824 }
14825
14826 void
14827 ClockClick (int which)
14828 {       // [HGM] code moved to back-end from winboard.c
14829         if(which) { // black clock
14830           if (gameMode == EditPosition || gameMode == IcsExamining) {
14831             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14832             SetBlackToPlayEvent();
14833           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
14834           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
14835           } else if (shiftKey) {
14836             AdjustClock(which, -1);
14837           } else if (gameMode == IcsPlayingWhite ||
14838                      gameMode == MachinePlaysBlack) {
14839             CallFlagEvent();
14840           }
14841         } else { // white clock
14842           if (gameMode == EditPosition || gameMode == IcsExamining) {
14843             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14844             SetWhiteToPlayEvent();
14845           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
14846           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
14847           } else if (shiftKey) {
14848             AdjustClock(which, -1);
14849           } else if (gameMode == IcsPlayingBlack ||
14850                    gameMode == MachinePlaysWhite) {
14851             CallFlagEvent();
14852           }
14853         }
14854 }
14855
14856 void
14857 DrawEvent ()
14858 {
14859     /* Offer draw or accept pending draw offer from opponent */
14860
14861     if (appData.icsActive) {
14862         /* Note: tournament rules require draw offers to be
14863            made after you make your move but before you punch
14864            your clock.  Currently ICS doesn't let you do that;
14865            instead, you immediately punch your clock after making
14866            a move, but you can offer a draw at any time. */
14867
14868         SendToICS(ics_prefix);
14869         SendToICS("draw\n");
14870         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
14871     } else if (cmailMsgLoaded) {
14872         if (currentMove == cmailOldMove &&
14873             commentList[cmailOldMove] != NULL &&
14874             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14875                    "Black offers a draw" : "White offers a draw")) {
14876             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14877             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14878         } else if (currentMove == cmailOldMove + 1) {
14879             char *offer = WhiteOnMove(cmailOldMove) ?
14880               "White offers a draw" : "Black offers a draw";
14881             AppendComment(currentMove, offer, TRUE);
14882             DisplayComment(currentMove - 1, offer);
14883             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
14884         } else {
14885             DisplayError(_("You must make your move before offering a draw"), 0);
14886             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14887         }
14888     } else if (first.offeredDraw) {
14889         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
14890     } else {
14891         if (first.sendDrawOffers) {
14892             SendToProgram("draw\n", &first);
14893             userOfferedDraw = TRUE;
14894         }
14895     }
14896 }
14897
14898 void
14899 AdjournEvent ()
14900 {
14901     /* Offer Adjourn or accept pending Adjourn offer from opponent */
14902
14903     if (appData.icsActive) {
14904         SendToICS(ics_prefix);
14905         SendToICS("adjourn\n");
14906     } else {
14907         /* Currently GNU Chess doesn't offer or accept Adjourns */
14908     }
14909 }
14910
14911
14912 void
14913 AbortEvent ()
14914 {
14915     /* Offer Abort or accept pending Abort offer from opponent */
14916
14917     if (appData.icsActive) {
14918         SendToICS(ics_prefix);
14919         SendToICS("abort\n");
14920     } else {
14921         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
14922     }
14923 }
14924
14925 void
14926 ResignEvent ()
14927 {
14928     /* Resign.  You can do this even if it's not your turn. */
14929
14930     if (appData.icsActive) {
14931         SendToICS(ics_prefix);
14932         SendToICS("resign\n");
14933     } else {
14934         switch (gameMode) {
14935           case MachinePlaysWhite:
14936             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14937             break;
14938           case MachinePlaysBlack:
14939             GameEnds(BlackWins, "White resigns", GE_PLAYER);
14940             break;
14941           case EditGame:
14942             if (cmailMsgLoaded) {
14943                 TruncateGame();
14944                 if (WhiteOnMove(cmailOldMove)) {
14945                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
14946                 } else {
14947                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14948                 }
14949                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
14950             }
14951             break;
14952           default:
14953             break;
14954         }
14955     }
14956 }
14957
14958
14959 void
14960 StopObservingEvent ()
14961 {
14962     /* Stop observing current games */
14963     SendToICS(ics_prefix);
14964     SendToICS("unobserve\n");
14965 }
14966
14967 void
14968 StopExaminingEvent ()
14969 {
14970     /* Stop observing current game */
14971     SendToICS(ics_prefix);
14972     SendToICS("unexamine\n");
14973 }
14974
14975 void
14976 ForwardInner (int target)
14977 {
14978     int limit; int oldSeekGraphUp = seekGraphUp;
14979
14980     if (appData.debugMode)
14981         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
14982                 target, currentMove, forwardMostMove);
14983
14984     if (gameMode == EditPosition)
14985       return;
14986
14987     seekGraphUp = FALSE;
14988     MarkTargetSquares(1);
14989
14990     if (gameMode == PlayFromGameFile && !pausing)
14991       PauseEvent();
14992
14993     if (gameMode == IcsExamining && pausing)
14994       limit = pauseExamForwardMostMove;
14995     else
14996       limit = forwardMostMove;
14997
14998     if (target > limit) target = limit;
14999
15000     if (target > 0 && moveList[target - 1][0]) {
15001         int fromX, fromY, toX, toY;
15002         toX = moveList[target - 1][2] - AAA;
15003         toY = moveList[target - 1][3] - ONE;
15004         if (moveList[target - 1][1] == '@') {
15005             if (appData.highlightLastMove) {
15006                 SetHighlights(-1, -1, toX, toY);
15007             }
15008         } else {
15009             fromX = moveList[target - 1][0] - AAA;
15010             fromY = moveList[target - 1][1] - ONE;
15011             if (target == currentMove + 1) {
15012                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
15013             }
15014             if (appData.highlightLastMove) {
15015                 SetHighlights(fromX, fromY, toX, toY);
15016             }
15017         }
15018     }
15019     if (gameMode == EditGame || gameMode == AnalyzeMode ||
15020         gameMode == Training || gameMode == PlayFromGameFile ||
15021         gameMode == AnalyzeFile) {
15022         while (currentMove < target) {
15023             if(second.analyzing) SendMoveToProgram(currentMove, &second);
15024             SendMoveToProgram(currentMove++, &first);
15025         }
15026     } else {
15027         currentMove = target;
15028     }
15029
15030     if (gameMode == EditGame || gameMode == EndOfGame) {
15031         whiteTimeRemaining = timeRemaining[0][currentMove];
15032         blackTimeRemaining = timeRemaining[1][currentMove];
15033     }
15034     DisplayBothClocks();
15035     DisplayMove(currentMove - 1);
15036     DrawPosition(oldSeekGraphUp, boards[currentMove]);
15037     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15038     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
15039         DisplayComment(currentMove - 1, commentList[currentMove]);
15040     }
15041     ClearMap(); // [HGM] exclude: invalidate map
15042 }
15043
15044
15045 void
15046 ForwardEvent ()
15047 {
15048     if (gameMode == IcsExamining && !pausing) {
15049         SendToICS(ics_prefix);
15050         SendToICS("forward\n");
15051     } else {
15052         ForwardInner(currentMove + 1);
15053     }
15054 }
15055
15056 void
15057 ToEndEvent ()
15058 {
15059     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15060         /* to optimze, we temporarily turn off analysis mode while we feed
15061          * the remaining moves to the engine. Otherwise we get analysis output
15062          * after each move.
15063          */
15064         if (first.analysisSupport) {
15065           SendToProgram("exit\nforce\n", &first);
15066           first.analyzing = FALSE;
15067         }
15068     }
15069
15070     if (gameMode == IcsExamining && !pausing) {
15071         SendToICS(ics_prefix);
15072         SendToICS("forward 999999\n");
15073     } else {
15074         ForwardInner(forwardMostMove);
15075     }
15076
15077     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15078         /* we have fed all the moves, so reactivate analysis mode */
15079         SendToProgram("analyze\n", &first);
15080         first.analyzing = TRUE;
15081         /*first.maybeThinking = TRUE;*/
15082         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15083     }
15084 }
15085
15086 void
15087 BackwardInner (int target)
15088 {
15089     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
15090
15091     if (appData.debugMode)
15092         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
15093                 target, currentMove, forwardMostMove);
15094
15095     if (gameMode == EditPosition) return;
15096     seekGraphUp = FALSE;
15097     MarkTargetSquares(1);
15098     if (currentMove <= backwardMostMove) {
15099         ClearHighlights();
15100         DrawPosition(full_redraw, boards[currentMove]);
15101         return;
15102     }
15103     if (gameMode == PlayFromGameFile && !pausing)
15104       PauseEvent();
15105
15106     if (moveList[target][0]) {
15107         int fromX, fromY, toX, toY;
15108         toX = moveList[target][2] - AAA;
15109         toY = moveList[target][3] - ONE;
15110         if (moveList[target][1] == '@') {
15111             if (appData.highlightLastMove) {
15112                 SetHighlights(-1, -1, toX, toY);
15113             }
15114         } else {
15115             fromX = moveList[target][0] - AAA;
15116             fromY = moveList[target][1] - ONE;
15117             if (target == currentMove - 1) {
15118                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
15119             }
15120             if (appData.highlightLastMove) {
15121                 SetHighlights(fromX, fromY, toX, toY);
15122             }
15123         }
15124     }
15125     if (gameMode == EditGame || gameMode==AnalyzeMode ||
15126         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
15127         while (currentMove > target) {
15128             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
15129                 // null move cannot be undone. Reload program with move history before it.
15130                 int i;
15131                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
15132                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
15133                 }
15134                 SendBoard(&first, i);
15135               if(second.analyzing) SendBoard(&second, i);
15136                 for(currentMove=i; currentMove<target; currentMove++) {
15137                     SendMoveToProgram(currentMove, &first);
15138                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
15139                 }
15140                 break;
15141             }
15142             SendToBoth("undo\n");
15143             currentMove--;
15144         }
15145     } else {
15146         currentMove = target;
15147     }
15148
15149     if (gameMode == EditGame || gameMode == EndOfGame) {
15150         whiteTimeRemaining = timeRemaining[0][currentMove];
15151         blackTimeRemaining = timeRemaining[1][currentMove];
15152     }
15153     DisplayBothClocks();
15154     DisplayMove(currentMove - 1);
15155     DrawPosition(full_redraw, boards[currentMove]);
15156     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15157     // [HGM] PV info: routine tests if comment empty
15158     DisplayComment(currentMove - 1, commentList[currentMove]);
15159     ClearMap(); // [HGM] exclude: invalidate map
15160 }
15161
15162 void
15163 BackwardEvent ()
15164 {
15165     if (gameMode == IcsExamining && !pausing) {
15166         SendToICS(ics_prefix);
15167         SendToICS("backward\n");
15168     } else {
15169         BackwardInner(currentMove - 1);
15170     }
15171 }
15172
15173 void
15174 ToStartEvent ()
15175 {
15176     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15177         /* to optimize, we temporarily turn off analysis mode while we undo
15178          * all the moves. Otherwise we get analysis output after each undo.
15179          */
15180         if (first.analysisSupport) {
15181           SendToProgram("exit\nforce\n", &first);
15182           first.analyzing = FALSE;
15183         }
15184     }
15185
15186     if (gameMode == IcsExamining && !pausing) {
15187         SendToICS(ics_prefix);
15188         SendToICS("backward 999999\n");
15189     } else {
15190         BackwardInner(backwardMostMove);
15191     }
15192
15193     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15194         /* we have fed all the moves, so reactivate analysis mode */
15195         SendToProgram("analyze\n", &first);
15196         first.analyzing = TRUE;
15197         /*first.maybeThinking = TRUE;*/
15198         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15199     }
15200 }
15201
15202 void
15203 ToNrEvent (int to)
15204 {
15205   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
15206   if (to >= forwardMostMove) to = forwardMostMove;
15207   if (to <= backwardMostMove) to = backwardMostMove;
15208   if (to < currentMove) {
15209     BackwardInner(to);
15210   } else {
15211     ForwardInner(to);
15212   }
15213 }
15214
15215 void
15216 RevertEvent (Boolean annotate)
15217 {
15218     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
15219         return;
15220     }
15221     if (gameMode != IcsExamining) {
15222         DisplayError(_("You are not examining a game"), 0);
15223         return;
15224     }
15225     if (pausing) {
15226         DisplayError(_("You can't revert while pausing"), 0);
15227         return;
15228     }
15229     SendToICS(ics_prefix);
15230     SendToICS("revert\n");
15231 }
15232
15233 void
15234 RetractMoveEvent ()
15235 {
15236     switch (gameMode) {
15237       case MachinePlaysWhite:
15238       case MachinePlaysBlack:
15239         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15240             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
15241             return;
15242         }
15243         if (forwardMostMove < 2) return;
15244         currentMove = forwardMostMove = forwardMostMove - 2;
15245         whiteTimeRemaining = timeRemaining[0][currentMove];
15246         blackTimeRemaining = timeRemaining[1][currentMove];
15247         DisplayBothClocks();
15248         DisplayMove(currentMove - 1);
15249         ClearHighlights();/*!! could figure this out*/
15250         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
15251         SendToProgram("remove\n", &first);
15252         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
15253         break;
15254
15255       case BeginningOfGame:
15256       default:
15257         break;
15258
15259       case IcsPlayingWhite:
15260       case IcsPlayingBlack:
15261         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
15262             SendToICS(ics_prefix);
15263             SendToICS("takeback 2\n");
15264         } else {
15265             SendToICS(ics_prefix);
15266             SendToICS("takeback 1\n");
15267         }
15268         break;
15269     }
15270 }
15271
15272 void
15273 MoveNowEvent ()
15274 {
15275     ChessProgramState *cps;
15276
15277     switch (gameMode) {
15278       case MachinePlaysWhite:
15279         if (!WhiteOnMove(forwardMostMove)) {
15280             DisplayError(_("It is your turn"), 0);
15281             return;
15282         }
15283         cps = &first;
15284         break;
15285       case MachinePlaysBlack:
15286         if (WhiteOnMove(forwardMostMove)) {
15287             DisplayError(_("It is your turn"), 0);
15288             return;
15289         }
15290         cps = &first;
15291         break;
15292       case TwoMachinesPlay:
15293         if (WhiteOnMove(forwardMostMove) ==
15294             (first.twoMachinesColor[0] == 'w')) {
15295             cps = &first;
15296         } else {
15297             cps = &second;
15298         }
15299         break;
15300       case BeginningOfGame:
15301       default:
15302         return;
15303     }
15304     SendToProgram("?\n", cps);
15305 }
15306
15307 void
15308 TruncateGameEvent ()
15309 {
15310     EditGameEvent();
15311     if (gameMode != EditGame) return;
15312     TruncateGame();
15313 }
15314
15315 void
15316 TruncateGame ()
15317 {
15318     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
15319     if (forwardMostMove > currentMove) {
15320         if (gameInfo.resultDetails != NULL) {
15321             free(gameInfo.resultDetails);
15322             gameInfo.resultDetails = NULL;
15323             gameInfo.result = GameUnfinished;
15324         }
15325         forwardMostMove = currentMove;
15326         HistorySet(parseList, backwardMostMove, forwardMostMove,
15327                    currentMove-1);
15328     }
15329 }
15330
15331 void
15332 HintEvent ()
15333 {
15334     if (appData.noChessProgram) return;
15335     switch (gameMode) {
15336       case MachinePlaysWhite:
15337         if (WhiteOnMove(forwardMostMove)) {
15338             DisplayError(_("Wait until your turn"), 0);
15339             return;
15340         }
15341         break;
15342       case BeginningOfGame:
15343       case MachinePlaysBlack:
15344         if (!WhiteOnMove(forwardMostMove)) {
15345             DisplayError(_("Wait until your turn"), 0);
15346             return;
15347         }
15348         break;
15349       default:
15350         DisplayError(_("No hint available"), 0);
15351         return;
15352     }
15353     SendToProgram("hint\n", &first);
15354     hintRequested = TRUE;
15355 }
15356
15357 void
15358 CreateBookEvent ()
15359 {
15360     ListGame * lg = (ListGame *) gameList.head;
15361     FILE *f, *g;
15362     int nItem;
15363     static int secondTime = FALSE;
15364
15365     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
15366         DisplayError(_("Game list not loaded or empty"), 0);
15367         return;
15368     }
15369
15370     if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
15371         fclose(g);
15372         secondTime++;
15373         DisplayNote(_("Book file exists! Try again for overwrite."));
15374         return;
15375     }
15376
15377     creatingBook = TRUE;
15378     secondTime = FALSE;
15379
15380     /* Get list size */
15381     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
15382         LoadGame(f, nItem, "", TRUE);
15383         AddGameToBook(TRUE);
15384         lg = (ListGame *) lg->node.succ;
15385     }
15386
15387     creatingBook = FALSE;
15388     FlushBook();
15389 }
15390
15391 void
15392 BookEvent ()
15393 {
15394     if (appData.noChessProgram) return;
15395     switch (gameMode) {
15396       case MachinePlaysWhite:
15397         if (WhiteOnMove(forwardMostMove)) {
15398             DisplayError(_("Wait until your turn"), 0);
15399             return;
15400         }
15401         break;
15402       case BeginningOfGame:
15403       case MachinePlaysBlack:
15404         if (!WhiteOnMove(forwardMostMove)) {
15405             DisplayError(_("Wait until your turn"), 0);
15406             return;
15407         }
15408         break;
15409       case EditPosition:
15410         EditPositionDone(TRUE);
15411         break;
15412       case TwoMachinesPlay:
15413         return;
15414       default:
15415         break;
15416     }
15417     SendToProgram("bk\n", &first);
15418     bookOutput[0] = NULLCHAR;
15419     bookRequested = TRUE;
15420 }
15421
15422 void
15423 AboutGameEvent ()
15424 {
15425     char *tags = PGNTags(&gameInfo);
15426     TagsPopUp(tags, CmailMsg());
15427     free(tags);
15428 }
15429
15430 /* end button procedures */
15431
15432 void
15433 PrintPosition (FILE *fp, int move)
15434 {
15435     int i, j;
15436
15437     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15438         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15439             char c = PieceToChar(boards[move][i][j]);
15440             fputc(c == 'x' ? '.' : c, fp);
15441             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
15442         }
15443     }
15444     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
15445       fprintf(fp, "white to play\n");
15446     else
15447       fprintf(fp, "black to play\n");
15448 }
15449
15450 void
15451 PrintOpponents (FILE *fp)
15452 {
15453     if (gameInfo.white != NULL) {
15454         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
15455     } else {
15456         fprintf(fp, "\n");
15457     }
15458 }
15459
15460 /* Find last component of program's own name, using some heuristics */
15461 void
15462 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
15463 {
15464     char *p, *q, c;
15465     int local = (strcmp(host, "localhost") == 0);
15466     while (!local && (p = strchr(prog, ';')) != NULL) {
15467         p++;
15468         while (*p == ' ') p++;
15469         prog = p;
15470     }
15471     if (*prog == '"' || *prog == '\'') {
15472         q = strchr(prog + 1, *prog);
15473     } else {
15474         q = strchr(prog, ' ');
15475     }
15476     if (q == NULL) q = prog + strlen(prog);
15477     p = q;
15478     while (p >= prog && *p != '/' && *p != '\\') p--;
15479     p++;
15480     if(p == prog && *p == '"') p++;
15481     c = *q; *q = 0;
15482     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
15483     memcpy(buf, p, q - p);
15484     buf[q - p] = NULLCHAR;
15485     if (!local) {
15486         strcat(buf, "@");
15487         strcat(buf, host);
15488     }
15489 }
15490
15491 char *
15492 TimeControlTagValue ()
15493 {
15494     char buf[MSG_SIZ];
15495     if (!appData.clockMode) {
15496       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
15497     } else if (movesPerSession > 0) {
15498       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
15499     } else if (timeIncrement == 0) {
15500       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
15501     } else {
15502       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
15503     }
15504     return StrSave(buf);
15505 }
15506
15507 void
15508 SetGameInfo ()
15509 {
15510     /* This routine is used only for certain modes */
15511     VariantClass v = gameInfo.variant;
15512     ChessMove r = GameUnfinished;
15513     char *p = NULL;
15514
15515     if(keepInfo) return;
15516
15517     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
15518         r = gameInfo.result;
15519         p = gameInfo.resultDetails;
15520         gameInfo.resultDetails = NULL;
15521     }
15522     ClearGameInfo(&gameInfo);
15523     gameInfo.variant = v;
15524
15525     switch (gameMode) {
15526       case MachinePlaysWhite:
15527         gameInfo.event = StrSave( appData.pgnEventHeader );
15528         gameInfo.site = StrSave(HostName());
15529         gameInfo.date = PGNDate();
15530         gameInfo.round = StrSave("-");
15531         gameInfo.white = StrSave(first.tidy);
15532         gameInfo.black = StrSave(UserName());
15533         gameInfo.timeControl = TimeControlTagValue();
15534         break;
15535
15536       case MachinePlaysBlack:
15537         gameInfo.event = StrSave( appData.pgnEventHeader );
15538         gameInfo.site = StrSave(HostName());
15539         gameInfo.date = PGNDate();
15540         gameInfo.round = StrSave("-");
15541         gameInfo.white = StrSave(UserName());
15542         gameInfo.black = StrSave(first.tidy);
15543         gameInfo.timeControl = TimeControlTagValue();
15544         break;
15545
15546       case TwoMachinesPlay:
15547         gameInfo.event = StrSave( appData.pgnEventHeader );
15548         gameInfo.site = StrSave(HostName());
15549         gameInfo.date = PGNDate();
15550         if (roundNr > 0) {
15551             char buf[MSG_SIZ];
15552             snprintf(buf, MSG_SIZ, "%d", roundNr);
15553             gameInfo.round = StrSave(buf);
15554         } else {
15555             gameInfo.round = StrSave("-");
15556         }
15557         if (first.twoMachinesColor[0] == 'w') {
15558             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15559             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15560         } else {
15561             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15562             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15563         }
15564         gameInfo.timeControl = TimeControlTagValue();
15565         break;
15566
15567       case EditGame:
15568         gameInfo.event = StrSave("Edited game");
15569         gameInfo.site = StrSave(HostName());
15570         gameInfo.date = PGNDate();
15571         gameInfo.round = StrSave("-");
15572         gameInfo.white = StrSave("-");
15573         gameInfo.black = StrSave("-");
15574         gameInfo.result = r;
15575         gameInfo.resultDetails = p;
15576         break;
15577
15578       case EditPosition:
15579         gameInfo.event = StrSave("Edited position");
15580         gameInfo.site = StrSave(HostName());
15581         gameInfo.date = PGNDate();
15582         gameInfo.round = StrSave("-");
15583         gameInfo.white = StrSave("-");
15584         gameInfo.black = StrSave("-");
15585         break;
15586
15587       case IcsPlayingWhite:
15588       case IcsPlayingBlack:
15589       case IcsObserving:
15590       case IcsExamining:
15591         break;
15592
15593       case PlayFromGameFile:
15594         gameInfo.event = StrSave("Game from non-PGN file");
15595         gameInfo.site = StrSave(HostName());
15596         gameInfo.date = PGNDate();
15597         gameInfo.round = StrSave("-");
15598         gameInfo.white = StrSave("?");
15599         gameInfo.black = StrSave("?");
15600         break;
15601
15602       default:
15603         break;
15604     }
15605 }
15606
15607 void
15608 ReplaceComment (int index, char *text)
15609 {
15610     int len;
15611     char *p;
15612     float score;
15613
15614     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
15615        pvInfoList[index-1].depth == len &&
15616        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
15617        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
15618     while (*text == '\n') text++;
15619     len = strlen(text);
15620     while (len > 0 && text[len - 1] == '\n') len--;
15621
15622     if (commentList[index] != NULL)
15623       free(commentList[index]);
15624
15625     if (len == 0) {
15626         commentList[index] = NULL;
15627         return;
15628     }
15629   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
15630       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
15631       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
15632     commentList[index] = (char *) malloc(len + 2);
15633     strncpy(commentList[index], text, len);
15634     commentList[index][len] = '\n';
15635     commentList[index][len + 1] = NULLCHAR;
15636   } else {
15637     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
15638     char *p;
15639     commentList[index] = (char *) malloc(len + 7);
15640     safeStrCpy(commentList[index], "{\n", 3);
15641     safeStrCpy(commentList[index]+2, text, len+1);
15642     commentList[index][len+2] = NULLCHAR;
15643     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
15644     strcat(commentList[index], "\n}\n");
15645   }
15646 }
15647
15648 void
15649 CrushCRs (char *text)
15650 {
15651   char *p = text;
15652   char *q = text;
15653   char ch;
15654
15655   do {
15656     ch = *p++;
15657     if (ch == '\r') continue;
15658     *q++ = ch;
15659   } while (ch != '\0');
15660 }
15661
15662 void
15663 AppendComment (int index, char *text, Boolean addBraces)
15664 /* addBraces  tells if we should add {} */
15665 {
15666     int oldlen, len;
15667     char *old;
15668
15669 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
15670     if(addBraces == 3) addBraces = 0; else // force appending literally
15671     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
15672
15673     CrushCRs(text);
15674     while (*text == '\n') text++;
15675     len = strlen(text);
15676     while (len > 0 && text[len - 1] == '\n') len--;
15677     text[len] = NULLCHAR;
15678
15679     if (len == 0) return;
15680
15681     if (commentList[index] != NULL) {
15682       Boolean addClosingBrace = addBraces;
15683         old = commentList[index];
15684         oldlen = strlen(old);
15685         while(commentList[index][oldlen-1] ==  '\n')
15686           commentList[index][--oldlen] = NULLCHAR;
15687         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
15688         safeStrCpy(commentList[index], old, oldlen + len + 6);
15689         free(old);
15690         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
15691         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
15692           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
15693           while (*text == '\n') { text++; len--; }
15694           commentList[index][--oldlen] = NULLCHAR;
15695       }
15696         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
15697         else          strcat(commentList[index], "\n");
15698         strcat(commentList[index], text);
15699         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
15700         else          strcat(commentList[index], "\n");
15701     } else {
15702         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
15703         if(addBraces)
15704           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
15705         else commentList[index][0] = NULLCHAR;
15706         strcat(commentList[index], text);
15707         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
15708         if(addBraces == TRUE) strcat(commentList[index], "}\n");
15709     }
15710 }
15711
15712 static char *
15713 FindStr (char * text, char * sub_text)
15714 {
15715     char * result = strstr( text, sub_text );
15716
15717     if( result != NULL ) {
15718         result += strlen( sub_text );
15719     }
15720
15721     return result;
15722 }
15723
15724 /* [AS] Try to extract PV info from PGN comment */
15725 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
15726 char *
15727 GetInfoFromComment (int index, char * text)
15728 {
15729     char * sep = text, *p;
15730
15731     if( text != NULL && index > 0 ) {
15732         int score = 0;
15733         int depth = 0;
15734         int time = -1, sec = 0, deci;
15735         char * s_eval = FindStr( text, "[%eval " );
15736         char * s_emt = FindStr( text, "[%emt " );
15737 #if 0
15738         if( s_eval != NULL || s_emt != NULL ) {
15739 #else
15740         if(0) { // [HGM] this code is not finished, and could actually be detrimental
15741 #endif
15742             /* New style */
15743             char delim;
15744
15745             if( s_eval != NULL ) {
15746                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
15747                     return text;
15748                 }
15749
15750                 if( delim != ']' ) {
15751                     return text;
15752                 }
15753             }
15754
15755             if( s_emt != NULL ) {
15756             }
15757                 return text;
15758         }
15759         else {
15760             /* We expect something like: [+|-]nnn.nn/dd */
15761             int score_lo = 0;
15762
15763             if(*text != '{') return text; // [HGM] braces: must be normal comment
15764
15765             sep = strchr( text, '/' );
15766             if( sep == NULL || sep < (text+4) ) {
15767                 return text;
15768             }
15769
15770             p = text;
15771             if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
15772             if(p[1] == '(') { // comment starts with PV
15773                p = strchr(p, ')'); // locate end of PV
15774                if(p == NULL || sep < p+5) return text;
15775                // at this point we have something like "{(.*) +0.23/6 ..."
15776                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
15777                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
15778                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
15779             }
15780             time = -1; sec = -1; deci = -1;
15781             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
15782                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
15783                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
15784                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
15785                 return text;
15786             }
15787
15788             if( score_lo < 0 || score_lo >= 100 ) {
15789                 return text;
15790             }
15791
15792             if(sec >= 0) time = 600*time + 10*sec; else
15793             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
15794
15795             score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
15796
15797             /* [HGM] PV time: now locate end of PV info */
15798             while( *++sep >= '0' && *sep <= '9'); // strip depth
15799             if(time >= 0)
15800             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
15801             if(sec >= 0)
15802             while( *++sep >= '0' && *sep <= '9'); // strip seconds
15803             if(deci >= 0)
15804             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
15805             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
15806         }
15807
15808         if( depth <= 0 ) {
15809             return text;
15810         }
15811
15812         if( time < 0 ) {
15813             time = -1;
15814         }
15815
15816         pvInfoList[index-1].depth = depth;
15817         pvInfoList[index-1].score = score;
15818         pvInfoList[index-1].time  = 10*time; // centi-sec
15819         if(*sep == '}') *sep = 0; else *--sep = '{';
15820         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
15821     }
15822     return sep;
15823 }
15824
15825 void
15826 SendToProgram (char *message, ChessProgramState *cps)
15827 {
15828     int count, outCount, error;
15829     char buf[MSG_SIZ];
15830
15831     if (cps->pr == NoProc) return;
15832     Attention(cps);
15833
15834     if (appData.debugMode) {
15835         TimeMark now;
15836         GetTimeMark(&now);
15837         fprintf(debugFP, "%ld >%-6s: %s",
15838                 SubtractTimeMarks(&now, &programStartTime),
15839                 cps->which, message);
15840         if(serverFP)
15841             fprintf(serverFP, "%ld >%-6s: %s",
15842                 SubtractTimeMarks(&now, &programStartTime),
15843                 cps->which, message), fflush(serverFP);
15844     }
15845
15846     count = strlen(message);
15847     outCount = OutputToProcess(cps->pr, message, count, &error);
15848     if (outCount < count && !exiting
15849                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
15850       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15851       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
15852         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15853             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15854                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15855                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15856                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15857             } else {
15858                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15859                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15860                 gameInfo.result = res;
15861             }
15862             gameInfo.resultDetails = StrSave(buf);
15863         }
15864         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15865         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15866     }
15867 }
15868
15869 void
15870 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
15871 {
15872     char *end_str;
15873     char buf[MSG_SIZ];
15874     ChessProgramState *cps = (ChessProgramState *)closure;
15875
15876     if (isr != cps->isr) return; /* Killed intentionally */
15877     if (count <= 0) {
15878         if (count == 0) {
15879             RemoveInputSource(cps->isr);
15880             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
15881                     _(cps->which), cps->program);
15882             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
15883             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15884                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15885                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15886                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15887                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15888                 } else {
15889                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15890                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15891                     gameInfo.result = res;
15892                 }
15893                 gameInfo.resultDetails = StrSave(buf);
15894             }
15895             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15896             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
15897         } else {
15898             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
15899                     _(cps->which), cps->program);
15900             RemoveInputSource(cps->isr);
15901
15902             /* [AS] Program is misbehaving badly... kill it */
15903             if( count == -2 ) {
15904                 DestroyChildProcess( cps->pr, 9 );
15905                 cps->pr = NoProc;
15906             }
15907
15908             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15909         }
15910         return;
15911     }
15912
15913     if ((end_str = strchr(message, '\r')) != NULL)
15914       *end_str = NULLCHAR;
15915     if ((end_str = strchr(message, '\n')) != NULL)
15916       *end_str = NULLCHAR;
15917
15918     if (appData.debugMode) {
15919         TimeMark now; int print = 1;
15920         char *quote = ""; char c; int i;
15921
15922         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
15923                 char start = message[0];
15924                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
15925                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
15926                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
15927                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
15928                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
15929                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
15930                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
15931                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
15932                    sscanf(message, "hint: %c", &c)!=1 &&
15933                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
15934                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
15935                     print = (appData.engineComments >= 2);
15936                 }
15937                 message[0] = start; // restore original message
15938         }
15939         if(print) {
15940                 GetTimeMark(&now);
15941                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
15942                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15943                         quote,
15944                         message);
15945                 if(serverFP)
15946                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
15947                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15948                         quote,
15949                         message), fflush(serverFP);
15950         }
15951     }
15952
15953     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
15954     if (appData.icsEngineAnalyze) {
15955         if (strstr(message, "whisper") != NULL ||
15956              strstr(message, "kibitz") != NULL ||
15957             strstr(message, "tellics") != NULL) return;
15958     }
15959
15960     HandleMachineMove(message, cps);
15961 }
15962
15963
15964 void
15965 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
15966 {
15967     char buf[MSG_SIZ];
15968     int seconds;
15969
15970     if( timeControl_2 > 0 ) {
15971         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
15972             tc = timeControl_2;
15973         }
15974     }
15975     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
15976     inc /= cps->timeOdds;
15977     st  /= cps->timeOdds;
15978
15979     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
15980
15981     if (st > 0) {
15982       /* Set exact time per move, normally using st command */
15983       if (cps->stKludge) {
15984         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
15985         seconds = st % 60;
15986         if (seconds == 0) {
15987           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
15988         } else {
15989           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
15990         }
15991       } else {
15992         snprintf(buf, MSG_SIZ, "st %d\n", st);
15993       }
15994     } else {
15995       /* Set conventional or incremental time control, using level command */
15996       if (seconds == 0) {
15997         /* Note old gnuchess bug -- minutes:seconds used to not work.
15998            Fixed in later versions, but still avoid :seconds
15999            when seconds is 0. */
16000         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
16001       } else {
16002         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
16003                  seconds, inc/1000.);
16004       }
16005     }
16006     SendToProgram(buf, cps);
16007
16008     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
16009     /* Orthogonally, limit search to given depth */
16010     if (sd > 0) {
16011       if (cps->sdKludge) {
16012         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
16013       } else {
16014         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
16015       }
16016       SendToProgram(buf, cps);
16017     }
16018
16019     if(cps->nps >= 0) { /* [HGM] nps */
16020         if(cps->supportsNPS == FALSE)
16021           cps->nps = -1; // don't use if engine explicitly says not supported!
16022         else {
16023           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
16024           SendToProgram(buf, cps);
16025         }
16026     }
16027 }
16028
16029 ChessProgramState *
16030 WhitePlayer ()
16031 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
16032 {
16033     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
16034        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
16035         return &second;
16036     return &first;
16037 }
16038
16039 void
16040 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
16041 {
16042     char message[MSG_SIZ];
16043     long time, otime;
16044
16045     /* Note: this routine must be called when the clocks are stopped
16046        or when they have *just* been set or switched; otherwise
16047        it will be off by the time since the current tick started.
16048     */
16049     if (machineWhite) {
16050         time = whiteTimeRemaining / 10;
16051         otime = blackTimeRemaining / 10;
16052     } else {
16053         time = blackTimeRemaining / 10;
16054         otime = whiteTimeRemaining / 10;
16055     }
16056     /* [HGM] translate opponent's time by time-odds factor */
16057     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
16058
16059     if (time <= 0) time = 1;
16060     if (otime <= 0) otime = 1;
16061
16062     snprintf(message, MSG_SIZ, "time %ld\n", time);
16063     SendToProgram(message, cps);
16064
16065     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
16066     SendToProgram(message, cps);
16067 }
16068
16069 int
16070 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16071 {
16072   char buf[MSG_SIZ];
16073   int len = strlen(name);
16074   int val;
16075
16076   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16077     (*p) += len + 1;
16078     sscanf(*p, "%d", &val);
16079     *loc = (val != 0);
16080     while (**p && **p != ' ')
16081       (*p)++;
16082     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16083     SendToProgram(buf, cps);
16084     return TRUE;
16085   }
16086   return FALSE;
16087 }
16088
16089 int
16090 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16091 {
16092   char buf[MSG_SIZ];
16093   int len = strlen(name);
16094   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16095     (*p) += len + 1;
16096     sscanf(*p, "%d", loc);
16097     while (**p && **p != ' ') (*p)++;
16098     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16099     SendToProgram(buf, cps);
16100     return TRUE;
16101   }
16102   return FALSE;
16103 }
16104
16105 int
16106 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
16107 {
16108   char buf[MSG_SIZ];
16109   int len = strlen(name);
16110   if (strncmp((*p), name, len) == 0
16111       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
16112     (*p) += len + 2;
16113     ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
16114     sscanf(*p, "%[^\"]", *loc);
16115     while (**p && **p != '\"') (*p)++;
16116     if (**p == '\"') (*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 ParseOption (Option *opt, ChessProgramState *cps)
16126 // [HGM] options: process the string that defines an engine option, and determine
16127 // name, type, default value, and allowed value range
16128 {
16129         char *p, *q, buf[MSG_SIZ];
16130         int n, min = (-1)<<31, max = 1<<31, def;
16131
16132         if(p = strstr(opt->name, " -spin ")) {
16133             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16134             if(max < min) max = min; // enforce consistency
16135             if(def < min) def = min;
16136             if(def > max) def = max;
16137             opt->value = def;
16138             opt->min = min;
16139             opt->max = max;
16140             opt->type = Spin;
16141         } else if((p = strstr(opt->name, " -slider "))) {
16142             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
16143             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16144             if(max < min) max = min; // enforce consistency
16145             if(def < min) def = min;
16146             if(def > max) def = max;
16147             opt->value = def;
16148             opt->min = min;
16149             opt->max = max;
16150             opt->type = Spin; // Slider;
16151         } else if((p = strstr(opt->name, " -string "))) {
16152             opt->textValue = p+9;
16153             opt->type = TextBox;
16154         } else if((p = strstr(opt->name, " -file "))) {
16155             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16156             opt->textValue = p+7;
16157             opt->type = FileName; // FileName;
16158         } else if((p = strstr(opt->name, " -path "))) {
16159             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16160             opt->textValue = p+7;
16161             opt->type = PathName; // PathName;
16162         } else if(p = strstr(opt->name, " -check ")) {
16163             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
16164             opt->value = (def != 0);
16165             opt->type = CheckBox;
16166         } else if(p = strstr(opt->name, " -combo ")) {
16167             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
16168             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
16169             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
16170             opt->value = n = 0;
16171             while(q = StrStr(q, " /// ")) {
16172                 n++; *q = 0;    // count choices, and null-terminate each of them
16173                 q += 5;
16174                 if(*q == '*') { // remember default, which is marked with * prefix
16175                     q++;
16176                     opt->value = n;
16177                 }
16178                 cps->comboList[cps->comboCnt++] = q;
16179             }
16180             cps->comboList[cps->comboCnt++] = NULL;
16181             opt->max = n + 1;
16182             opt->type = ComboBox;
16183         } else if(p = strstr(opt->name, " -button")) {
16184             opt->type = Button;
16185         } else if(p = strstr(opt->name, " -save")) {
16186             opt->type = SaveButton;
16187         } else return FALSE;
16188         *p = 0; // terminate option name
16189         // now look if the command-line options define a setting for this engine option.
16190         if(cps->optionSettings && cps->optionSettings[0])
16191             p = strstr(cps->optionSettings, opt->name); else p = NULL;
16192         if(p && (p == cps->optionSettings || p[-1] == ',')) {
16193           snprintf(buf, MSG_SIZ, "option %s", p);
16194                 if(p = strstr(buf, ",")) *p = 0;
16195                 if(q = strchr(buf, '=')) switch(opt->type) {
16196                     case ComboBox:
16197                         for(n=0; n<opt->max; n++)
16198                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
16199                         break;
16200                     case TextBox:
16201                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
16202                         break;
16203                     case Spin:
16204                     case CheckBox:
16205                         opt->value = atoi(q+1);
16206                     default:
16207                         break;
16208                 }
16209                 strcat(buf, "\n");
16210                 SendToProgram(buf, cps);
16211         }
16212         return TRUE;
16213 }
16214
16215 void
16216 FeatureDone (ChessProgramState *cps, int val)
16217 {
16218   DelayedEventCallback cb = GetDelayedEvent();
16219   if ((cb == InitBackEnd3 && cps == &first) ||
16220       (cb == SettingsMenuIfReady && cps == &second) ||
16221       (cb == LoadEngine) ||
16222       (cb == TwoMachinesEventIfReady)) {
16223     CancelDelayedEvent();
16224     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
16225   }
16226   cps->initDone = val;
16227   if(val) cps->reload = FALSE;
16228 }
16229
16230 /* Parse feature command from engine */
16231 void
16232 ParseFeatures (char *args, ChessProgramState *cps)
16233 {
16234   char *p = args;
16235   char *q = NULL;
16236   int val;
16237   char buf[MSG_SIZ];
16238
16239   for (;;) {
16240     while (*p == ' ') p++;
16241     if (*p == NULLCHAR) return;
16242
16243     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
16244     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
16245     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
16246     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
16247     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
16248     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
16249     if (BoolFeature(&p, "reuse", &val, cps)) {
16250       /* Engine can disable reuse, but can't enable it if user said no */
16251       if (!val) cps->reuse = FALSE;
16252       continue;
16253     }
16254     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
16255     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
16256       if (gameMode == TwoMachinesPlay) {
16257         DisplayTwoMachinesTitle();
16258       } else {
16259         DisplayTitle("");
16260       }
16261       continue;
16262     }
16263     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
16264     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
16265     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
16266     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
16267     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
16268     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
16269     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
16270     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
16271     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
16272     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
16273     if (IntFeature(&p, "done", &val, cps)) {
16274       FeatureDone(cps, val);
16275       continue;
16276     }
16277     /* Added by Tord: */
16278     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
16279     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
16280     /* End of additions by Tord */
16281
16282     /* [HGM] added features: */
16283     if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
16284     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
16285     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
16286     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
16287     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
16288     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
16289     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
16290     if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
16291         if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
16292         FREE(cps->option[cps->nrOptions].name);
16293         cps->option[cps->nrOptions].name = q; q = NULL;
16294         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
16295           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
16296             SendToProgram(buf, cps);
16297             continue;
16298         }
16299         if(cps->nrOptions >= MAX_OPTIONS) {
16300             cps->nrOptions--;
16301             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
16302             DisplayError(buf, 0);
16303         }
16304         continue;
16305     }
16306     /* End of additions by HGM */
16307
16308     /* unknown feature: complain and skip */
16309     q = p;
16310     while (*q && *q != '=') q++;
16311     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
16312     SendToProgram(buf, cps);
16313     p = q;
16314     if (*p == '=') {
16315       p++;
16316       if (*p == '\"') {
16317         p++;
16318         while (*p && *p != '\"') p++;
16319         if (*p == '\"') p++;
16320       } else {
16321         while (*p && *p != ' ') p++;
16322       }
16323     }
16324   }
16325
16326 }
16327
16328 void
16329 PeriodicUpdatesEvent (int newState)
16330 {
16331     if (newState == appData.periodicUpdates)
16332       return;
16333
16334     appData.periodicUpdates=newState;
16335
16336     /* Display type changes, so update it now */
16337 //    DisplayAnalysis();
16338
16339     /* Get the ball rolling again... */
16340     if (newState) {
16341         AnalysisPeriodicEvent(1);
16342         StartAnalysisClock();
16343     }
16344 }
16345
16346 void
16347 PonderNextMoveEvent (int newState)
16348 {
16349     if (newState == appData.ponderNextMove) return;
16350     if (gameMode == EditPosition) EditPositionDone(TRUE);
16351     if (newState) {
16352         SendToProgram("hard\n", &first);
16353         if (gameMode == TwoMachinesPlay) {
16354             SendToProgram("hard\n", &second);
16355         }
16356     } else {
16357         SendToProgram("easy\n", &first);
16358         thinkOutput[0] = NULLCHAR;
16359         if (gameMode == TwoMachinesPlay) {
16360             SendToProgram("easy\n", &second);
16361         }
16362     }
16363     appData.ponderNextMove = newState;
16364 }
16365
16366 void
16367 NewSettingEvent (int option, int *feature, char *command, int value)
16368 {
16369     char buf[MSG_SIZ];
16370
16371     if (gameMode == EditPosition) EditPositionDone(TRUE);
16372     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
16373     if(feature == NULL || *feature) SendToProgram(buf, &first);
16374     if (gameMode == TwoMachinesPlay) {
16375         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
16376     }
16377 }
16378
16379 void
16380 ShowThinkingEvent ()
16381 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
16382 {
16383     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
16384     int newState = appData.showThinking
16385         // [HGM] thinking: other features now need thinking output as well
16386         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
16387
16388     if (oldState == newState) return;
16389     oldState = newState;
16390     if (gameMode == EditPosition) EditPositionDone(TRUE);
16391     if (oldState) {
16392         SendToProgram("post\n", &first);
16393         if (gameMode == TwoMachinesPlay) {
16394             SendToProgram("post\n", &second);
16395         }
16396     } else {
16397         SendToProgram("nopost\n", &first);
16398         thinkOutput[0] = NULLCHAR;
16399         if (gameMode == TwoMachinesPlay) {
16400             SendToProgram("nopost\n", &second);
16401         }
16402     }
16403 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
16404 }
16405
16406 void
16407 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
16408 {
16409   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
16410   if (pr == NoProc) return;
16411   AskQuestion(title, question, replyPrefix, pr);
16412 }
16413
16414 void
16415 TypeInEvent (char firstChar)
16416 {
16417     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
16418         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
16419         gameMode == AnalyzeMode || gameMode == EditGame ||
16420         gameMode == EditPosition || gameMode == IcsExamining ||
16421         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
16422         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
16423                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
16424                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
16425         gameMode == Training) PopUpMoveDialog(firstChar);
16426 }
16427
16428 void
16429 TypeInDoneEvent (char *move)
16430 {
16431         Board board;
16432         int n, fromX, fromY, toX, toY;
16433         char promoChar;
16434         ChessMove moveType;
16435
16436         // [HGM] FENedit
16437         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
16438                 EditPositionPasteFEN(move);
16439                 return;
16440         }
16441         // [HGM] movenum: allow move number to be typed in any mode
16442         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
16443           ToNrEvent(2*n-1);
16444           return;
16445         }
16446         // undocumented kludge: allow command-line option to be typed in!
16447         // (potentially fatal, and does not implement the effect of the option.)
16448         // should only be used for options that are values on which future decisions will be made,
16449         // and definitely not on options that would be used during initialization.
16450         if(strstr(move, "!!! -") == move) {
16451             ParseArgsFromString(move+4);
16452             return;
16453         }
16454
16455       if (gameMode != EditGame && currentMove != forwardMostMove &&
16456         gameMode != Training) {
16457         DisplayMoveError(_("Displayed move is not current"));
16458       } else {
16459         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16460           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
16461         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
16462         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16463           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
16464           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
16465         } else {
16466           DisplayMoveError(_("Could not parse move"));
16467         }
16468       }
16469 }
16470
16471 void
16472 DisplayMove (int moveNumber)
16473 {
16474     char message[MSG_SIZ];
16475     char res[MSG_SIZ];
16476     char cpThinkOutput[MSG_SIZ];
16477
16478     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
16479
16480     if (moveNumber == forwardMostMove - 1 ||
16481         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16482
16483         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
16484
16485         if (strchr(cpThinkOutput, '\n')) {
16486             *strchr(cpThinkOutput, '\n') = NULLCHAR;
16487         }
16488     } else {
16489         *cpThinkOutput = NULLCHAR;
16490     }
16491
16492     /* [AS] Hide thinking from human user */
16493     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
16494         *cpThinkOutput = NULLCHAR;
16495         if( thinkOutput[0] != NULLCHAR ) {
16496             int i;
16497
16498             for( i=0; i<=hiddenThinkOutputState; i++ ) {
16499                 cpThinkOutput[i] = '.';
16500             }
16501             cpThinkOutput[i] = NULLCHAR;
16502             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
16503         }
16504     }
16505
16506     if (moveNumber == forwardMostMove - 1 &&
16507         gameInfo.resultDetails != NULL) {
16508         if (gameInfo.resultDetails[0] == NULLCHAR) {
16509           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
16510         } else {
16511           snprintf(res, MSG_SIZ, " {%s} %s",
16512                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
16513         }
16514     } else {
16515         res[0] = NULLCHAR;
16516     }
16517
16518     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16519         DisplayMessage(res, cpThinkOutput);
16520     } else {
16521       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
16522                 WhiteOnMove(moveNumber) ? " " : ".. ",
16523                 parseList[moveNumber], res);
16524         DisplayMessage(message, cpThinkOutput);
16525     }
16526 }
16527
16528 void
16529 DisplayComment (int moveNumber, char *text)
16530 {
16531     char title[MSG_SIZ];
16532
16533     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16534       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
16535     } else {
16536       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
16537               WhiteOnMove(moveNumber) ? " " : ".. ",
16538               parseList[moveNumber]);
16539     }
16540     if (text != NULL && (appData.autoDisplayComment || commentUp))
16541         CommentPopUp(title, text);
16542 }
16543
16544 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
16545  * might be busy thinking or pondering.  It can be omitted if your
16546  * gnuchess is configured to stop thinking immediately on any user
16547  * input.  However, that gnuchess feature depends on the FIONREAD
16548  * ioctl, which does not work properly on some flavors of Unix.
16549  */
16550 void
16551 Attention (ChessProgramState *cps)
16552 {
16553 #if ATTENTION
16554     if (!cps->useSigint) return;
16555     if (appData.noChessProgram || (cps->pr == NoProc)) return;
16556     switch (gameMode) {
16557       case MachinePlaysWhite:
16558       case MachinePlaysBlack:
16559       case TwoMachinesPlay:
16560       case IcsPlayingWhite:
16561       case IcsPlayingBlack:
16562       case AnalyzeMode:
16563       case AnalyzeFile:
16564         /* Skip if we know it isn't thinking */
16565         if (!cps->maybeThinking) return;
16566         if (appData.debugMode)
16567           fprintf(debugFP, "Interrupting %s\n", cps->which);
16568         InterruptChildProcess(cps->pr);
16569         cps->maybeThinking = FALSE;
16570         break;
16571       default:
16572         break;
16573     }
16574 #endif /*ATTENTION*/
16575 }
16576
16577 int
16578 CheckFlags ()
16579 {
16580     if (whiteTimeRemaining <= 0) {
16581         if (!whiteFlag) {
16582             whiteFlag = TRUE;
16583             if (appData.icsActive) {
16584                 if (appData.autoCallFlag &&
16585                     gameMode == IcsPlayingBlack && !blackFlag) {
16586                   SendToICS(ics_prefix);
16587                   SendToICS("flag\n");
16588                 }
16589             } else {
16590                 if (blackFlag) {
16591                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16592                 } else {
16593                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
16594                     if (appData.autoCallFlag) {
16595                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
16596                         return TRUE;
16597                     }
16598                 }
16599             }
16600         }
16601     }
16602     if (blackTimeRemaining <= 0) {
16603         if (!blackFlag) {
16604             blackFlag = TRUE;
16605             if (appData.icsActive) {
16606                 if (appData.autoCallFlag &&
16607                     gameMode == IcsPlayingWhite && !whiteFlag) {
16608                   SendToICS(ics_prefix);
16609                   SendToICS("flag\n");
16610                 }
16611             } else {
16612                 if (whiteFlag) {
16613                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16614                 } else {
16615                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
16616                     if (appData.autoCallFlag) {
16617                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
16618                         return TRUE;
16619                     }
16620                 }
16621             }
16622         }
16623     }
16624     return FALSE;
16625 }
16626
16627 void
16628 CheckTimeControl ()
16629 {
16630     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
16631         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
16632
16633     /*
16634      * add time to clocks when time control is achieved ([HGM] now also used for increment)
16635      */
16636     if ( !WhiteOnMove(forwardMostMove) ) {
16637         /* White made time control */
16638         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
16639         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
16640         /* [HGM] time odds: correct new time quota for time odds! */
16641                                             / WhitePlayer()->timeOdds;
16642         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
16643     } else {
16644         lastBlack -= blackTimeRemaining;
16645         /* Black made time control */
16646         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
16647                                             / WhitePlayer()->other->timeOdds;
16648         lastWhite = whiteTimeRemaining;
16649     }
16650 }
16651
16652 void
16653 DisplayBothClocks ()
16654 {
16655     int wom = gameMode == EditPosition ?
16656       !blackPlaysFirst : WhiteOnMove(currentMove);
16657     DisplayWhiteClock(whiteTimeRemaining, wom);
16658     DisplayBlackClock(blackTimeRemaining, !wom);
16659 }
16660
16661
16662 /* Timekeeping seems to be a portability nightmare.  I think everyone
16663    has ftime(), but I'm really not sure, so I'm including some ifdefs
16664    to use other calls if you don't.  Clocks will be less accurate if
16665    you have neither ftime nor gettimeofday.
16666 */
16667
16668 /* VS 2008 requires the #include outside of the function */
16669 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
16670 #include <sys/timeb.h>
16671 #endif
16672
16673 /* Get the current time as a TimeMark */
16674 void
16675 GetTimeMark (TimeMark *tm)
16676 {
16677 #if HAVE_GETTIMEOFDAY
16678
16679     struct timeval timeVal;
16680     struct timezone timeZone;
16681
16682     gettimeofday(&timeVal, &timeZone);
16683     tm->sec = (long) timeVal.tv_sec;
16684     tm->ms = (int) (timeVal.tv_usec / 1000L);
16685
16686 #else /*!HAVE_GETTIMEOFDAY*/
16687 #if HAVE_FTIME
16688
16689 // include <sys/timeb.h> / moved to just above start of function
16690     struct timeb timeB;
16691
16692     ftime(&timeB);
16693     tm->sec = (long) timeB.time;
16694     tm->ms = (int) timeB.millitm;
16695
16696 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
16697     tm->sec = (long) time(NULL);
16698     tm->ms = 0;
16699 #endif
16700 #endif
16701 }
16702
16703 /* Return the difference in milliseconds between two
16704    time marks.  We assume the difference will fit in a long!
16705 */
16706 long
16707 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
16708 {
16709     return 1000L*(tm2->sec - tm1->sec) +
16710            (long) (tm2->ms - tm1->ms);
16711 }
16712
16713
16714 /*
16715  * Code to manage the game clocks.
16716  *
16717  * In tournament play, black starts the clock and then white makes a move.
16718  * We give the human user a slight advantage if he is playing white---the
16719  * clocks don't run until he makes his first move, so it takes zero time.
16720  * Also, we don't account for network lag, so we could get out of sync
16721  * with GNU Chess's clock -- but then, referees are always right.
16722  */
16723
16724 static TimeMark tickStartTM;
16725 static long intendedTickLength;
16726
16727 long
16728 NextTickLength (long timeRemaining)
16729 {
16730     long nominalTickLength, nextTickLength;
16731
16732     if (timeRemaining > 0L && timeRemaining <= 10000L)
16733       nominalTickLength = 100L;
16734     else
16735       nominalTickLength = 1000L;
16736     nextTickLength = timeRemaining % nominalTickLength;
16737     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
16738
16739     return nextTickLength;
16740 }
16741
16742 /* Adjust clock one minute up or down */
16743 void
16744 AdjustClock (Boolean which, int dir)
16745 {
16746     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
16747     if(which) blackTimeRemaining += 60000*dir;
16748     else      whiteTimeRemaining += 60000*dir;
16749     DisplayBothClocks();
16750     adjustedClock = TRUE;
16751 }
16752
16753 /* Stop clocks and reset to a fresh time control */
16754 void
16755 ResetClocks ()
16756 {
16757     (void) StopClockTimer();
16758     if (appData.icsActive) {
16759         whiteTimeRemaining = blackTimeRemaining = 0;
16760     } else if (searchTime) {
16761         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16762         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16763     } else { /* [HGM] correct new time quote for time odds */
16764         whiteTC = blackTC = fullTimeControlString;
16765         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
16766         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
16767     }
16768     if (whiteFlag || blackFlag) {
16769         DisplayTitle("");
16770         whiteFlag = blackFlag = FALSE;
16771     }
16772     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
16773     DisplayBothClocks();
16774     adjustedClock = FALSE;
16775 }
16776
16777 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
16778
16779 /* Decrement running clock by amount of time that has passed */
16780 void
16781 DecrementClocks ()
16782 {
16783     long timeRemaining;
16784     long lastTickLength, fudge;
16785     TimeMark now;
16786
16787     if (!appData.clockMode) return;
16788     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
16789
16790     GetTimeMark(&now);
16791
16792     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16793
16794     /* Fudge if we woke up a little too soon */
16795     fudge = intendedTickLength - lastTickLength;
16796     if (fudge < 0 || fudge > FUDGE) fudge = 0;
16797
16798     if (WhiteOnMove(forwardMostMove)) {
16799         if(whiteNPS >= 0) lastTickLength = 0;
16800         timeRemaining = whiteTimeRemaining -= lastTickLength;
16801         if(timeRemaining < 0 && !appData.icsActive) {
16802             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
16803             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
16804                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
16805                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
16806             }
16807         }
16808         DisplayWhiteClock(whiteTimeRemaining - fudge,
16809                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16810     } else {
16811         if(blackNPS >= 0) lastTickLength = 0;
16812         timeRemaining = blackTimeRemaining -= lastTickLength;
16813         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
16814             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
16815             if(suddenDeath) {
16816                 blackStartMove = forwardMostMove;
16817                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
16818             }
16819         }
16820         DisplayBlackClock(blackTimeRemaining - fudge,
16821                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16822     }
16823     if (CheckFlags()) return;
16824
16825     if(twoBoards) { // count down secondary board's clocks as well
16826         activePartnerTime -= lastTickLength;
16827         partnerUp = 1;
16828         if(activePartner == 'W')
16829             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
16830         else
16831             DisplayBlackClock(activePartnerTime, TRUE);
16832         partnerUp = 0;
16833     }
16834
16835     tickStartTM = now;
16836     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
16837     StartClockTimer(intendedTickLength);
16838
16839     /* if the time remaining has fallen below the alarm threshold, sound the
16840      * alarm. if the alarm has sounded and (due to a takeback or time control
16841      * with increment) the time remaining has increased to a level above the
16842      * threshold, reset the alarm so it can sound again.
16843      */
16844
16845     if (appData.icsActive && appData.icsAlarm) {
16846
16847         /* make sure we are dealing with the user's clock */
16848         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
16849                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
16850            )) return;
16851
16852         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
16853             alarmSounded = FALSE;
16854         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
16855             PlayAlarmSound();
16856             alarmSounded = TRUE;
16857         }
16858     }
16859 }
16860
16861
16862 /* A player has just moved, so stop the previously running
16863    clock and (if in clock mode) start the other one.
16864    We redisplay both clocks in case we're in ICS mode, because
16865    ICS gives us an update to both clocks after every move.
16866    Note that this routine is called *after* forwardMostMove
16867    is updated, so the last fractional tick must be subtracted
16868    from the color that is *not* on move now.
16869 */
16870 void
16871 SwitchClocks (int newMoveNr)
16872 {
16873     long lastTickLength;
16874     TimeMark now;
16875     int flagged = FALSE;
16876
16877     GetTimeMark(&now);
16878
16879     if (StopClockTimer() && appData.clockMode) {
16880         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16881         if (!WhiteOnMove(forwardMostMove)) {
16882             if(blackNPS >= 0) lastTickLength = 0;
16883             blackTimeRemaining -= lastTickLength;
16884            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16885 //         if(pvInfoList[forwardMostMove].time == -1)
16886                  pvInfoList[forwardMostMove].time =               // use GUI time
16887                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
16888         } else {
16889            if(whiteNPS >= 0) lastTickLength = 0;
16890            whiteTimeRemaining -= lastTickLength;
16891            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16892 //         if(pvInfoList[forwardMostMove].time == -1)
16893                  pvInfoList[forwardMostMove].time =
16894                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
16895         }
16896         flagged = CheckFlags();
16897     }
16898     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
16899     CheckTimeControl();
16900
16901     if (flagged || !appData.clockMode) return;
16902
16903     switch (gameMode) {
16904       case MachinePlaysBlack:
16905       case MachinePlaysWhite:
16906       case BeginningOfGame:
16907         if (pausing) return;
16908         break;
16909
16910       case EditGame:
16911       case PlayFromGameFile:
16912       case IcsExamining:
16913         return;
16914
16915       default:
16916         break;
16917     }
16918
16919     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
16920         if(WhiteOnMove(forwardMostMove))
16921              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16922         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16923     }
16924
16925     tickStartTM = now;
16926     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16927       whiteTimeRemaining : blackTimeRemaining);
16928     StartClockTimer(intendedTickLength);
16929 }
16930
16931
16932 /* Stop both clocks */
16933 void
16934 StopClocks ()
16935 {
16936     long lastTickLength;
16937     TimeMark now;
16938
16939     if (!StopClockTimer()) return;
16940     if (!appData.clockMode) return;
16941
16942     GetTimeMark(&now);
16943
16944     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16945     if (WhiteOnMove(forwardMostMove)) {
16946         if(whiteNPS >= 0) lastTickLength = 0;
16947         whiteTimeRemaining -= lastTickLength;
16948         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
16949     } else {
16950         if(blackNPS >= 0) lastTickLength = 0;
16951         blackTimeRemaining -= lastTickLength;
16952         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
16953     }
16954     CheckFlags();
16955 }
16956
16957 /* Start clock of player on move.  Time may have been reset, so
16958    if clock is already running, stop and restart it. */
16959 void
16960 StartClocks ()
16961 {
16962     (void) StopClockTimer(); /* in case it was running already */
16963     DisplayBothClocks();
16964     if (CheckFlags()) return;
16965
16966     if (!appData.clockMode) return;
16967     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
16968
16969     GetTimeMark(&tickStartTM);
16970     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16971       whiteTimeRemaining : blackTimeRemaining);
16972
16973    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
16974     whiteNPS = blackNPS = -1;
16975     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
16976        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
16977         whiteNPS = first.nps;
16978     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
16979        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
16980         blackNPS = first.nps;
16981     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
16982         whiteNPS = second.nps;
16983     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
16984         blackNPS = second.nps;
16985     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
16986
16987     StartClockTimer(intendedTickLength);
16988 }
16989
16990 char *
16991 TimeString (long ms)
16992 {
16993     long second, minute, hour, day;
16994     char *sign = "";
16995     static char buf[32];
16996
16997     if (ms > 0 && ms <= 9900) {
16998       /* convert milliseconds to tenths, rounding up */
16999       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
17000
17001       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
17002       return buf;
17003     }
17004
17005     /* convert milliseconds to seconds, rounding up */
17006     /* use floating point to avoid strangeness of integer division
17007        with negative dividends on many machines */
17008     second = (long) floor(((double) (ms + 999L)) / 1000.0);
17009
17010     if (second < 0) {
17011         sign = "-";
17012         second = -second;
17013     }
17014
17015     day = second / (60 * 60 * 24);
17016     second = second % (60 * 60 * 24);
17017     hour = second / (60 * 60);
17018     second = second % (60 * 60);
17019     minute = second / 60;
17020     second = second % 60;
17021
17022     if (day > 0)
17023       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
17024               sign, day, hour, minute, second);
17025     else if (hour > 0)
17026       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
17027     else
17028       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
17029
17030     return buf;
17031 }
17032
17033
17034 /*
17035  * This is necessary because some C libraries aren't ANSI C compliant yet.
17036  */
17037 char *
17038 StrStr (char *string, char *match)
17039 {
17040     int i, length;
17041
17042     length = strlen(match);
17043
17044     for (i = strlen(string) - length; i >= 0; i--, string++)
17045       if (!strncmp(match, string, length))
17046         return string;
17047
17048     return NULL;
17049 }
17050
17051 char *
17052 StrCaseStr (char *string, char *match)
17053 {
17054     int i, j, length;
17055
17056     length = strlen(match);
17057
17058     for (i = strlen(string) - length; i >= 0; i--, string++) {
17059         for (j = 0; j < length; j++) {
17060             if (ToLower(match[j]) != ToLower(string[j]))
17061               break;
17062         }
17063         if (j == length) return string;
17064     }
17065
17066     return NULL;
17067 }
17068
17069 #ifndef _amigados
17070 int
17071 StrCaseCmp (char *s1, char *s2)
17072 {
17073     char c1, c2;
17074
17075     for (;;) {
17076         c1 = ToLower(*s1++);
17077         c2 = ToLower(*s2++);
17078         if (c1 > c2) return 1;
17079         if (c1 < c2) return -1;
17080         if (c1 == NULLCHAR) return 0;
17081     }
17082 }
17083
17084
17085 int
17086 ToLower (int c)
17087 {
17088     return isupper(c) ? tolower(c) : c;
17089 }
17090
17091
17092 int
17093 ToUpper (int c)
17094 {
17095     return islower(c) ? toupper(c) : c;
17096 }
17097 #endif /* !_amigados    */
17098
17099 char *
17100 StrSave (char *s)
17101 {
17102   char *ret;
17103
17104   if ((ret = (char *) malloc(strlen(s) + 1)))
17105     {
17106       safeStrCpy(ret, s, strlen(s)+1);
17107     }
17108   return ret;
17109 }
17110
17111 char *
17112 StrSavePtr (char *s, char **savePtr)
17113 {
17114     if (*savePtr) {
17115         free(*savePtr);
17116     }
17117     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
17118       safeStrCpy(*savePtr, s, strlen(s)+1);
17119     }
17120     return(*savePtr);
17121 }
17122
17123 char *
17124 PGNDate ()
17125 {
17126     time_t clock;
17127     struct tm *tm;
17128     char buf[MSG_SIZ];
17129
17130     clock = time((time_t *)NULL);
17131     tm = localtime(&clock);
17132     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
17133             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
17134     return StrSave(buf);
17135 }
17136
17137
17138 char *
17139 PositionToFEN (int move, char *overrideCastling, int moveCounts)
17140 {
17141     int i, j, fromX, fromY, toX, toY;
17142     int whiteToPlay;
17143     char buf[MSG_SIZ];
17144     char *p, *q;
17145     int emptycount;
17146     ChessSquare piece;
17147
17148     whiteToPlay = (gameMode == EditPosition) ?
17149       !blackPlaysFirst : (move % 2 == 0);
17150     p = buf;
17151
17152     /* Piece placement data */
17153     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17154         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
17155         emptycount = 0;
17156         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
17157             if (boards[move][i][j] == EmptySquare) {
17158                 emptycount++;
17159             } else { ChessSquare piece = boards[move][i][j];
17160                 if (emptycount > 0) {
17161                     if(emptycount<10) /* [HGM] can be >= 10 */
17162                         *p++ = '0' + emptycount;
17163                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17164                     emptycount = 0;
17165                 }
17166                 if(PieceToChar(piece) == '+') {
17167                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
17168                     *p++ = '+';
17169                     piece = (ChessSquare)(DEMOTED piece);
17170                 }
17171                 *p++ = PieceToChar(piece);
17172                 if(p[-1] == '~') {
17173                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
17174                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
17175                     *p++ = '~';
17176                 }
17177             }
17178         }
17179         if (emptycount > 0) {
17180             if(emptycount<10) /* [HGM] can be >= 10 */
17181                 *p++ = '0' + emptycount;
17182             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17183             emptycount = 0;
17184         }
17185         *p++ = '/';
17186     }
17187     *(p - 1) = ' ';
17188
17189     /* [HGM] print Crazyhouse or Shogi holdings */
17190     if( gameInfo.holdingsWidth ) {
17191         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
17192         q = p;
17193         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
17194             piece = boards[move][i][BOARD_WIDTH-1];
17195             if( piece != EmptySquare )
17196               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
17197                   *p++ = PieceToChar(piece);
17198         }
17199         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
17200             piece = boards[move][BOARD_HEIGHT-i-1][0];
17201             if( piece != EmptySquare )
17202               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
17203                   *p++ = PieceToChar(piece);
17204         }
17205
17206         if( q == p ) *p++ = '-';
17207         *p++ = ']';
17208         *p++ = ' ';
17209     }
17210
17211     /* Active color */
17212     *p++ = whiteToPlay ? 'w' : 'b';
17213     *p++ = ' ';
17214
17215   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
17216     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
17217   } else {
17218   if(nrCastlingRights) {
17219      q = p;
17220      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
17221        /* [HGM] write directly from rights */
17222            if(boards[move][CASTLING][2] != NoRights &&
17223               boards[move][CASTLING][0] != NoRights   )
17224                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
17225            if(boards[move][CASTLING][2] != NoRights &&
17226               boards[move][CASTLING][1] != NoRights   )
17227                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
17228            if(boards[move][CASTLING][5] != NoRights &&
17229               boards[move][CASTLING][3] != NoRights   )
17230                 *p++ = boards[move][CASTLING][3] + AAA;
17231            if(boards[move][CASTLING][5] != NoRights &&
17232               boards[move][CASTLING][4] != NoRights   )
17233                 *p++ = boards[move][CASTLING][4] + AAA;
17234      } else {
17235
17236         /* [HGM] write true castling rights */
17237         if( nrCastlingRights == 6 ) {
17238             int q, k=0;
17239             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
17240                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
17241             q = (boards[move][CASTLING][1] == BOARD_LEFT &&
17242                  boards[move][CASTLING][2] != NoRights  );
17243             if(gameInfo.variant == VariantSChess) { // for S-Chess, indicate all vrgin backrank pieces
17244                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_RGHT]; // count white held pieces
17245                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17246                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
17247                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
17248             }
17249             if(q) *p++ = 'Q';
17250             k = 0;
17251             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
17252                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
17253             q = (boards[move][CASTLING][4] == BOARD_LEFT &&
17254                  boards[move][CASTLING][5] != NoRights  );
17255             if(gameInfo.variant == VariantSChess) {
17256                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_LEFT-1]; // count black held pieces
17257                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17258                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
17259                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17260             }
17261             if(q) *p++ = 'q';
17262         }
17263      }
17264      if (q == p) *p++ = '-'; /* No castling rights */
17265      *p++ = ' ';
17266   }
17267
17268   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17269      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17270      gameInfo.variant != VariantMakruk   && gameInfo.variant != VariantASEAN ) {
17271     /* En passant target square */
17272     if (move > backwardMostMove) {
17273         fromX = moveList[move - 1][0] - AAA;
17274         fromY = moveList[move - 1][1] - ONE;
17275         toX = moveList[move - 1][2] - AAA;
17276         toY = moveList[move - 1][3] - ONE;
17277         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
17278             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
17279             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
17280             fromX == toX) {
17281             /* 2-square pawn move just happened */
17282             *p++ = toX + AAA;
17283             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17284         } else {
17285             *p++ = '-';
17286         }
17287     } else if(move == backwardMostMove) {
17288         // [HGM] perhaps we should always do it like this, and forget the above?
17289         if((signed char)boards[move][EP_STATUS] >= 0) {
17290             *p++ = boards[move][EP_STATUS] + AAA;
17291             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17292         } else {
17293             *p++ = '-';
17294         }
17295     } else {
17296         *p++ = '-';
17297     }
17298     *p++ = ' ';
17299   }
17300   }
17301
17302     if(moveCounts)
17303     {   int i = 0, j=move;
17304
17305         /* [HGM] find reversible plies */
17306         if (appData.debugMode) { int k;
17307             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
17308             for(k=backwardMostMove; k<=forwardMostMove; k++)
17309                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
17310
17311         }
17312
17313         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
17314         if( j == backwardMostMove ) i += initialRulePlies;
17315         sprintf(p, "%d ", i);
17316         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
17317
17318         /* Fullmove number */
17319         sprintf(p, "%d", (move / 2) + 1);
17320     } else *--p = NULLCHAR;
17321
17322     return StrSave(buf);
17323 }
17324
17325 Boolean
17326 ParseFEN (Board board, int *blackPlaysFirst, char *fen)
17327 {
17328     int i, j;
17329     char *p, c;
17330     int emptycount, virgin[BOARD_FILES];
17331     ChessSquare piece;
17332
17333     p = fen;
17334
17335     /* [HGM] by default clear Crazyhouse holdings, if present */
17336     if(gameInfo.holdingsWidth) {
17337        for(i=0; i<BOARD_HEIGHT; i++) {
17338            board[i][0]             = EmptySquare; /* black holdings */
17339            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
17340            board[i][1]             = (ChessSquare) 0; /* black counts */
17341            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
17342        }
17343     }
17344
17345     /* Piece placement data */
17346     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17347         j = 0;
17348         for (;;) {
17349             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
17350                 if (*p == '/') p++;
17351                 emptycount = gameInfo.boardWidth - j;
17352                 while (emptycount--)
17353                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17354                 break;
17355 #if(BOARD_FILES >= 10)
17356             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
17357                 p++; emptycount=10;
17358                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17359                 while (emptycount--)
17360                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17361 #endif
17362             } else if (isdigit(*p)) {
17363                 emptycount = *p++ - '0';
17364                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
17365                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17366                 while (emptycount--)
17367                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17368             } else if (*p == '+' || isalpha(*p)) {
17369                 if (j >= gameInfo.boardWidth) return FALSE;
17370                 if(*p=='+') {
17371                     piece = CharToPiece(*++p);
17372                     if(piece == EmptySquare) return FALSE; /* unknown piece */
17373                     piece = (ChessSquare) (PROMOTED piece ); p++;
17374                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
17375                 } else piece = CharToPiece(*p++);
17376
17377                 if(piece==EmptySquare) return FALSE; /* unknown piece */
17378                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
17379                     piece = (ChessSquare) (PROMOTED piece);
17380                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
17381                     p++;
17382                 }
17383                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
17384             } else {
17385                 return FALSE;
17386             }
17387         }
17388     }
17389     while (*p == '/' || *p == ' ') p++;
17390
17391     /* [HGM] look for Crazyhouse holdings here */
17392     while(*p==' ') p++;
17393     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
17394         if(*p == '[') p++;
17395         if(*p == '-' ) p++; /* empty holdings */ else {
17396             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
17397             /* if we would allow FEN reading to set board size, we would   */
17398             /* have to add holdings and shift the board read so far here   */
17399             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
17400                 p++;
17401                 if((int) piece >= (int) BlackPawn ) {
17402                     i = (int)piece - (int)BlackPawn;
17403                     i = PieceToNumber((ChessSquare)i);
17404                     if( i >= gameInfo.holdingsSize ) return FALSE;
17405                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
17406                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
17407                 } else {
17408                     i = (int)piece - (int)WhitePawn;
17409                     i = PieceToNumber((ChessSquare)i);
17410                     if( i >= gameInfo.holdingsSize ) return FALSE;
17411                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
17412                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
17413                 }
17414             }
17415         }
17416         if(*p == ']') p++;
17417     }
17418
17419     while(*p == ' ') p++;
17420
17421     /* Active color */
17422     c = *p++;
17423     if(appData.colorNickNames) {
17424       if( c == appData.colorNickNames[0] ) c = 'w'; else
17425       if( c == appData.colorNickNames[1] ) c = 'b';
17426     }
17427     switch (c) {
17428       case 'w':
17429         *blackPlaysFirst = FALSE;
17430         break;
17431       case 'b':
17432         *blackPlaysFirst = TRUE;
17433         break;
17434       default:
17435         return FALSE;
17436     }
17437
17438     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
17439     /* return the extra info in global variiables             */
17440
17441     /* set defaults in case FEN is incomplete */
17442     board[EP_STATUS] = EP_UNKNOWN;
17443     for(i=0; i<nrCastlingRights; i++ ) {
17444         board[CASTLING][i] =
17445             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
17446     }   /* assume possible unless obviously impossible */
17447     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
17448     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
17449     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
17450                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
17451     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
17452     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
17453     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
17454                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
17455     FENrulePlies = 0;
17456
17457     while(*p==' ') p++;
17458     if(nrCastlingRights) {
17459       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
17460       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
17461           /* castling indicator present, so default becomes no castlings */
17462           for(i=0; i<nrCastlingRights; i++ ) {
17463                  board[CASTLING][i] = NoRights;
17464           }
17465       }
17466       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
17467              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess) &&
17468              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
17469              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
17470         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
17471
17472         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
17473             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
17474             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
17475         }
17476         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
17477             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
17478         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
17479                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
17480         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
17481                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
17482         switch(c) {
17483           case'K':
17484               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
17485               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
17486               board[CASTLING][2] = whiteKingFile;
17487               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
17488               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17489               break;
17490           case'Q':
17491               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
17492               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
17493               board[CASTLING][2] = whiteKingFile;
17494               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
17495               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17496               break;
17497           case'k':
17498               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
17499               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
17500               board[CASTLING][5] = blackKingFile;
17501               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
17502               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17503               break;
17504           case'q':
17505               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
17506               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
17507               board[CASTLING][5] = blackKingFile;
17508               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
17509               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17510           case '-':
17511               break;
17512           default: /* FRC castlings */
17513               if(c >= 'a') { /* black rights */
17514                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
17515                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17516                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
17517                   if(i == BOARD_RGHT) break;
17518                   board[CASTLING][5] = i;
17519                   c -= AAA;
17520                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
17521                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
17522                   if(c > i)
17523                       board[CASTLING][3] = c;
17524                   else
17525                       board[CASTLING][4] = c;
17526               } else { /* white rights */
17527                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
17528                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17529                     if(board[0][i] == WhiteKing) break;
17530                   if(i == BOARD_RGHT) break;
17531                   board[CASTLING][2] = i;
17532                   c -= AAA - 'a' + 'A';
17533                   if(board[0][c] >= WhiteKing) break;
17534                   if(c > i)
17535                       board[CASTLING][0] = c;
17536                   else
17537                       board[CASTLING][1] = c;
17538               }
17539         }
17540       }
17541       for(i=0; i<nrCastlingRights; i++)
17542         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
17543       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = virgin[i];
17544     if (appData.debugMode) {
17545         fprintf(debugFP, "FEN castling rights:");
17546         for(i=0; i<nrCastlingRights; i++)
17547         fprintf(debugFP, " %d", board[CASTLING][i]);
17548         fprintf(debugFP, "\n");
17549     }
17550
17551       while(*p==' ') p++;
17552     }
17553
17554     /* read e.p. field in games that know e.p. capture */
17555     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17556        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17557        gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
17558       if(*p=='-') {
17559         p++; board[EP_STATUS] = EP_NONE;
17560       } else {
17561          char c = *p++ - AAA;
17562
17563          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
17564          if(*p >= '0' && *p <='9') p++;
17565          board[EP_STATUS] = c;
17566       }
17567     }
17568
17569
17570     if(sscanf(p, "%d", &i) == 1) {
17571         FENrulePlies = i; /* 50-move ply counter */
17572         /* (The move number is still ignored)    */
17573     }
17574
17575     return TRUE;
17576 }
17577
17578 void
17579 EditPositionPasteFEN (char *fen)
17580 {
17581   if (fen != NULL) {
17582     Board initial_position;
17583
17584     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
17585       DisplayError(_("Bad FEN position in clipboard"), 0);
17586       return ;
17587     } else {
17588       int savedBlackPlaysFirst = blackPlaysFirst;
17589       EditPositionEvent();
17590       blackPlaysFirst = savedBlackPlaysFirst;
17591       CopyBoard(boards[0], initial_position);
17592       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
17593       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
17594       DisplayBothClocks();
17595       DrawPosition(FALSE, boards[currentMove]);
17596     }
17597   }
17598 }
17599
17600 static char cseq[12] = "\\   ";
17601
17602 Boolean
17603 set_cont_sequence (char *new_seq)
17604 {
17605     int len;
17606     Boolean ret;
17607
17608     // handle bad attempts to set the sequence
17609         if (!new_seq)
17610                 return 0; // acceptable error - no debug
17611
17612     len = strlen(new_seq);
17613     ret = (len > 0) && (len < sizeof(cseq));
17614     if (ret)
17615       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
17616     else if (appData.debugMode)
17617       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
17618     return ret;
17619 }
17620
17621 /*
17622     reformat a source message so words don't cross the width boundary.  internal
17623     newlines are not removed.  returns the wrapped size (no null character unless
17624     included in source message).  If dest is NULL, only calculate the size required
17625     for the dest buffer.  lp argument indicats line position upon entry, and it's
17626     passed back upon exit.
17627 */
17628 int
17629 wrap (char *dest, char *src, int count, int width, int *lp)
17630 {
17631     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
17632
17633     cseq_len = strlen(cseq);
17634     old_line = line = *lp;
17635     ansi = len = clen = 0;
17636
17637     for (i=0; i < count; i++)
17638     {
17639         if (src[i] == '\033')
17640             ansi = 1;
17641
17642         // if we hit the width, back up
17643         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
17644         {
17645             // store i & len in case the word is too long
17646             old_i = i, old_len = len;
17647
17648             // find the end of the last word
17649             while (i && src[i] != ' ' && src[i] != '\n')
17650             {
17651                 i--;
17652                 len--;
17653             }
17654
17655             // word too long?  restore i & len before splitting it
17656             if ((old_i-i+clen) >= width)
17657             {
17658                 i = old_i;
17659                 len = old_len;
17660             }
17661
17662             // extra space?
17663             if (i && src[i-1] == ' ')
17664                 len--;
17665
17666             if (src[i] != ' ' && src[i] != '\n')
17667             {
17668                 i--;
17669                 if (len)
17670                     len--;
17671             }
17672
17673             // now append the newline and continuation sequence
17674             if (dest)
17675                 dest[len] = '\n';
17676             len++;
17677             if (dest)
17678                 strncpy(dest+len, cseq, cseq_len);
17679             len += cseq_len;
17680             line = cseq_len;
17681             clen = cseq_len;
17682             continue;
17683         }
17684
17685         if (dest)
17686             dest[len] = src[i];
17687         len++;
17688         if (!ansi)
17689             line++;
17690         if (src[i] == '\n')
17691             line = 0;
17692         if (src[i] == 'm')
17693             ansi = 0;
17694     }
17695     if (dest && appData.debugMode)
17696     {
17697         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
17698             count, width, line, len, *lp);
17699         show_bytes(debugFP, src, count);
17700         fprintf(debugFP, "\ndest: ");
17701         show_bytes(debugFP, dest, len);
17702         fprintf(debugFP, "\n");
17703     }
17704     *lp = dest ? line : old_line;
17705
17706     return len;
17707 }
17708
17709 // [HGM] vari: routines for shelving variations
17710 Boolean modeRestore = FALSE;
17711
17712 void
17713 PushInner (int firstMove, int lastMove)
17714 {
17715         int i, j, nrMoves = lastMove - firstMove;
17716
17717         // push current tail of game on stack
17718         savedResult[storedGames] = gameInfo.result;
17719         savedDetails[storedGames] = gameInfo.resultDetails;
17720         gameInfo.resultDetails = NULL;
17721         savedFirst[storedGames] = firstMove;
17722         savedLast [storedGames] = lastMove;
17723         savedFramePtr[storedGames] = framePtr;
17724         framePtr -= nrMoves; // reserve space for the boards
17725         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
17726             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
17727             for(j=0; j<MOVE_LEN; j++)
17728                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
17729             for(j=0; j<2*MOVE_LEN; j++)
17730                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
17731             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
17732             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
17733             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
17734             pvInfoList[firstMove+i-1].depth = 0;
17735             commentList[framePtr+i] = commentList[firstMove+i];
17736             commentList[firstMove+i] = NULL;
17737         }
17738
17739         storedGames++;
17740         forwardMostMove = firstMove; // truncate game so we can start variation
17741 }
17742
17743 void
17744 PushTail (int firstMove, int lastMove)
17745 {
17746         if(appData.icsActive) { // only in local mode
17747                 forwardMostMove = currentMove; // mimic old ICS behavior
17748                 return;
17749         }
17750         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
17751
17752         PushInner(firstMove, lastMove);
17753         if(storedGames == 1) GreyRevert(FALSE);
17754         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
17755 }
17756
17757 void
17758 PopInner (Boolean annotate)
17759 {
17760         int i, j, nrMoves;
17761         char buf[8000], moveBuf[20];
17762
17763         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
17764         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
17765         nrMoves = savedLast[storedGames] - currentMove;
17766         if(annotate) {
17767                 int cnt = 10;
17768                 if(!WhiteOnMove(currentMove))
17769                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
17770                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
17771                 for(i=currentMove; i<forwardMostMove; i++) {
17772                         if(WhiteOnMove(i))
17773                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
17774                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
17775                         strcat(buf, moveBuf);
17776                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
17777                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
17778                 }
17779                 strcat(buf, ")");
17780         }
17781         for(i=1; i<=nrMoves; i++) { // copy last variation back
17782             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
17783             for(j=0; j<MOVE_LEN; j++)
17784                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
17785             for(j=0; j<2*MOVE_LEN; j++)
17786                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
17787             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
17788             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
17789             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
17790             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
17791             commentList[currentMove+i] = commentList[framePtr+i];
17792             commentList[framePtr+i] = NULL;
17793         }
17794         if(annotate) AppendComment(currentMove+1, buf, FALSE);
17795         framePtr = savedFramePtr[storedGames];
17796         gameInfo.result = savedResult[storedGames];
17797         if(gameInfo.resultDetails != NULL) {
17798             free(gameInfo.resultDetails);
17799       }
17800         gameInfo.resultDetails = savedDetails[storedGames];
17801         forwardMostMove = currentMove + nrMoves;
17802 }
17803
17804 Boolean
17805 PopTail (Boolean annotate)
17806 {
17807         if(appData.icsActive) return FALSE; // only in local mode
17808         if(!storedGames) return FALSE; // sanity
17809         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
17810
17811         PopInner(annotate);
17812         if(currentMove < forwardMostMove) ForwardEvent(); else
17813         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
17814
17815         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
17816         return TRUE;
17817 }
17818
17819 void
17820 CleanupTail ()
17821 {       // remove all shelved variations
17822         int i;
17823         for(i=0; i<storedGames; i++) {
17824             if(savedDetails[i])
17825                 free(savedDetails[i]);
17826             savedDetails[i] = NULL;
17827         }
17828         for(i=framePtr; i<MAX_MOVES; i++) {
17829                 if(commentList[i]) free(commentList[i]);
17830                 commentList[i] = NULL;
17831         }
17832         framePtr = MAX_MOVES-1;
17833         storedGames = 0;
17834 }
17835
17836 void
17837 LoadVariation (int index, char *text)
17838 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
17839         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
17840         int level = 0, move;
17841
17842         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
17843         // first find outermost bracketing variation
17844         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
17845             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
17846                 if(*p == '{') wait = '}'; else
17847                 if(*p == '[') wait = ']'; else
17848                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
17849                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
17850             }
17851             if(*p == wait) wait = NULLCHAR; // closing ]} found
17852             p++;
17853         }
17854         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
17855         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
17856         end[1] = NULLCHAR; // clip off comment beyond variation
17857         ToNrEvent(currentMove-1);
17858         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
17859         // kludge: use ParsePV() to append variation to game
17860         move = currentMove;
17861         ParsePV(start, TRUE, TRUE);
17862         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
17863         ClearPremoveHighlights();
17864         CommentPopDown();
17865         ToNrEvent(currentMove+1);
17866 }
17867
17868 void
17869 LoadTheme ()
17870 {
17871     char *p, *q, buf[MSG_SIZ];
17872     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
17873         snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
17874         ParseArgsFromString(buf);
17875         ActivateTheme(TRUE); // also redo colors
17876         return;
17877     }
17878     p = nickName;
17879     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
17880     {
17881         int len;
17882         q = appData.themeNames;
17883         snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
17884       if(appData.useBitmaps) {
17885         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
17886                 appData.liteBackTextureFile, appData.darkBackTextureFile,
17887                 appData.liteBackTextureMode,
17888                 appData.darkBackTextureMode );
17889       } else {
17890         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
17891                 Col2Text(2),   // lightSquareColor
17892                 Col2Text(3) ); // darkSquareColor
17893       }
17894       if(appData.useBorder) {
17895         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
17896                 appData.border);
17897       } else {
17898         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
17899       }
17900       if(appData.useFont) {
17901         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
17902                 appData.renderPiecesWithFont,
17903                 appData.fontToPieceTable,
17904                 Col2Text(9),    // appData.fontBackColorWhite
17905                 Col2Text(10) ); // appData.fontForeColorBlack
17906       } else {
17907         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
17908                 appData.pieceDirectory);
17909         if(!appData.pieceDirectory[0])
17910           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
17911                 Col2Text(0),   // whitePieceColor
17912                 Col2Text(1) ); // blackPieceColor
17913       }
17914       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
17915                 Col2Text(4),   // highlightSquareColor
17916                 Col2Text(5) ); // premoveHighlightColor
17917         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
17918         if(insert != q) insert[-1] = NULLCHAR;
17919         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
17920         if(q)   free(q);
17921     }
17922     ActivateTheme(FALSE);
17923 }