a9b94ad996ca65550ec82c615176d7fc9b0781b6
[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((VariantClass v, int w, int h, int s));
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 static int initPing = -1;
279
280 /* States for ics_getting_history */
281 #define H_FALSE 0
282 #define H_REQUESTED 1
283 #define H_GOT_REQ_HEADER 2
284 #define H_GOT_UNREQ_HEADER 3
285 #define H_GETTING_MOVES 4
286 #define H_GOT_UNWANTED_HEADER 5
287
288 /* whosays values for GameEnds */
289 #define GE_ICS 0
290 #define GE_ENGINE 1
291 #define GE_PLAYER 2
292 #define GE_FILE 3
293 #define GE_XBOARD 4
294 #define GE_ENGINE1 5
295 #define GE_ENGINE2 6
296
297 /* Maximum number of games in a cmail message */
298 #define CMAIL_MAX_GAMES 20
299
300 /* Different types of move when calling RegisterMove */
301 #define CMAIL_MOVE   0
302 #define CMAIL_RESIGN 1
303 #define CMAIL_DRAW   2
304 #define CMAIL_ACCEPT 3
305
306 /* Different types of result to remember for each game */
307 #define CMAIL_NOT_RESULT 0
308 #define CMAIL_OLD_RESULT 1
309 #define CMAIL_NEW_RESULT 2
310
311 /* Telnet protocol constants */
312 #define TN_WILL 0373
313 #define TN_WONT 0374
314 #define TN_DO   0375
315 #define TN_DONT 0376
316 #define TN_IAC  0377
317 #define TN_ECHO 0001
318 #define TN_SGA  0003
319 #define TN_PORT 23
320
321 char*
322 safeStrCpy (char *dst, const char *src, size_t count)
323 { // [HGM] made safe
324   int i;
325   assert( dst != NULL );
326   assert( src != NULL );
327   assert( count > 0 );
328
329   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
330   if(  i == count && dst[count-1] != NULLCHAR)
331     {
332       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
333       if(appData.debugMode)
334         fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
335     }
336
337   return dst;
338 }
339
340 /* Some compiler can't cast u64 to double
341  * This function do the job for us:
342
343  * We use the highest bit for cast, this only
344  * works if the highest bit is not
345  * in use (This should not happen)
346  *
347  * We used this for all compiler
348  */
349 double
350 u64ToDouble (u64 value)
351 {
352   double r;
353   u64 tmp = value & u64Const(0x7fffffffffffffff);
354   r = (double)(s64)tmp;
355   if (value & u64Const(0x8000000000000000))
356        r +=  9.2233720368547758080e18; /* 2^63 */
357  return r;
358 }
359
360 /* Fake up flags for now, as we aren't keeping track of castling
361    availability yet. [HGM] Change of logic: the flag now only
362    indicates the type of castlings allowed by the rule of the game.
363    The actual rights themselves are maintained in the array
364    castlingRights, as part of the game history, and are not probed
365    by this function.
366  */
367 int
368 PosFlags (index)
369 {
370   int flags = F_ALL_CASTLE_OK;
371   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
372   switch (gameInfo.variant) {
373   case VariantSuicide:
374     flags &= ~F_ALL_CASTLE_OK;
375   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
376     flags |= F_IGNORE_CHECK;
377   case VariantLosers:
378     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
379     break;
380   case VariantAtomic:
381     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
382     break;
383   case VariantKriegspiel:
384     flags |= F_KRIEGSPIEL_CAPTURE;
385     break;
386   case VariantCapaRandom:
387   case VariantFischeRandom:
388     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
389   case VariantNoCastle:
390   case VariantShatranj:
391   case VariantCourier:
392   case VariantMakruk:
393   case VariantASEAN:
394   case VariantGrand:
395     flags &= ~F_ALL_CASTLE_OK;
396     break;
397   default:
398     break;
399   }
400   return flags;
401 }
402
403 FILE *gameFileFP, *debugFP, *serverFP;
404 char *currentDebugFile; // [HGM] debug split: to remember name
405
406 /*
407     [AS] Note: sometimes, the sscanf() function is used to parse the input
408     into a fixed-size buffer. Because of this, we must be prepared to
409     receive strings as long as the size of the input buffer, which is currently
410     set to 4K for Windows and 8K for the rest.
411     So, we must either allocate sufficiently large buffers here, or
412     reduce the size of the input buffer in the input reading part.
413 */
414
415 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
416 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
417 char thinkOutput1[MSG_SIZ*10];
418
419 ChessProgramState first, second, pairing;
420
421 /* premove variables */
422 int premoveToX = 0;
423 int premoveToY = 0;
424 int premoveFromX = 0;
425 int premoveFromY = 0;
426 int premovePromoChar = 0;
427 int gotPremove = 0;
428 Boolean alarmSounded;
429 /* end premove variables */
430
431 char *ics_prefix = "$";
432 enum ICS_TYPE ics_type = ICS_GENERIC;
433
434 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
435 int pauseExamForwardMostMove = 0;
436 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
437 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
438 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
439 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
440 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
441 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
442 int whiteFlag = FALSE, blackFlag = FALSE;
443 int userOfferedDraw = FALSE;
444 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
445 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
446 int cmailMoveType[CMAIL_MAX_GAMES];
447 long ics_clock_paused = 0;
448 ProcRef icsPR = NoProc, cmailPR = NoProc;
449 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
450 GameMode gameMode = BeginningOfGame;
451 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
452 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
453 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
454 int hiddenThinkOutputState = 0; /* [AS] */
455 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
456 int adjudicateLossPlies = 6;
457 char white_holding[64], black_holding[64];
458 TimeMark lastNodeCountTime;
459 long lastNodeCount=0;
460 int shiftKey, controlKey; // [HGM] set by mouse handler
461
462 int have_sent_ICS_logon = 0;
463 int movesPerSession;
464 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
465 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack, activePartnerTime;
466 Boolean adjustedClock;
467 long timeControl_2; /* [AS] Allow separate time controls */
468 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC, activePartner; /* [HGM] secondary TC: merge of MPS, TC and inc */
469 long timeRemaining[2][MAX_MOVES];
470 int matchGame = 0, nextGame = 0, roundNr = 0;
471 Boolean waitingForGame = FALSE, startingEngine = FALSE;
472 TimeMark programStartTime, pauseStart;
473 char ics_handle[MSG_SIZ];
474 int have_set_title = 0;
475
476 /* animateTraining preserves the state of appData.animate
477  * when Training mode is activated. This allows the
478  * response to be animated when appData.animate == TRUE and
479  * appData.animateDragging == TRUE.
480  */
481 Boolean animateTraining;
482
483 GameInfo gameInfo;
484
485 AppData appData;
486
487 Board boards[MAX_MOVES];
488 /* [HGM] Following 7 needed for accurate legality tests: */
489 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
490 signed char  initialRights[BOARD_FILES];
491 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
492 int   initialRulePlies, FENrulePlies;
493 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
494 int loadFlag = 0;
495 Boolean shuffleOpenings;
496 int mute; // mute all sounds
497
498 // [HGM] vari: next 12 to save and restore variations
499 #define MAX_VARIATIONS 10
500 int framePtr = MAX_MOVES-1; // points to free stack entry
501 int storedGames = 0;
502 int savedFirst[MAX_VARIATIONS];
503 int savedLast[MAX_VARIATIONS];
504 int savedFramePtr[MAX_VARIATIONS];
505 char *savedDetails[MAX_VARIATIONS];
506 ChessMove savedResult[MAX_VARIATIONS];
507
508 void PushTail P((int firstMove, int lastMove));
509 Boolean PopTail P((Boolean annotate));
510 void PushInner P((int firstMove, int lastMove));
511 void PopInner P((Boolean annotate));
512 void CleanupTail P((void));
513
514 ChessSquare  FIDEArray[2][BOARD_FILES] = {
515     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
516         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
517     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
518         BlackKing, BlackBishop, BlackKnight, BlackRook }
519 };
520
521 ChessSquare twoKingsArray[2][BOARD_FILES] = {
522     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
523         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
524     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
525         BlackKing, BlackKing, BlackKnight, BlackRook }
526 };
527
528 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
529     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
530         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
531     { BlackRook, BlackMan, BlackBishop, BlackQueen,
532         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
533 };
534
535 ChessSquare SpartanArray[2][BOARD_FILES] = {
536     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
537         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
538     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
539         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
540 };
541
542 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
543     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
544         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
545     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
546         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
547 };
548
549 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
550     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
551         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
552     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
553         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
554 };
555
556 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
557     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
558         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
559     { BlackRook, BlackKnight, BlackMan, BlackFerz,
560         BlackKing, BlackMan, BlackKnight, BlackRook }
561 };
562
563 ChessSquare aseanArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
564     { WhiteRook, WhiteKnight, WhiteMan, WhiteFerz,
565         WhiteKing, WhiteMan, WhiteKnight, WhiteRook },
566     { BlackRook, BlackKnight, BlackMan, BlackFerz,
567         BlackKing, BlackMan, BlackKnight, BlackRook }
568 };
569
570 ChessSquare  lionArray[2][BOARD_FILES] = {
571     { WhiteRook, WhiteLion, WhiteBishop, WhiteQueen,
572         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
573     { BlackRook, BlackLion, BlackBishop, BlackQueen,
574         BlackKing, BlackBishop, BlackKnight, BlackRook }
575 };
576
577
578 #if (BOARD_FILES>=10)
579 ChessSquare ShogiArray[2][BOARD_FILES] = {
580     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
581         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
582     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
583         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
584 };
585
586 ChessSquare XiangqiArray[2][BOARD_FILES] = {
587     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
588         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
589     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
590         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
591 };
592
593 ChessSquare CapablancaArray[2][BOARD_FILES] = {
594     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
595         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
596     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
597         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
598 };
599
600 ChessSquare GreatArray[2][BOARD_FILES] = {
601     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
602         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
603     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
604         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
605 };
606
607 ChessSquare JanusArray[2][BOARD_FILES] = {
608     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
609         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
610     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
611         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
612 };
613
614 ChessSquare GrandArray[2][BOARD_FILES] = {
615     { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
616         WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
617     { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
618         BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
619 };
620
621 #ifdef GOTHIC
622 ChessSquare GothicArray[2][BOARD_FILES] = {
623     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
624         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
625     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
626         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
627 };
628 #else // !GOTHIC
629 #define GothicArray CapablancaArray
630 #endif // !GOTHIC
631
632 #ifdef FALCON
633 ChessSquare FalconArray[2][BOARD_FILES] = {
634     { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
635         WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
636     { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
637         BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
638 };
639 #else // !FALCON
640 #define FalconArray CapablancaArray
641 #endif // !FALCON
642
643 #else // !(BOARD_FILES>=10)
644 #define XiangqiPosition FIDEArray
645 #define CapablancaArray FIDEArray
646 #define GothicArray FIDEArray
647 #define GreatArray FIDEArray
648 #endif // !(BOARD_FILES>=10)
649
650 #if (BOARD_FILES>=12)
651 ChessSquare CourierArray[2][BOARD_FILES] = {
652     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
653         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
654     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
655         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
656 };
657 ChessSquare ChuArray[6][BOARD_FILES] = {
658     { WhiteLance, WhiteUnicorn, WhiteMan, WhiteFerz, WhiteWazir, WhiteKing,
659       WhiteAlfil, WhiteWazir, WhiteFerz, WhiteMan, WhiteUnicorn, WhiteLance },
660     { BlackLance, BlackUnicorn, BlackMan, BlackFerz, BlackWazir, BlackAlfil,
661       BlackKing, BlackWazir, BlackFerz, BlackMan, BlackUnicorn, BlackLance },
662     { WhiteCannon, EmptySquare, WhiteBishop, EmptySquare, WhiteNightrider, WhiteMarshall,
663       WhiteAngel, WhiteNightrider, EmptySquare, WhiteBishop, EmptySquare, WhiteCannon },
664     { BlackCannon, EmptySquare, BlackBishop, EmptySquare, BlackNightrider, BlackAngel,
665       BlackMarshall, BlackNightrider, EmptySquare, BlackBishop, EmptySquare, BlackCannon },
666     { WhiteFalcon, WhiteSilver, WhiteRook, WhiteCardinal, WhiteDragon, WhiteLion,
667       WhiteQueen, WhiteDragon, WhiteCardinal, WhiteRook, WhiteSilver, WhiteFalcon },
668     { BlackFalcon, BlackSilver, BlackRook, BlackCardinal, BlackDragon, BlackQueen,
669       BlackLion, BlackDragon, BlackCardinal, BlackRook, BlackSilver, BlackFalcon }
670 };
671 #else // !(BOARD_FILES>=12)
672 #define CourierArray CapablancaArray
673 #define ChuArray CapablancaArray
674 #endif // !(BOARD_FILES>=12)
675
676
677 Board initialPosition;
678
679
680 /* Convert str to a rating. Checks for special cases of "----",
681
682    "++++", etc. Also strips ()'s */
683 int
684 string_to_rating (char *str)
685 {
686   while(*str && !isdigit(*str)) ++str;
687   if (!*str)
688     return 0;   /* One of the special "no rating" cases */
689   else
690     return atoi(str);
691 }
692
693 void
694 ClearProgramStats ()
695 {
696     /* Init programStats */
697     programStats.movelist[0] = 0;
698     programStats.depth = 0;
699     programStats.nr_moves = 0;
700     programStats.moves_left = 0;
701     programStats.nodes = 0;
702     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
703     programStats.score = 0;
704     programStats.got_only_move = 0;
705     programStats.got_fail = 0;
706     programStats.line_is_book = 0;
707 }
708
709 void
710 CommonEngineInit ()
711 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
712     if (appData.firstPlaysBlack) {
713         first.twoMachinesColor = "black\n";
714         second.twoMachinesColor = "white\n";
715     } else {
716         first.twoMachinesColor = "white\n";
717         second.twoMachinesColor = "black\n";
718     }
719
720     first.other = &second;
721     second.other = &first;
722
723     { float norm = 1;
724         if(appData.timeOddsMode) {
725             norm = appData.timeOdds[0];
726             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
727         }
728         first.timeOdds  = appData.timeOdds[0]/norm;
729         second.timeOdds = appData.timeOdds[1]/norm;
730     }
731
732     if(programVersion) free(programVersion);
733     if (appData.noChessProgram) {
734         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
735         sprintf(programVersion, "%s", PACKAGE_STRING);
736     } else {
737       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
738       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
739       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
740     }
741 }
742
743 void
744 UnloadEngine (ChessProgramState *cps)
745 {
746         /* Kill off first chess program */
747         if (cps->isr != NULL)
748           RemoveInputSource(cps->isr);
749         cps->isr = NULL;
750
751         if (cps->pr != NoProc) {
752             ExitAnalyzeMode();
753             DoSleep( appData.delayBeforeQuit );
754             SendToProgram("quit\n", cps);
755             DoSleep( appData.delayAfterQuit );
756             DestroyChildProcess(cps->pr, cps->useSigterm);
757         }
758         cps->pr = NoProc;
759         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
760 }
761
762 void
763 ClearOptions (ChessProgramState *cps)
764 {
765     int i;
766     cps->nrOptions = cps->comboCnt = 0;
767     for(i=0; i<MAX_OPTIONS; i++) {
768         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
769         cps->option[i].textValue = 0;
770     }
771 }
772
773 char *engineNames[] = {
774   /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
775      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
776 N_("first"),
777   /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
778      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
779 N_("second")
780 };
781
782 void
783 InitEngine (ChessProgramState *cps, int n)
784 {   // [HGM] all engine initialiation put in a function that does one engine
785
786     ClearOptions(cps);
787
788     cps->which = engineNames[n];
789     cps->maybeThinking = FALSE;
790     cps->pr = NoProc;
791     cps->isr = NULL;
792     cps->sendTime = 2;
793     cps->sendDrawOffers = 1;
794
795     cps->program = appData.chessProgram[n];
796     cps->host = appData.host[n];
797     cps->dir = appData.directory[n];
798     cps->initString = appData.engInitString[n];
799     cps->computerString = appData.computerString[n];
800     cps->useSigint  = TRUE;
801     cps->useSigterm = TRUE;
802     cps->reuse = appData.reuse[n];
803     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
804     cps->useSetboard = FALSE;
805     cps->useSAN = FALSE;
806     cps->usePing = FALSE;
807     cps->lastPing = 0;
808     cps->lastPong = 0;
809     cps->usePlayother = FALSE;
810     cps->useColors = TRUE;
811     cps->useUsermove = FALSE;
812     cps->sendICS = FALSE;
813     cps->sendName = appData.icsActive;
814     cps->sdKludge = FALSE;
815     cps->stKludge = FALSE;
816     if(cps->tidy == NULL) cps->tidy = (char*) malloc(MSG_SIZ);
817     TidyProgramName(cps->program, cps->host, cps->tidy);
818     cps->matchWins = 0;
819     ASSIGN(cps->variants, appData.variant);
820     cps->analysisSupport = 2; /* detect */
821     cps->analyzing = FALSE;
822     cps->initDone = FALSE;
823     cps->reload = FALSE;
824
825     /* New features added by Tord: */
826     cps->useFEN960 = FALSE;
827     cps->useOOCastle = TRUE;
828     /* End of new features added by Tord. */
829     cps->fenOverride  = appData.fenOverride[n];
830
831     /* [HGM] time odds: set factor for each machine */
832     cps->timeOdds  = appData.timeOdds[n];
833
834     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
835     cps->accumulateTC = appData.accumulateTC[n];
836     cps->maxNrOfSessions = 1;
837
838     /* [HGM] debug */
839     cps->debug = FALSE;
840
841     cps->supportsNPS = UNKNOWN;
842     cps->memSize = FALSE;
843     cps->maxCores = FALSE;
844     ASSIGN(cps->egtFormats, "");
845
846     /* [HGM] options */
847     cps->optionSettings  = appData.engOptions[n];
848
849     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
850     cps->isUCI = appData.isUCI[n]; /* [AS] */
851     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
852     cps->highlight = 0;
853
854     if (appData.protocolVersion[n] > PROTOVER
855         || appData.protocolVersion[n] < 1)
856       {
857         char buf[MSG_SIZ];
858         int len;
859
860         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
861                        appData.protocolVersion[n]);
862         if( (len >= MSG_SIZ) && appData.debugMode )
863           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
864
865         DisplayFatalError(buf, 0, 2);
866       }
867     else
868       {
869         cps->protocolVersion = appData.protocolVersion[n];
870       }
871
872     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
873     ParseFeatures(appData.featureDefaults, cps);
874 }
875
876 ChessProgramState *savCps;
877
878 GameMode oldMode;
879
880 void
881 LoadEngine ()
882 {
883     int i;
884     if(WaitForEngine(savCps, LoadEngine)) return;
885     CommonEngineInit(); // recalculate time odds
886     if(gameInfo.variant != StringToVariant(appData.variant)) {
887         // we changed variant when loading the engine; this forces us to reset
888         Reset(TRUE, savCps != &first);
889         oldMode = BeginningOfGame; // to prevent restoring old mode
890     }
891     InitChessProgram(savCps, FALSE);
892     if(gameMode == EditGame) SendToProgram("force\n", savCps); // in EditGame mode engine must be in force mode
893     DisplayMessage("", "");
894     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
895     for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
896     ThawUI();
897     SetGNUMode();
898     if(oldMode == AnalyzeMode) AnalyzeModeEvent();
899 }
900
901 void
902 ReplaceEngine (ChessProgramState *cps, int n)
903 {
904     oldMode = gameMode; // remember mode, so it can be restored after loading sequence is complete
905     keepInfo = 1;
906     if(oldMode != BeginningOfGame) EditGameEvent();
907     keepInfo = 0;
908     UnloadEngine(cps);
909     appData.noChessProgram = FALSE;
910     appData.clockMode = TRUE;
911     InitEngine(cps, n);
912     UpdateLogos(TRUE);
913     if(n) return; // only startup first engine immediately; second can wait
914     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
915     LoadEngine();
916 }
917
918 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
919 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
920
921 static char resetOptions[] =
922         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
923         "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
924         "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 "
925         "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
926
927 void
928 FloatToFront(char **list, char *engineLine)
929 {
930     char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
931     int i=0;
932     if(appData.recentEngines <= 0) return;
933     TidyProgramName(engineLine, "localhost", tidy+1);
934     tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
935     strncpy(buf+1, *list, MSG_SIZ-50);
936     if(p = strstr(buf, tidy)) { // tidy name appears in list
937         q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
938         while(*p++ = *++q); // squeeze out
939     }
940     strcat(tidy, buf+1); // put list behind tidy name
941     p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
942     if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
943     ASSIGN(*list, tidy+1);
944 }
945
946 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
947
948 void
949 Load (ChessProgramState *cps, int i)
950 {
951     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ], buf3[MSG_SIZ], jar;
952     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
953         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
954         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
955         ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
956         FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
957         appData.firstProtocolVersion = PROTOVER;
958         ParseArgsFromString(buf);
959         SwapEngines(i);
960         ReplaceEngine(cps, i);
961         FloatToFront(&appData.recentEngineList, engineLine);
962         return;
963     }
964     p = engineName;
965     while(q = strchr(p, SLASH)) p = q+1;
966     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
967     if(engineDir[0] != NULLCHAR) {
968         ASSIGN(appData.directory[i], engineDir); p = engineName;
969     } else if(p != engineName) { // derive directory from engine path, when not given
970         p[-1] = 0;
971         ASSIGN(appData.directory[i], engineName);
972         p[-1] = SLASH;
973         if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
974     } else { ASSIGN(appData.directory[i], "."); }
975     jar = (strstr(p, ".jar") == p + strlen(p) - 4);
976     if(params[0]) {
977         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
978         snprintf(command, MSG_SIZ, "%s %s", p, params);
979         p = command;
980     }
981     if(jar) { snprintf(buf3, MSG_SIZ, "java -jar %s", p); p = buf3; }
982     ASSIGN(appData.chessProgram[i], p);
983     appData.isUCI[i] = isUCI;
984     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
985     appData.hasOwnBookUCI[i] = hasBook;
986     if(!nickName[0]) useNick = FALSE;
987     if(useNick) ASSIGN(appData.pgnName[i], nickName);
988     if(addToList) {
989         int len;
990         char quote;
991         q = firstChessProgramNames;
992         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
993         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
994         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
995                         quote, p, quote, appData.directory[i],
996                         useNick ? " -fn \"" : "",
997                         useNick ? nickName : "",
998                         useNick ? "\"" : "",
999                         v1 ? " -firstProtocolVersion 1" : "",
1000                         hasBook ? "" : " -fNoOwnBookUCI",
1001                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
1002                         storeVariant ? " -variant " : "",
1003                         storeVariant ? VariantName(gameInfo.variant) : "");
1004         if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
1005         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
1006         if(insert != q) insert[-1] = NULLCHAR;
1007         snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
1008         if(q)   free(q);
1009         FloatToFront(&appData.recentEngineList, buf);
1010     }
1011     ReplaceEngine(cps, i);
1012 }
1013
1014 void
1015 InitTimeControls ()
1016 {
1017     int matched, min, sec;
1018     /*
1019      * Parse timeControl resource
1020      */
1021     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
1022                           appData.movesPerSession)) {
1023         char buf[MSG_SIZ];
1024         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
1025         DisplayFatalError(buf, 0, 2);
1026     }
1027
1028     /*
1029      * Parse searchTime resource
1030      */
1031     if (*appData.searchTime != NULLCHAR) {
1032         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
1033         if (matched == 1) {
1034             searchTime = min * 60;
1035         } else if (matched == 2) {
1036             searchTime = min * 60 + sec;
1037         } else {
1038             char buf[MSG_SIZ];
1039             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
1040             DisplayFatalError(buf, 0, 2);
1041         }
1042     }
1043 }
1044
1045 void
1046 InitBackEnd1 ()
1047 {
1048
1049     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1050     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1051
1052     GetTimeMark(&programStartTime);
1053     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1054     appData.seedBase = random() + (random()<<15);
1055     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1056
1057     ClearProgramStats();
1058     programStats.ok_to_send = 1;
1059     programStats.seen_stat = 0;
1060
1061     /*
1062      * Initialize game list
1063      */
1064     ListNew(&gameList);
1065
1066
1067     /*
1068      * Internet chess server status
1069      */
1070     if (appData.icsActive) {
1071         appData.matchMode = FALSE;
1072         appData.matchGames = 0;
1073 #if ZIPPY
1074         appData.noChessProgram = !appData.zippyPlay;
1075 #else
1076         appData.zippyPlay = FALSE;
1077         appData.zippyTalk = FALSE;
1078         appData.noChessProgram = TRUE;
1079 #endif
1080         if (*appData.icsHelper != NULLCHAR) {
1081             appData.useTelnet = TRUE;
1082             appData.telnetProgram = appData.icsHelper;
1083         }
1084     } else {
1085         appData.zippyTalk = appData.zippyPlay = FALSE;
1086     }
1087
1088     /* [AS] Initialize pv info list [HGM] and game state */
1089     {
1090         int i, j;
1091
1092         for( i=0; i<=framePtr; i++ ) {
1093             pvInfoList[i].depth = -1;
1094             boards[i][EP_STATUS] = EP_NONE;
1095             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1096         }
1097     }
1098
1099     InitTimeControls();
1100
1101     /* [AS] Adjudication threshold */
1102     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1103
1104     InitEngine(&first, 0);
1105     InitEngine(&second, 1);
1106     CommonEngineInit();
1107
1108     pairing.which = "pairing"; // pairing engine
1109     pairing.pr = NoProc;
1110     pairing.isr = NULL;
1111     pairing.program = appData.pairingEngine;
1112     pairing.host = "localhost";
1113     pairing.dir = ".";
1114
1115     if (appData.icsActive) {
1116         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1117     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1118         appData.clockMode = FALSE;
1119         first.sendTime = second.sendTime = 0;
1120     }
1121
1122 #if ZIPPY
1123     /* Override some settings from environment variables, for backward
1124        compatibility.  Unfortunately it's not feasible to have the env
1125        vars just set defaults, at least in xboard.  Ugh.
1126     */
1127     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1128       ZippyInit();
1129     }
1130 #endif
1131
1132     if (!appData.icsActive) {
1133       char buf[MSG_SIZ];
1134       int len;
1135
1136       /* Check for variants that are supported only in ICS mode,
1137          or not at all.  Some that are accepted here nevertheless
1138          have bugs; see comments below.
1139       */
1140       VariantClass variant = StringToVariant(appData.variant);
1141       switch (variant) {
1142       case VariantBughouse:     /* need four players and two boards */
1143       case VariantKriegspiel:   /* need to hide pieces and move details */
1144         /* case VariantFischeRandom: (Fabien: moved below) */
1145         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1146         if( (len >= MSG_SIZ) && appData.debugMode )
1147           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1148
1149         DisplayFatalError(buf, 0, 2);
1150         return;
1151
1152       case VariantUnknown:
1153       case VariantLoadable:
1154       case Variant29:
1155       case Variant30:
1156       case Variant31:
1157       case Variant32:
1158       case Variant33:
1159       case Variant34:
1160       case Variant35:
1161       case Variant36:
1162       default:
1163         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1164         if( (len >= MSG_SIZ) && appData.debugMode )
1165           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1166
1167         DisplayFatalError(buf, 0, 2);
1168         return;
1169
1170       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1171       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1172       case VariantGothic:     /* [HGM] should work */
1173       case VariantCapablanca: /* [HGM] should work */
1174       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1175       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1176       case VariantChu:        /* [HGM] experimental */
1177       case VariantKnightmate: /* [HGM] should work */
1178       case VariantCylinder:   /* [HGM] untested */
1179       case VariantFalcon:     /* [HGM] untested */
1180       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1181                                  offboard interposition not understood */
1182       case VariantNormal:     /* definitely works! */
1183       case VariantWildCastle: /* pieces not automatically shuffled */
1184       case VariantNoCastle:   /* pieces not automatically shuffled */
1185       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1186       case VariantLosers:     /* should work except for win condition,
1187                                  and doesn't know captures are mandatory */
1188       case VariantSuicide:    /* should work except for win condition,
1189                                  and doesn't know captures are mandatory */
1190       case VariantGiveaway:   /* should work except for win condition,
1191                                  and doesn't know captures are mandatory */
1192       case VariantTwoKings:   /* should work */
1193       case VariantAtomic:     /* should work except for win condition */
1194       case Variant3Check:     /* should work except for win condition */
1195       case VariantShatranj:   /* should work except for all win conditions */
1196       case VariantMakruk:     /* should work except for draw countdown */
1197       case VariantASEAN :     /* should work except for draw countdown */
1198       case VariantBerolina:   /* might work if TestLegality is off */
1199       case VariantCapaRandom: /* should work */
1200       case VariantJanus:      /* should work */
1201       case VariantSuper:      /* experimental */
1202       case VariantGreat:      /* experimental, requires legality testing to be off */
1203       case VariantSChess:     /* S-Chess, should work */
1204       case VariantGrand:      /* should work */
1205       case VariantSpartan:    /* should work */
1206       case VariantLion:       /* should work */
1207         break;
1208       }
1209     }
1210
1211 }
1212
1213 int
1214 NextIntegerFromString (char ** str, long * value)
1215 {
1216     int result = -1;
1217     char * s = *str;
1218
1219     while( *s == ' ' || *s == '\t' ) {
1220         s++;
1221     }
1222
1223     *value = 0;
1224
1225     if( *s >= '0' && *s <= '9' ) {
1226         while( *s >= '0' && *s <= '9' ) {
1227             *value = *value * 10 + (*s - '0');
1228             s++;
1229         }
1230
1231         result = 0;
1232     }
1233
1234     *str = s;
1235
1236     return result;
1237 }
1238
1239 int
1240 NextTimeControlFromString (char ** str, long * value)
1241 {
1242     long temp;
1243     int result = NextIntegerFromString( str, &temp );
1244
1245     if( result == 0 ) {
1246         *value = temp * 60; /* Minutes */
1247         if( **str == ':' ) {
1248             (*str)++;
1249             result = NextIntegerFromString( str, &temp );
1250             *value += temp; /* Seconds */
1251         }
1252     }
1253
1254     return result;
1255 }
1256
1257 int
1258 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1259 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1260     int result = -1, type = 0; long temp, temp2;
1261
1262     if(**str != ':') return -1; // old params remain in force!
1263     (*str)++;
1264     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1265     if( NextIntegerFromString( str, &temp ) ) return -1;
1266     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1267
1268     if(**str != '/') {
1269         /* time only: incremental or sudden-death time control */
1270         if(**str == '+') { /* increment follows; read it */
1271             (*str)++;
1272             if(**str == '!') type = *(*str)++; // Bronstein TC
1273             if(result = NextIntegerFromString( str, &temp2)) return -1;
1274             *inc = temp2 * 1000;
1275             if(**str == '.') { // read fraction of increment
1276                 char *start = ++(*str);
1277                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1278                 temp2 *= 1000;
1279                 while(start++ < *str) temp2 /= 10;
1280                 *inc += temp2;
1281             }
1282         } else *inc = 0;
1283         *moves = 0; *tc = temp * 1000; *incType = type;
1284         return 0;
1285     }
1286
1287     (*str)++; /* classical time control */
1288     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1289
1290     if(result == 0) {
1291         *moves = temp;
1292         *tc    = temp2 * 1000;
1293         *inc   = 0;
1294         *incType = type;
1295     }
1296     return result;
1297 }
1298
1299 int
1300 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1301 {   /* [HGM] get time to add from the multi-session time-control string */
1302     int incType, moves=1; /* kludge to force reading of first session */
1303     long time, increment;
1304     char *s = tcString;
1305
1306     if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1307     do {
1308         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1309         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1310         if(movenr == -1) return time;    /* last move before new session     */
1311         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1312         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1313         if(!moves) return increment;     /* current session is incremental   */
1314         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1315     } while(movenr >= -1);               /* try again for next session       */
1316
1317     return 0; // no new time quota on this move
1318 }
1319
1320 int
1321 ParseTimeControl (char *tc, float ti, int mps)
1322 {
1323   long tc1;
1324   long tc2;
1325   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1326   int min, sec=0;
1327
1328   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1329   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1330       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1331   if(ti > 0) {
1332
1333     if(mps)
1334       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1335     else
1336       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1337   } else {
1338     if(mps)
1339       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1340     else
1341       snprintf(buf, MSG_SIZ, ":%s", mytc);
1342   }
1343   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1344
1345   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1346     return FALSE;
1347   }
1348
1349   if( *tc == '/' ) {
1350     /* Parse second time control */
1351     tc++;
1352
1353     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1354       return FALSE;
1355     }
1356
1357     if( tc2 == 0 ) {
1358       return FALSE;
1359     }
1360
1361     timeControl_2 = tc2 * 1000;
1362   }
1363   else {
1364     timeControl_2 = 0;
1365   }
1366
1367   if( tc1 == 0 ) {
1368     return FALSE;
1369   }
1370
1371   timeControl = tc1 * 1000;
1372
1373   if (ti >= 0) {
1374     timeIncrement = ti * 1000;  /* convert to ms */
1375     movesPerSession = 0;
1376   } else {
1377     timeIncrement = 0;
1378     movesPerSession = mps;
1379   }
1380   return TRUE;
1381 }
1382
1383 void
1384 InitBackEnd2 ()
1385 {
1386     if (appData.debugMode) {
1387 #    ifdef __GIT_VERSION
1388       fprintf(debugFP, "Version: %s (%s)\n", programVersion, __GIT_VERSION);
1389 #    else
1390       fprintf(debugFP, "Version: %s\n", programVersion);
1391 #    endif
1392     }
1393     ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1394
1395     set_cont_sequence(appData.wrapContSeq);
1396     if (appData.matchGames > 0) {
1397         appData.matchMode = TRUE;
1398     } else if (appData.matchMode) {
1399         appData.matchGames = 1;
1400     }
1401     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1402         appData.matchGames = appData.sameColorGames;
1403     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1404         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1405         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1406     }
1407     Reset(TRUE, FALSE);
1408     if (appData.noChessProgram || first.protocolVersion == 1) {
1409       InitBackEnd3();
1410     } else {
1411       /* kludge: allow timeout for initial "feature" commands */
1412       FreezeUI();
1413       DisplayMessage("", _("Starting chess program"));
1414       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1415     }
1416 }
1417
1418 int
1419 CalculateIndex (int index, int gameNr)
1420 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1421     int res;
1422     if(index > 0) return index; // fixed nmber
1423     if(index == 0) return 1;
1424     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1425     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1426     return res;
1427 }
1428
1429 int
1430 LoadGameOrPosition (int gameNr)
1431 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1432     if (*appData.loadGameFile != NULLCHAR) {
1433         if (!LoadGameFromFile(appData.loadGameFile,
1434                 CalculateIndex(appData.loadGameIndex, gameNr),
1435                               appData.loadGameFile, FALSE)) {
1436             DisplayFatalError(_("Bad game file"), 0, 1);
1437             return 0;
1438         }
1439     } else if (*appData.loadPositionFile != NULLCHAR) {
1440         if (!LoadPositionFromFile(appData.loadPositionFile,
1441                 CalculateIndex(appData.loadPositionIndex, gameNr),
1442                                   appData.loadPositionFile)) {
1443             DisplayFatalError(_("Bad position file"), 0, 1);
1444             return 0;
1445         }
1446     }
1447     return 1;
1448 }
1449
1450 void
1451 ReserveGame (int gameNr, char resChar)
1452 {
1453     FILE *tf = fopen(appData.tourneyFile, "r+");
1454     char *p, *q, c, buf[MSG_SIZ];
1455     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1456     safeStrCpy(buf, lastMsg, MSG_SIZ);
1457     DisplayMessage(_("Pick new game"), "");
1458     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1459     ParseArgsFromFile(tf);
1460     p = q = appData.results;
1461     if(appData.debugMode) {
1462       char *r = appData.participants;
1463       fprintf(debugFP, "results = '%s'\n", p);
1464       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1465       fprintf(debugFP, "\n");
1466     }
1467     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1468     nextGame = q - p;
1469     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1470     safeStrCpy(q, p, strlen(p) + 2);
1471     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1472     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1473     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1474         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1475         q[nextGame] = '*';
1476     }
1477     fseek(tf, -(strlen(p)+4), SEEK_END);
1478     c = fgetc(tf);
1479     if(c != '"') // depending on DOS or Unix line endings we can be one off
1480          fseek(tf, -(strlen(p)+2), SEEK_END);
1481     else fseek(tf, -(strlen(p)+3), SEEK_END);
1482     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1483     DisplayMessage(buf, "");
1484     free(p); appData.results = q;
1485     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1486        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1487       int round = appData.defaultMatchGames * appData.tourneyType;
1488       if(gameNr < 0 || appData.tourneyType < 1 ||  // gauntlet engine can always stay loaded as first engine
1489          appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1490         UnloadEngine(&first);  // next game belongs to other pairing;
1491         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1492     }
1493     if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1494 }
1495
1496 void
1497 MatchEvent (int mode)
1498 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1499         int dummy;
1500         if(matchMode) { // already in match mode: switch it off
1501             abortMatch = TRUE;
1502             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1503             return;
1504         }
1505 //      if(gameMode != BeginningOfGame) {
1506 //          DisplayError(_("You can only start a match from the initial position."), 0);
1507 //          return;
1508 //      }
1509         abortMatch = FALSE;
1510         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1511         /* Set up machine vs. machine match */
1512         nextGame = 0;
1513         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1514         if(appData.tourneyFile[0]) {
1515             ReserveGame(-1, 0);
1516             if(nextGame > appData.matchGames) {
1517                 char buf[MSG_SIZ];
1518                 if(strchr(appData.results, '*') == NULL) {
1519                     FILE *f;
1520                     appData.tourneyCycles++;
1521                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1522                         fclose(f);
1523                         NextTourneyGame(-1, &dummy);
1524                         ReserveGame(-1, 0);
1525                         if(nextGame <= appData.matchGames) {
1526                             DisplayNote(_("You restarted an already completed tourney.\nOne more cycle will now be added to it.\nGames commence in 10 sec."));
1527                             matchMode = mode;
1528                             ScheduleDelayedEvent(NextMatchGame, 10000);
1529                             return;
1530                         }
1531                     }
1532                 }
1533                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1534                 DisplayError(buf, 0);
1535                 appData.tourneyFile[0] = 0;
1536                 return;
1537             }
1538         } else
1539         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1540             DisplayFatalError(_("Can't have a match with no chess programs"),
1541                               0, 2);
1542             return;
1543         }
1544         matchMode = mode;
1545         matchGame = roundNr = 1;
1546         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1547         NextMatchGame();
1548 }
1549
1550 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1551
1552 void
1553 InitBackEnd3 P((void))
1554 {
1555     GameMode initialMode;
1556     char buf[MSG_SIZ];
1557     int err, len;
1558
1559     InitChessProgram(&first, startedFromSetupPosition);
1560
1561     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1562         free(programVersion);
1563         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1564         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1565         FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1566     }
1567
1568     if (appData.icsActive) {
1569 #ifdef WIN32
1570         /* [DM] Make a console window if needed [HGM] merged ifs */
1571         ConsoleCreate();
1572 #endif
1573         err = establish();
1574         if (err != 0)
1575           {
1576             if (*appData.icsCommPort != NULLCHAR)
1577               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1578                              appData.icsCommPort);
1579             else
1580               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1581                         appData.icsHost, appData.icsPort);
1582
1583             if( (len >= MSG_SIZ) && appData.debugMode )
1584               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1585
1586             DisplayFatalError(buf, err, 1);
1587             return;
1588         }
1589         SetICSMode();
1590         telnetISR =
1591           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1592         fromUserISR =
1593           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1594         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1595             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1596     } else if (appData.noChessProgram) {
1597         SetNCPMode();
1598     } else {
1599         SetGNUMode();
1600     }
1601
1602     if (*appData.cmailGameName != NULLCHAR) {
1603         SetCmailMode();
1604         OpenLoopback(&cmailPR);
1605         cmailISR =
1606           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1607     }
1608
1609     ThawUI();
1610     DisplayMessage("", "");
1611     if (StrCaseCmp(appData.initialMode, "") == 0) {
1612       initialMode = BeginningOfGame;
1613       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1614         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1615         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1616         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1617         ModeHighlight();
1618       }
1619     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1620       initialMode = TwoMachinesPlay;
1621     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1622       initialMode = AnalyzeFile;
1623     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1624       initialMode = AnalyzeMode;
1625     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1626       initialMode = MachinePlaysWhite;
1627     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1628       initialMode = MachinePlaysBlack;
1629     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1630       initialMode = EditGame;
1631     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1632       initialMode = EditPosition;
1633     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1634       initialMode = Training;
1635     } else {
1636       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1637       if( (len >= MSG_SIZ) && appData.debugMode )
1638         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1639
1640       DisplayFatalError(buf, 0, 2);
1641       return;
1642     }
1643
1644     if (appData.matchMode) {
1645         if(appData.tourneyFile[0]) { // start tourney from command line
1646             FILE *f;
1647             if(f = fopen(appData.tourneyFile, "r")) {
1648                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1649                 fclose(f);
1650                 appData.clockMode = TRUE;
1651                 SetGNUMode();
1652             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1653         }
1654         MatchEvent(TRUE);
1655     } else if (*appData.cmailGameName != NULLCHAR) {
1656         /* Set up cmail mode */
1657         ReloadCmailMsgEvent(TRUE);
1658     } else {
1659         /* Set up other modes */
1660         if (initialMode == AnalyzeFile) {
1661           if (*appData.loadGameFile == NULLCHAR) {
1662             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1663             return;
1664           }
1665         }
1666         if (*appData.loadGameFile != NULLCHAR) {
1667             (void) LoadGameFromFile(appData.loadGameFile,
1668                                     appData.loadGameIndex,
1669                                     appData.loadGameFile, TRUE);
1670         } else if (*appData.loadPositionFile != NULLCHAR) {
1671             (void) LoadPositionFromFile(appData.loadPositionFile,
1672                                         appData.loadPositionIndex,
1673                                         appData.loadPositionFile);
1674             /* [HGM] try to make self-starting even after FEN load */
1675             /* to allow automatic setup of fairy variants with wtm */
1676             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1677                 gameMode = BeginningOfGame;
1678                 setboardSpoiledMachineBlack = 1;
1679             }
1680             /* [HGM] loadPos: make that every new game uses the setup */
1681             /* from file as long as we do not switch variant          */
1682             if(!blackPlaysFirst) {
1683                 startedFromPositionFile = TRUE;
1684                 CopyBoard(filePosition, boards[0]);
1685             }
1686         }
1687         if (initialMode == AnalyzeMode) {
1688           if (appData.noChessProgram) {
1689             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1690             return;
1691           }
1692           if (appData.icsActive) {
1693             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1694             return;
1695           }
1696           AnalyzeModeEvent();
1697         } else if (initialMode == AnalyzeFile) {
1698           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1699           ShowThinkingEvent();
1700           AnalyzeFileEvent();
1701           AnalysisPeriodicEvent(1);
1702         } else if (initialMode == MachinePlaysWhite) {
1703           if (appData.noChessProgram) {
1704             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1705                               0, 2);
1706             return;
1707           }
1708           if (appData.icsActive) {
1709             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1710                               0, 2);
1711             return;
1712           }
1713           MachineWhiteEvent();
1714         } else if (initialMode == MachinePlaysBlack) {
1715           if (appData.noChessProgram) {
1716             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1717                               0, 2);
1718             return;
1719           }
1720           if (appData.icsActive) {
1721             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1722                               0, 2);
1723             return;
1724           }
1725           MachineBlackEvent();
1726         } else if (initialMode == TwoMachinesPlay) {
1727           if (appData.noChessProgram) {
1728             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1729                               0, 2);
1730             return;
1731           }
1732           if (appData.icsActive) {
1733             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1734                               0, 2);
1735             return;
1736           }
1737           TwoMachinesEvent();
1738         } else if (initialMode == EditGame) {
1739           EditGameEvent();
1740         } else if (initialMode == EditPosition) {
1741           EditPositionEvent();
1742         } else if (initialMode == Training) {
1743           if (*appData.loadGameFile == NULLCHAR) {
1744             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1745             return;
1746           }
1747           TrainingEvent();
1748         }
1749     }
1750 }
1751
1752 void
1753 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1754 {
1755     DisplayBook(current+1);
1756
1757     MoveHistorySet( movelist, first, last, current, pvInfoList );
1758
1759     EvalGraphSet( first, last, current, pvInfoList );
1760
1761     MakeEngineOutputTitle();
1762 }
1763
1764 /*
1765  * Establish will establish a contact to a remote host.port.
1766  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1767  *  used to talk to the host.
1768  * Returns 0 if okay, error code if not.
1769  */
1770 int
1771 establish ()
1772 {
1773     char buf[MSG_SIZ];
1774
1775     if (*appData.icsCommPort != NULLCHAR) {
1776         /* Talk to the host through a serial comm port */
1777         return OpenCommPort(appData.icsCommPort, &icsPR);
1778
1779     } else if (*appData.gateway != NULLCHAR) {
1780         if (*appData.remoteShell == NULLCHAR) {
1781             /* Use the rcmd protocol to run telnet program on a gateway host */
1782             snprintf(buf, sizeof(buf), "%s %s %s",
1783                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1784             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1785
1786         } else {
1787             /* Use the rsh program to run telnet program on a gateway host */
1788             if (*appData.remoteUser == NULLCHAR) {
1789                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1790                         appData.gateway, appData.telnetProgram,
1791                         appData.icsHost, appData.icsPort);
1792             } else {
1793                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1794                         appData.remoteShell, appData.gateway,
1795                         appData.remoteUser, appData.telnetProgram,
1796                         appData.icsHost, appData.icsPort);
1797             }
1798             return StartChildProcess(buf, "", &icsPR);
1799
1800         }
1801     } else if (appData.useTelnet) {
1802         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1803
1804     } else {
1805         /* TCP socket interface differs somewhat between
1806            Unix and NT; handle details in the front end.
1807            */
1808         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1809     }
1810 }
1811
1812 void
1813 EscapeExpand (char *p, char *q)
1814 {       // [HGM] initstring: routine to shape up string arguments
1815         while(*p++ = *q++) if(p[-1] == '\\')
1816             switch(*q++) {
1817                 case 'n': p[-1] = '\n'; break;
1818                 case 'r': p[-1] = '\r'; break;
1819                 case 't': p[-1] = '\t'; break;
1820                 case '\\': p[-1] = '\\'; break;
1821                 case 0: *p = 0; return;
1822                 default: p[-1] = q[-1]; break;
1823             }
1824 }
1825
1826 void
1827 show_bytes (FILE *fp, char *buf, int count)
1828 {
1829     while (count--) {
1830         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1831             fprintf(fp, "\\%03o", *buf & 0xff);
1832         } else {
1833             putc(*buf, fp);
1834         }
1835         buf++;
1836     }
1837     fflush(fp);
1838 }
1839
1840 /* Returns an errno value */
1841 int
1842 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1843 {
1844     char buf[8192], *p, *q, *buflim;
1845     int left, newcount, outcount;
1846
1847     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1848         *appData.gateway != NULLCHAR) {
1849         if (appData.debugMode) {
1850             fprintf(debugFP, ">ICS: ");
1851             show_bytes(debugFP, message, count);
1852             fprintf(debugFP, "\n");
1853         }
1854         return OutputToProcess(pr, message, count, outError);
1855     }
1856
1857     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1858     p = message;
1859     q = buf;
1860     left = count;
1861     newcount = 0;
1862     while (left) {
1863         if (q >= buflim) {
1864             if (appData.debugMode) {
1865                 fprintf(debugFP, ">ICS: ");
1866                 show_bytes(debugFP, buf, newcount);
1867                 fprintf(debugFP, "\n");
1868             }
1869             outcount = OutputToProcess(pr, buf, newcount, outError);
1870             if (outcount < newcount) return -1; /* to be sure */
1871             q = buf;
1872             newcount = 0;
1873         }
1874         if (*p == '\n') {
1875             *q++ = '\r';
1876             newcount++;
1877         } else if (((unsigned char) *p) == TN_IAC) {
1878             *q++ = (char) TN_IAC;
1879             newcount ++;
1880         }
1881         *q++ = *p++;
1882         newcount++;
1883         left--;
1884     }
1885     if (appData.debugMode) {
1886         fprintf(debugFP, ">ICS: ");
1887         show_bytes(debugFP, buf, newcount);
1888         fprintf(debugFP, "\n");
1889     }
1890     outcount = OutputToProcess(pr, buf, newcount, outError);
1891     if (outcount < newcount) return -1; /* to be sure */
1892     return count;
1893 }
1894
1895 void
1896 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1897 {
1898     int outError, outCount;
1899     static int gotEof = 0;
1900     static FILE *ini;
1901
1902     /* Pass data read from player on to ICS */
1903     if (count > 0) {
1904         gotEof = 0;
1905         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1906         if (outCount < count) {
1907             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1908         }
1909         if(have_sent_ICS_logon == 2) {
1910           if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1911             fprintf(ini, "%s", message);
1912             have_sent_ICS_logon = 3;
1913           } else
1914             have_sent_ICS_logon = 1;
1915         } else if(have_sent_ICS_logon == 3) {
1916             fprintf(ini, "%s", message);
1917             fclose(ini);
1918           have_sent_ICS_logon = 1;
1919         }
1920     } else if (count < 0) {
1921         RemoveInputSource(isr);
1922         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1923     } else if (gotEof++ > 0) {
1924         RemoveInputSource(isr);
1925         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1926     }
1927 }
1928
1929 void
1930 KeepAlive ()
1931 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1932     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1933     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1934     SendToICS("date\n");
1935     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1936 }
1937
1938 /* added routine for printf style output to ics */
1939 void
1940 ics_printf (char *format, ...)
1941 {
1942     char buffer[MSG_SIZ];
1943     va_list args;
1944
1945     va_start(args, format);
1946     vsnprintf(buffer, sizeof(buffer), format, args);
1947     buffer[sizeof(buffer)-1] = '\0';
1948     SendToICS(buffer);
1949     va_end(args);
1950 }
1951
1952 void
1953 SendToICS (char *s)
1954 {
1955     int count, outCount, outError;
1956
1957     if (icsPR == NoProc) return;
1958
1959     count = strlen(s);
1960     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1961     if (outCount < count) {
1962         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1963     }
1964 }
1965
1966 /* This is used for sending logon scripts to the ICS. Sending
1967    without a delay causes problems when using timestamp on ICC
1968    (at least on my machine). */
1969 void
1970 SendToICSDelayed (char *s, long msdelay)
1971 {
1972     int count, outCount, outError;
1973
1974     if (icsPR == NoProc) return;
1975
1976     count = strlen(s);
1977     if (appData.debugMode) {
1978         fprintf(debugFP, ">ICS: ");
1979         show_bytes(debugFP, s, count);
1980         fprintf(debugFP, "\n");
1981     }
1982     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1983                                       msdelay);
1984     if (outCount < count) {
1985         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1986     }
1987 }
1988
1989
1990 /* Remove all highlighting escape sequences in s
1991    Also deletes any suffix starting with '('
1992    */
1993 char *
1994 StripHighlightAndTitle (char *s)
1995 {
1996     static char retbuf[MSG_SIZ];
1997     char *p = retbuf;
1998
1999     while (*s != NULLCHAR) {
2000         while (*s == '\033') {
2001             while (*s != NULLCHAR && !isalpha(*s)) s++;
2002             if (*s != NULLCHAR) s++;
2003         }
2004         while (*s != NULLCHAR && *s != '\033') {
2005             if (*s == '(' || *s == '[') {
2006                 *p = NULLCHAR;
2007                 return retbuf;
2008             }
2009             *p++ = *s++;
2010         }
2011     }
2012     *p = NULLCHAR;
2013     return retbuf;
2014 }
2015
2016 /* Remove all highlighting escape sequences in s */
2017 char *
2018 StripHighlight (char *s)
2019 {
2020     static char retbuf[MSG_SIZ];
2021     char *p = retbuf;
2022
2023     while (*s != NULLCHAR) {
2024         while (*s == '\033') {
2025             while (*s != NULLCHAR && !isalpha(*s)) s++;
2026             if (*s != NULLCHAR) s++;
2027         }
2028         while (*s != NULLCHAR && *s != '\033') {
2029             *p++ = *s++;
2030         }
2031     }
2032     *p = NULLCHAR;
2033     return retbuf;
2034 }
2035
2036 char engineVariant[MSG_SIZ];
2037 char *variantNames[] = VARIANT_NAMES;
2038 char *
2039 VariantName (VariantClass v)
2040 {
2041     if(v == VariantUnknown || *engineVariant) return engineVariant;
2042     return variantNames[v];
2043 }
2044
2045
2046 /* Identify a variant from the strings the chess servers use or the
2047    PGN Variant tag names we use. */
2048 VariantClass
2049 StringToVariant (char *e)
2050 {
2051     char *p;
2052     int wnum = -1;
2053     VariantClass v = VariantNormal;
2054     int i, found = FALSE;
2055     char buf[MSG_SIZ];
2056     int len;
2057
2058     if (!e) return v;
2059
2060     /* [HGM] skip over optional board-size prefixes */
2061     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
2062         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
2063         while( *e++ != '_');
2064     }
2065
2066     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2067         v = VariantNormal;
2068         found = TRUE;
2069     } else
2070     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2071       if (StrCaseStr(e, variantNames[i])) {
2072         v = (VariantClass) i;
2073         found = TRUE;
2074         break;
2075       }
2076     }
2077
2078     if (!found) {
2079       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2080           || StrCaseStr(e, "wild/fr")
2081           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2082         v = VariantFischeRandom;
2083       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2084                  (i = 1, p = StrCaseStr(e, "w"))) {
2085         p += i;
2086         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2087         if (isdigit(*p)) {
2088           wnum = atoi(p);
2089         } else {
2090           wnum = -1;
2091         }
2092         switch (wnum) {
2093         case 0: /* FICS only, actually */
2094         case 1:
2095           /* Castling legal even if K starts on d-file */
2096           v = VariantWildCastle;
2097           break;
2098         case 2:
2099         case 3:
2100         case 4:
2101           /* Castling illegal even if K & R happen to start in
2102              normal positions. */
2103           v = VariantNoCastle;
2104           break;
2105         case 5:
2106         case 7:
2107         case 8:
2108         case 10:
2109         case 11:
2110         case 12:
2111         case 13:
2112         case 14:
2113         case 15:
2114         case 18:
2115         case 19:
2116           /* Castling legal iff K & R start in normal positions */
2117           v = VariantNormal;
2118           break;
2119         case 6:
2120         case 20:
2121         case 21:
2122           /* Special wilds for position setup; unclear what to do here */
2123           v = VariantLoadable;
2124           break;
2125         case 9:
2126           /* Bizarre ICC game */
2127           v = VariantTwoKings;
2128           break;
2129         case 16:
2130           v = VariantKriegspiel;
2131           break;
2132         case 17:
2133           v = VariantLosers;
2134           break;
2135         case 22:
2136           v = VariantFischeRandom;
2137           break;
2138         case 23:
2139           v = VariantCrazyhouse;
2140           break;
2141         case 24:
2142           v = VariantBughouse;
2143           break;
2144         case 25:
2145           v = Variant3Check;
2146           break;
2147         case 26:
2148           /* Not quite the same as FICS suicide! */
2149           v = VariantGiveaway;
2150           break;
2151         case 27:
2152           v = VariantAtomic;
2153           break;
2154         case 28:
2155           v = VariantShatranj;
2156           break;
2157
2158         /* Temporary names for future ICC types.  The name *will* change in
2159            the next xboard/WinBoard release after ICC defines it. */
2160         case 29:
2161           v = Variant29;
2162           break;
2163         case 30:
2164           v = Variant30;
2165           break;
2166         case 31:
2167           v = Variant31;
2168           break;
2169         case 32:
2170           v = Variant32;
2171           break;
2172         case 33:
2173           v = Variant33;
2174           break;
2175         case 34:
2176           v = Variant34;
2177           break;
2178         case 35:
2179           v = Variant35;
2180           break;
2181         case 36:
2182           v = Variant36;
2183           break;
2184         case 37:
2185           v = VariantShogi;
2186           break;
2187         case 38:
2188           v = VariantXiangqi;
2189           break;
2190         case 39:
2191           v = VariantCourier;
2192           break;
2193         case 40:
2194           v = VariantGothic;
2195           break;
2196         case 41:
2197           v = VariantCapablanca;
2198           break;
2199         case 42:
2200           v = VariantKnightmate;
2201           break;
2202         case 43:
2203           v = VariantFairy;
2204           break;
2205         case 44:
2206           v = VariantCylinder;
2207           break;
2208         case 45:
2209           v = VariantFalcon;
2210           break;
2211         case 46:
2212           v = VariantCapaRandom;
2213           break;
2214         case 47:
2215           v = VariantBerolina;
2216           break;
2217         case 48:
2218           v = VariantJanus;
2219           break;
2220         case 49:
2221           v = VariantSuper;
2222           break;
2223         case 50:
2224           v = VariantGreat;
2225           break;
2226         case -1:
2227           /* Found "wild" or "w" in the string but no number;
2228              must assume it's normal chess. */
2229           v = VariantNormal;
2230           break;
2231         default:
2232           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2233           if( (len >= MSG_SIZ) && appData.debugMode )
2234             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2235
2236           DisplayError(buf, 0);
2237           v = VariantUnknown;
2238           break;
2239         }
2240       }
2241     }
2242     if (appData.debugMode) {
2243       fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
2244               e, wnum, VariantName(v));
2245     }
2246     return v;
2247 }
2248
2249 static int leftover_start = 0, leftover_len = 0;
2250 char star_match[STAR_MATCH_N][MSG_SIZ];
2251
2252 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2253    advance *index beyond it, and set leftover_start to the new value of
2254    *index; else return FALSE.  If pattern contains the character '*', it
2255    matches any sequence of characters not containing '\r', '\n', or the
2256    character following the '*' (if any), and the matched sequence(s) are
2257    copied into star_match.
2258    */
2259 int
2260 looking_at ( char *buf, int *index, char *pattern)
2261 {
2262     char *bufp = &buf[*index], *patternp = pattern;
2263     int star_count = 0;
2264     char *matchp = star_match[0];
2265
2266     for (;;) {
2267         if (*patternp == NULLCHAR) {
2268             *index = leftover_start = bufp - buf;
2269             *matchp = NULLCHAR;
2270             return TRUE;
2271         }
2272         if (*bufp == NULLCHAR) return FALSE;
2273         if (*patternp == '*') {
2274             if (*bufp == *(patternp + 1)) {
2275                 *matchp = NULLCHAR;
2276                 matchp = star_match[++star_count];
2277                 patternp += 2;
2278                 bufp++;
2279                 continue;
2280             } else if (*bufp == '\n' || *bufp == '\r') {
2281                 patternp++;
2282                 if (*patternp == NULLCHAR)
2283                   continue;
2284                 else
2285                   return FALSE;
2286             } else {
2287                 *matchp++ = *bufp++;
2288                 continue;
2289             }
2290         }
2291         if (*patternp != *bufp) return FALSE;
2292         patternp++;
2293         bufp++;
2294     }
2295 }
2296
2297 void
2298 SendToPlayer (char *data, int length)
2299 {
2300     int error, outCount;
2301     outCount = OutputToProcess(NoProc, data, length, &error);
2302     if (outCount < length) {
2303         DisplayFatalError(_("Error writing to display"), error, 1);
2304     }
2305 }
2306
2307 void
2308 PackHolding (char packed[], char *holding)
2309 {
2310     char *p = holding;
2311     char *q = packed;
2312     int runlength = 0;
2313     int curr = 9999;
2314     do {
2315         if (*p == curr) {
2316             runlength++;
2317         } else {
2318             switch (runlength) {
2319               case 0:
2320                 break;
2321               case 1:
2322                 *q++ = curr;
2323                 break;
2324               case 2:
2325                 *q++ = curr;
2326                 *q++ = curr;
2327                 break;
2328               default:
2329                 sprintf(q, "%d", runlength);
2330                 while (*q) q++;
2331                 *q++ = curr;
2332                 break;
2333             }
2334             runlength = 1;
2335             curr = *p;
2336         }
2337     } while (*p++);
2338     *q = NULLCHAR;
2339 }
2340
2341 /* Telnet protocol requests from the front end */
2342 void
2343 TelnetRequest (unsigned char ddww, unsigned char option)
2344 {
2345     unsigned char msg[3];
2346     int outCount, outError;
2347
2348     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2349
2350     if (appData.debugMode) {
2351         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2352         switch (ddww) {
2353           case TN_DO:
2354             ddwwStr = "DO";
2355             break;
2356           case TN_DONT:
2357             ddwwStr = "DONT";
2358             break;
2359           case TN_WILL:
2360             ddwwStr = "WILL";
2361             break;
2362           case TN_WONT:
2363             ddwwStr = "WONT";
2364             break;
2365           default:
2366             ddwwStr = buf1;
2367             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2368             break;
2369         }
2370         switch (option) {
2371           case TN_ECHO:
2372             optionStr = "ECHO";
2373             break;
2374           default:
2375             optionStr = buf2;
2376             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2377             break;
2378         }
2379         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2380     }
2381     msg[0] = TN_IAC;
2382     msg[1] = ddww;
2383     msg[2] = option;
2384     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2385     if (outCount < 3) {
2386         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2387     }
2388 }
2389
2390 void
2391 DoEcho ()
2392 {
2393     if (!appData.icsActive) return;
2394     TelnetRequest(TN_DO, TN_ECHO);
2395 }
2396
2397 void
2398 DontEcho ()
2399 {
2400     if (!appData.icsActive) return;
2401     TelnetRequest(TN_DONT, TN_ECHO);
2402 }
2403
2404 void
2405 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2406 {
2407     /* put the holdings sent to us by the server on the board holdings area */
2408     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2409     char p;
2410     ChessSquare piece;
2411
2412     if(gameInfo.holdingsWidth < 2)  return;
2413     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2414         return; // prevent overwriting by pre-board holdings
2415
2416     if( (int)lowestPiece >= BlackPawn ) {
2417         holdingsColumn = 0;
2418         countsColumn = 1;
2419         holdingsStartRow = BOARD_HEIGHT-1;
2420         direction = -1;
2421     } else {
2422         holdingsColumn = BOARD_WIDTH-1;
2423         countsColumn = BOARD_WIDTH-2;
2424         holdingsStartRow = 0;
2425         direction = 1;
2426     }
2427
2428     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2429         board[i][holdingsColumn] = EmptySquare;
2430         board[i][countsColumn]   = (ChessSquare) 0;
2431     }
2432     while( (p=*holdings++) != NULLCHAR ) {
2433         piece = CharToPiece( ToUpper(p) );
2434         if(piece == EmptySquare) continue;
2435         /*j = (int) piece - (int) WhitePawn;*/
2436         j = PieceToNumber(piece);
2437         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2438         if(j < 0) continue;               /* should not happen */
2439         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2440         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2441         board[holdingsStartRow+j*direction][countsColumn]++;
2442     }
2443 }
2444
2445
2446 void
2447 VariantSwitch (Board board, VariantClass newVariant)
2448 {
2449    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2450    static Board oldBoard;
2451
2452    startedFromPositionFile = FALSE;
2453    if(gameInfo.variant == newVariant) return;
2454
2455    /* [HGM] This routine is called each time an assignment is made to
2456     * gameInfo.variant during a game, to make sure the board sizes
2457     * are set to match the new variant. If that means adding or deleting
2458     * holdings, we shift the playing board accordingly
2459     * This kludge is needed because in ICS observe mode, we get boards
2460     * of an ongoing game without knowing the variant, and learn about the
2461     * latter only later. This can be because of the move list we requested,
2462     * in which case the game history is refilled from the beginning anyway,
2463     * but also when receiving holdings of a crazyhouse game. In the latter
2464     * case we want to add those holdings to the already received position.
2465     */
2466
2467
2468    if (appData.debugMode) {
2469      fprintf(debugFP, "Switch board from %s to %s\n",
2470              VariantName(gameInfo.variant), VariantName(newVariant));
2471      setbuf(debugFP, NULL);
2472    }
2473    shuffleOpenings = 0;       /* [HGM] shuffle */
2474    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2475    switch(newVariant)
2476      {
2477      case VariantShogi:
2478        newWidth = 9;  newHeight = 9;
2479        gameInfo.holdingsSize = 7;
2480      case VariantBughouse:
2481      case VariantCrazyhouse:
2482        newHoldingsWidth = 2; break;
2483      case VariantGreat:
2484        newWidth = 10;
2485      case VariantSuper:
2486        newHoldingsWidth = 2;
2487        gameInfo.holdingsSize = 8;
2488        break;
2489      case VariantGothic:
2490      case VariantCapablanca:
2491      case VariantCapaRandom:
2492        newWidth = 10;
2493      default:
2494        newHoldingsWidth = gameInfo.holdingsSize = 0;
2495      };
2496
2497    if(newWidth  != gameInfo.boardWidth  ||
2498       newHeight != gameInfo.boardHeight ||
2499       newHoldingsWidth != gameInfo.holdingsWidth ) {
2500
2501      /* shift position to new playing area, if needed */
2502      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2503        for(i=0; i<BOARD_HEIGHT; i++)
2504          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2505            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2506              board[i][j];
2507        for(i=0; i<newHeight; i++) {
2508          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2509          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2510        }
2511      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2512        for(i=0; i<BOARD_HEIGHT; i++)
2513          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2514            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2515              board[i][j];
2516      }
2517      board[HOLDINGS_SET] = 0;
2518      gameInfo.boardWidth  = newWidth;
2519      gameInfo.boardHeight = newHeight;
2520      gameInfo.holdingsWidth = newHoldingsWidth;
2521      gameInfo.variant = newVariant;
2522      InitDrawingSizes(-2, 0);
2523    } else gameInfo.variant = newVariant;
2524    CopyBoard(oldBoard, board);   // remember correctly formatted board
2525      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2526    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2527 }
2528
2529 static int loggedOn = FALSE;
2530
2531 /*-- Game start info cache: --*/
2532 int gs_gamenum;
2533 char gs_kind[MSG_SIZ];
2534 static char player1Name[128] = "";
2535 static char player2Name[128] = "";
2536 static char cont_seq[] = "\n\\   ";
2537 static int player1Rating = -1;
2538 static int player2Rating = -1;
2539 /*----------------------------*/
2540
2541 ColorClass curColor = ColorNormal;
2542 int suppressKibitz = 0;
2543
2544 // [HGM] seekgraph
2545 Boolean soughtPending = FALSE;
2546 Boolean seekGraphUp;
2547 #define MAX_SEEK_ADS 200
2548 #define SQUARE 0x80
2549 char *seekAdList[MAX_SEEK_ADS];
2550 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2551 float tcList[MAX_SEEK_ADS];
2552 char colorList[MAX_SEEK_ADS];
2553 int nrOfSeekAds = 0;
2554 int minRating = 1010, maxRating = 2800;
2555 int hMargin = 10, vMargin = 20, h, w;
2556 extern int squareSize, lineGap;
2557
2558 void
2559 PlotSeekAd (int i)
2560 {
2561         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2562         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2563         if(r < minRating+100 && r >=0 ) r = minRating+100;
2564         if(r > maxRating) r = maxRating;
2565         if(tc < 1.f) tc = 1.f;
2566         if(tc > 95.f) tc = 95.f;
2567         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2568         y = ((double)r - minRating)/(maxRating - minRating)
2569             * (h-vMargin-squareSize/8-1) + vMargin;
2570         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2571         if(strstr(seekAdList[i], " u ")) color = 1;
2572         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2573            !strstr(seekAdList[i], "bullet") &&
2574            !strstr(seekAdList[i], "blitz") &&
2575            !strstr(seekAdList[i], "standard") ) color = 2;
2576         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2577         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2578 }
2579
2580 void
2581 PlotSingleSeekAd (int i)
2582 {
2583         PlotSeekAd(i);
2584 }
2585
2586 void
2587 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2588 {
2589         char buf[MSG_SIZ], *ext = "";
2590         VariantClass v = StringToVariant(type);
2591         if(strstr(type, "wild")) {
2592             ext = type + 4; // append wild number
2593             if(v == VariantFischeRandom) type = "chess960"; else
2594             if(v == VariantLoadable) type = "setup"; else
2595             type = VariantName(v);
2596         }
2597         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2598         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2599             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2600             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2601             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2602             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2603             seekNrList[nrOfSeekAds] = nr;
2604             zList[nrOfSeekAds] = 0;
2605             seekAdList[nrOfSeekAds++] = StrSave(buf);
2606             if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2607         }
2608 }
2609
2610 void
2611 EraseSeekDot (int i)
2612 {
2613     int x = xList[i], y = yList[i], d=squareSize/4, k;
2614     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2615     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2616     // now replot every dot that overlapped
2617     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2618         int xx = xList[k], yy = yList[k];
2619         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2620             DrawSeekDot(xx, yy, colorList[k]);
2621     }
2622 }
2623
2624 void
2625 RemoveSeekAd (int nr)
2626 {
2627         int i;
2628         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2629             EraseSeekDot(i);
2630             if(seekAdList[i]) free(seekAdList[i]);
2631             seekAdList[i] = seekAdList[--nrOfSeekAds];
2632             seekNrList[i] = seekNrList[nrOfSeekAds];
2633             ratingList[i] = ratingList[nrOfSeekAds];
2634             colorList[i]  = colorList[nrOfSeekAds];
2635             tcList[i] = tcList[nrOfSeekAds];
2636             xList[i]  = xList[nrOfSeekAds];
2637             yList[i]  = yList[nrOfSeekAds];
2638             zList[i]  = zList[nrOfSeekAds];
2639             seekAdList[nrOfSeekAds] = NULL;
2640             break;
2641         }
2642 }
2643
2644 Boolean
2645 MatchSoughtLine (char *line)
2646 {
2647     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2648     int nr, base, inc, u=0; char dummy;
2649
2650     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2651        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2652        (u=1) &&
2653        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2654         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2655         // match: compact and save the line
2656         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2657         return TRUE;
2658     }
2659     return FALSE;
2660 }
2661
2662 int
2663 DrawSeekGraph ()
2664 {
2665     int i;
2666     if(!seekGraphUp) return FALSE;
2667     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2668     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2669
2670     DrawSeekBackground(0, 0, w, h);
2671     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2672     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2673     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2674         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2675         yy = h-1-yy;
2676         DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2677         if(i%500 == 0) {
2678             char buf[MSG_SIZ];
2679             snprintf(buf, MSG_SIZ, "%d", i);
2680             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2681         }
2682     }
2683     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2684     for(i=1; i<100; i+=(i<10?1:5)) {
2685         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2686         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2687         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2688             char buf[MSG_SIZ];
2689             snprintf(buf, MSG_SIZ, "%d", i);
2690             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2691         }
2692     }
2693     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2694     return TRUE;
2695 }
2696
2697 int
2698 SeekGraphClick (ClickType click, int x, int y, int moving)
2699 {
2700     static int lastDown = 0, displayed = 0, lastSecond;
2701     if(y < 0) return FALSE;
2702     if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2703         (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2704         if(!seekGraphUp) return FALSE;
2705         seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2706         DrawPosition(TRUE, NULL);
2707         return TRUE;
2708     }
2709     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2710         if(click == Release || moving) return FALSE;
2711         nrOfSeekAds = 0;
2712         soughtPending = TRUE;
2713         SendToICS(ics_prefix);
2714         SendToICS("sought\n"); // should this be "sought all"?
2715     } else { // issue challenge based on clicked ad
2716         int dist = 10000; int i, closest = 0, second = 0;
2717         for(i=0; i<nrOfSeekAds; i++) {
2718             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2719             if(d < dist) { dist = d; closest = i; }
2720             second += (d - zList[i] < 120); // count in-range ads
2721             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2722         }
2723         if(dist < 120) {
2724             char buf[MSG_SIZ];
2725             second = (second > 1);
2726             if(displayed != closest || second != lastSecond) {
2727                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2728                 lastSecond = second; displayed = closest;
2729             }
2730             if(click == Press) {
2731                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2732                 lastDown = closest;
2733                 return TRUE;
2734             } // on press 'hit', only show info
2735             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2736             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2737             SendToICS(ics_prefix);
2738             SendToICS(buf);
2739             return TRUE; // let incoming board of started game pop down the graph
2740         } else if(click == Release) { // release 'miss' is ignored
2741             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2742             if(moving == 2) { // right up-click
2743                 nrOfSeekAds = 0; // refresh graph
2744                 soughtPending = TRUE;
2745                 SendToICS(ics_prefix);
2746                 SendToICS("sought\n"); // should this be "sought all"?
2747             }
2748             return TRUE;
2749         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2750         // press miss or release hit 'pop down' seek graph
2751         seekGraphUp = FALSE;
2752         DrawPosition(TRUE, NULL);
2753     }
2754     return TRUE;
2755 }
2756
2757 void
2758 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2759 {
2760 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2761 #define STARTED_NONE 0
2762 #define STARTED_MOVES 1
2763 #define STARTED_BOARD 2
2764 #define STARTED_OBSERVE 3
2765 #define STARTED_HOLDINGS 4
2766 #define STARTED_CHATTER 5
2767 #define STARTED_COMMENT 6
2768 #define STARTED_MOVES_NOHIDE 7
2769
2770     static int started = STARTED_NONE;
2771     static char parse[20000];
2772     static int parse_pos = 0;
2773     static char buf[BUF_SIZE + 1];
2774     static int firstTime = TRUE, intfSet = FALSE;
2775     static ColorClass prevColor = ColorNormal;
2776     static int savingComment = FALSE;
2777     static int cmatch = 0; // continuation sequence match
2778     char *bp;
2779     char str[MSG_SIZ];
2780     int i, oldi;
2781     int buf_len;
2782     int next_out;
2783     int tkind;
2784     int backup;    /* [DM] For zippy color lines */
2785     char *p;
2786     char talker[MSG_SIZ]; // [HGM] chat
2787     int channel;
2788
2789     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2790
2791     if (appData.debugMode) {
2792       if (!error) {
2793         fprintf(debugFP, "<ICS: ");
2794         show_bytes(debugFP, data, count);
2795         fprintf(debugFP, "\n");
2796       }
2797     }
2798
2799     if (appData.debugMode) { int f = forwardMostMove;
2800         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2801                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2802                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2803     }
2804     if (count > 0) {
2805         /* If last read ended with a partial line that we couldn't parse,
2806            prepend it to the new read and try again. */
2807         if (leftover_len > 0) {
2808             for (i=0; i<leftover_len; i++)
2809               buf[i] = buf[leftover_start + i];
2810         }
2811
2812     /* copy new characters into the buffer */
2813     bp = buf + leftover_len;
2814     buf_len=leftover_len;
2815     for (i=0; i<count; i++)
2816     {
2817         // ignore these
2818         if (data[i] == '\r')
2819             continue;
2820
2821         // join lines split by ICS?
2822         if (!appData.noJoin)
2823         {
2824             /*
2825                 Joining just consists of finding matches against the
2826                 continuation sequence, and discarding that sequence
2827                 if found instead of copying it.  So, until a match
2828                 fails, there's nothing to do since it might be the
2829                 complete sequence, and thus, something we don't want
2830                 copied.
2831             */
2832             if (data[i] == cont_seq[cmatch])
2833             {
2834                 cmatch++;
2835                 if (cmatch == strlen(cont_seq))
2836                 {
2837                     cmatch = 0; // complete match.  just reset the counter
2838
2839                     /*
2840                         it's possible for the ICS to not include the space
2841                         at the end of the last word, making our [correct]
2842                         join operation fuse two separate words.  the server
2843                         does this when the space occurs at the width setting.
2844                     */
2845                     if (!buf_len || buf[buf_len-1] != ' ')
2846                     {
2847                         *bp++ = ' ';
2848                         buf_len++;
2849                     }
2850                 }
2851                 continue;
2852             }
2853             else if (cmatch)
2854             {
2855                 /*
2856                     match failed, so we have to copy what matched before
2857                     falling through and copying this character.  In reality,
2858                     this will only ever be just the newline character, but
2859                     it doesn't hurt to be precise.
2860                 */
2861                 strncpy(bp, cont_seq, cmatch);
2862                 bp += cmatch;
2863                 buf_len += cmatch;
2864                 cmatch = 0;
2865             }
2866         }
2867
2868         // copy this char
2869         *bp++ = data[i];
2870         buf_len++;
2871     }
2872
2873         buf[buf_len] = NULLCHAR;
2874 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2875         next_out = 0;
2876         leftover_start = 0;
2877
2878         i = 0;
2879         while (i < buf_len) {
2880             /* Deal with part of the TELNET option negotiation
2881                protocol.  We refuse to do anything beyond the
2882                defaults, except that we allow the WILL ECHO option,
2883                which ICS uses to turn off password echoing when we are
2884                directly connected to it.  We reject this option
2885                if localLineEditing mode is on (always on in xboard)
2886                and we are talking to port 23, which might be a real
2887                telnet server that will try to keep WILL ECHO on permanently.
2888              */
2889             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2890                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2891                 unsigned char option;
2892                 oldi = i;
2893                 switch ((unsigned char) buf[++i]) {
2894                   case TN_WILL:
2895                     if (appData.debugMode)
2896                       fprintf(debugFP, "\n<WILL ");
2897                     switch (option = (unsigned char) buf[++i]) {
2898                       case TN_ECHO:
2899                         if (appData.debugMode)
2900                           fprintf(debugFP, "ECHO ");
2901                         /* Reply only if this is a change, according
2902                            to the protocol rules. */
2903                         if (remoteEchoOption) break;
2904                         if (appData.localLineEditing &&
2905                             atoi(appData.icsPort) == TN_PORT) {
2906                             TelnetRequest(TN_DONT, TN_ECHO);
2907                         } else {
2908                             EchoOff();
2909                             TelnetRequest(TN_DO, TN_ECHO);
2910                             remoteEchoOption = TRUE;
2911                         }
2912                         break;
2913                       default:
2914                         if (appData.debugMode)
2915                           fprintf(debugFP, "%d ", option);
2916                         /* Whatever this is, we don't want it. */
2917                         TelnetRequest(TN_DONT, option);
2918                         break;
2919                     }
2920                     break;
2921                   case TN_WONT:
2922                     if (appData.debugMode)
2923                       fprintf(debugFP, "\n<WONT ");
2924                     switch (option = (unsigned char) buf[++i]) {
2925                       case TN_ECHO:
2926                         if (appData.debugMode)
2927                           fprintf(debugFP, "ECHO ");
2928                         /* Reply only if this is a change, according
2929                            to the protocol rules. */
2930                         if (!remoteEchoOption) break;
2931                         EchoOn();
2932                         TelnetRequest(TN_DONT, TN_ECHO);
2933                         remoteEchoOption = FALSE;
2934                         break;
2935                       default:
2936                         if (appData.debugMode)
2937                           fprintf(debugFP, "%d ", (unsigned char) option);
2938                         /* Whatever this is, it must already be turned
2939                            off, because we never agree to turn on
2940                            anything non-default, so according to the
2941                            protocol rules, we don't reply. */
2942                         break;
2943                     }
2944                     break;
2945                   case TN_DO:
2946                     if (appData.debugMode)
2947                       fprintf(debugFP, "\n<DO ");
2948                     switch (option = (unsigned char) buf[++i]) {
2949                       default:
2950                         /* Whatever this is, we refuse to do it. */
2951                         if (appData.debugMode)
2952                           fprintf(debugFP, "%d ", option);
2953                         TelnetRequest(TN_WONT, option);
2954                         break;
2955                     }
2956                     break;
2957                   case TN_DONT:
2958                     if (appData.debugMode)
2959                       fprintf(debugFP, "\n<DONT ");
2960                     switch (option = (unsigned char) buf[++i]) {
2961                       default:
2962                         if (appData.debugMode)
2963                           fprintf(debugFP, "%d ", option);
2964                         /* Whatever this is, we are already not doing
2965                            it, because we never agree to do anything
2966                            non-default, so according to the protocol
2967                            rules, we don't reply. */
2968                         break;
2969                     }
2970                     break;
2971                   case TN_IAC:
2972                     if (appData.debugMode)
2973                       fprintf(debugFP, "\n<IAC ");
2974                     /* Doubled IAC; pass it through */
2975                     i--;
2976                     break;
2977                   default:
2978                     if (appData.debugMode)
2979                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2980                     /* Drop all other telnet commands on the floor */
2981                     break;
2982                 }
2983                 if (oldi > next_out)
2984                   SendToPlayer(&buf[next_out], oldi - next_out);
2985                 if (++i > next_out)
2986                   next_out = i;
2987                 continue;
2988             }
2989
2990             /* OK, this at least will *usually* work */
2991             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2992                 loggedOn = TRUE;
2993             }
2994
2995             if (loggedOn && !intfSet) {
2996                 if (ics_type == ICS_ICC) {
2997                   snprintf(str, MSG_SIZ,
2998                           "/set-quietly interface %s\n/set-quietly style 12\n",
2999                           programVersion);
3000                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3001                       strcat(str, "/set-2 51 1\n/set seek 1\n");
3002                 } else if (ics_type == ICS_CHESSNET) {
3003                   snprintf(str, MSG_SIZ, "/style 12\n");
3004                 } else {
3005                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
3006                   strcat(str, programVersion);
3007                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
3008                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3009                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
3010 #ifdef WIN32
3011                   strcat(str, "$iset nohighlight 1\n");
3012 #endif
3013                   strcat(str, "$iset lock 1\n$style 12\n");
3014                 }
3015                 SendToICS(str);
3016                 NotifyFrontendLogin();
3017                 intfSet = TRUE;
3018             }
3019
3020             if (started == STARTED_COMMENT) {
3021                 /* Accumulate characters in comment */
3022                 parse[parse_pos++] = buf[i];
3023                 if (buf[i] == '\n') {
3024                     parse[parse_pos] = NULLCHAR;
3025                     if(chattingPartner>=0) {
3026                         char mess[MSG_SIZ];
3027                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
3028                         OutputChatMessage(chattingPartner, mess);
3029                         chattingPartner = -1;
3030                         next_out = i+1; // [HGM] suppress printing in ICS window
3031                     } else
3032                     if(!suppressKibitz) // [HGM] kibitz
3033                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
3034                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
3035                         int nrDigit = 0, nrAlph = 0, j;
3036                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
3037                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
3038                         parse[parse_pos] = NULLCHAR;
3039                         // try to be smart: if it does not look like search info, it should go to
3040                         // ICS interaction window after all, not to engine-output window.
3041                         for(j=0; j<parse_pos; j++) { // count letters and digits
3042                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
3043                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
3044                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
3045                         }
3046                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
3047                             int depth=0; float score;
3048                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
3049                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
3050                                 pvInfoList[forwardMostMove-1].depth = depth;
3051                                 pvInfoList[forwardMostMove-1].score = 100*score;
3052                             }
3053                             OutputKibitz(suppressKibitz, parse);
3054                         } else {
3055                             char tmp[MSG_SIZ];
3056                             if(gameMode == IcsObserving) // restore original ICS messages
3057                               /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3058                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3059                             else
3060                             /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3061                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3062                             SendToPlayer(tmp, strlen(tmp));
3063                         }
3064                         next_out = i+1; // [HGM] suppress printing in ICS window
3065                     }
3066                     started = STARTED_NONE;
3067                 } else {
3068                     /* Don't match patterns against characters in comment */
3069                     i++;
3070                     continue;
3071                 }
3072             }
3073             if (started == STARTED_CHATTER) {
3074                 if (buf[i] != '\n') {
3075                     /* Don't match patterns against characters in chatter */
3076                     i++;
3077                     continue;
3078                 }
3079                 started = STARTED_NONE;
3080                 if(suppressKibitz) next_out = i+1;
3081             }
3082
3083             /* Kludge to deal with rcmd protocol */
3084             if (firstTime && looking_at(buf, &i, "\001*")) {
3085                 DisplayFatalError(&buf[1], 0, 1);
3086                 continue;
3087             } else {
3088                 firstTime = FALSE;
3089             }
3090
3091             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3092                 ics_type = ICS_ICC;
3093                 ics_prefix = "/";
3094                 if (appData.debugMode)
3095                   fprintf(debugFP, "ics_type %d\n", ics_type);
3096                 continue;
3097             }
3098             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3099                 ics_type = ICS_FICS;
3100                 ics_prefix = "$";
3101                 if (appData.debugMode)
3102                   fprintf(debugFP, "ics_type %d\n", ics_type);
3103                 continue;
3104             }
3105             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3106                 ics_type = ICS_CHESSNET;
3107                 ics_prefix = "/";
3108                 if (appData.debugMode)
3109                   fprintf(debugFP, "ics_type %d\n", ics_type);
3110                 continue;
3111             }
3112
3113             if (!loggedOn &&
3114                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3115                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3116                  looking_at(buf, &i, "will be \"*\""))) {
3117               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3118               continue;
3119             }
3120
3121             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3122               char buf[MSG_SIZ];
3123               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3124               DisplayIcsInteractionTitle(buf);
3125               have_set_title = TRUE;
3126             }
3127
3128             /* skip finger notes */
3129             if (started == STARTED_NONE &&
3130                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3131                  (buf[i] == '1' && buf[i+1] == '0')) &&
3132                 buf[i+2] == ':' && buf[i+3] == ' ') {
3133               started = STARTED_CHATTER;
3134               i += 3;
3135               continue;
3136             }
3137
3138             oldi = i;
3139             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3140             if(appData.seekGraph) {
3141                 if(soughtPending && MatchSoughtLine(buf+i)) {
3142                     i = strstr(buf+i, "rated") - buf;
3143                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3144                     next_out = leftover_start = i;
3145                     started = STARTED_CHATTER;
3146                     suppressKibitz = TRUE;
3147                     continue;
3148                 }
3149                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3150                         && looking_at(buf, &i, "* ads displayed")) {
3151                     soughtPending = FALSE;
3152                     seekGraphUp = TRUE;
3153                     DrawSeekGraph();
3154                     continue;
3155                 }
3156                 if(appData.autoRefresh) {
3157                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3158                         int s = (ics_type == ICS_ICC); // ICC format differs
3159                         if(seekGraphUp)
3160                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3161                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3162                         looking_at(buf, &i, "*% "); // eat prompt
3163                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3164                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3165                         next_out = i; // suppress
3166                         continue;
3167                     }
3168                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3169                         char *p = star_match[0];
3170                         while(*p) {
3171                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3172                             while(*p && *p++ != ' '); // next
3173                         }
3174                         looking_at(buf, &i, "*% "); // eat prompt
3175                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3176                         next_out = i;
3177                         continue;
3178                     }
3179                 }
3180             }
3181
3182             /* skip formula vars */
3183             if (started == STARTED_NONE &&
3184                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3185               started = STARTED_CHATTER;
3186               i += 3;
3187               continue;
3188             }
3189
3190             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3191             if (appData.autoKibitz && started == STARTED_NONE &&
3192                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3193                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3194                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3195                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3196                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3197                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3198                         suppressKibitz = TRUE;
3199                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3200                         next_out = i;
3201                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3202                                 && (gameMode == IcsPlayingWhite)) ||
3203                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3204                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3205                             started = STARTED_CHATTER; // own kibitz we simply discard
3206                         else {
3207                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3208                             parse_pos = 0; parse[0] = NULLCHAR;
3209                             savingComment = TRUE;
3210                             suppressKibitz = gameMode != IcsObserving ? 2 :
3211                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3212                         }
3213                         continue;
3214                 } else
3215                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3216                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3217                          && atoi(star_match[0])) {
3218                     // suppress the acknowledgements of our own autoKibitz
3219                     char *p;
3220                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3221                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3222                     SendToPlayer(star_match[0], strlen(star_match[0]));
3223                     if(looking_at(buf, &i, "*% ")) // eat prompt
3224                         suppressKibitz = FALSE;
3225                     next_out = i;
3226                     continue;
3227                 }
3228             } // [HGM] kibitz: end of patch
3229
3230             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3231
3232             // [HGM] chat: intercept tells by users for which we have an open chat window
3233             channel = -1;
3234             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3235                                            looking_at(buf, &i, "* whispers:") ||
3236                                            looking_at(buf, &i, "* kibitzes:") ||
3237                                            looking_at(buf, &i, "* shouts:") ||
3238                                            looking_at(buf, &i, "* c-shouts:") ||
3239                                            looking_at(buf, &i, "--> * ") ||
3240                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3241                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3242                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3243                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3244                 int p;
3245                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3246                 chattingPartner = -1;
3247
3248                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3249                 for(p=0; p<MAX_CHAT; p++) {
3250                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3251                     talker[0] = '['; strcat(talker, "] ");
3252                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3253                     chattingPartner = p; break;
3254                     }
3255                 } else
3256                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3257                 for(p=0; p<MAX_CHAT; p++) {
3258                     if(!strcmp("kibitzes", chatPartner[p])) {
3259                         talker[0] = '['; strcat(talker, "] ");
3260                         chattingPartner = p; break;
3261                     }
3262                 } else
3263                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3264                 for(p=0; p<MAX_CHAT; p++) {
3265                     if(!strcmp("whispers", chatPartner[p])) {
3266                         talker[0] = '['; strcat(talker, "] ");
3267                         chattingPartner = p; break;
3268                     }
3269                 } else
3270                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3271                   if(buf[i-8] == '-' && buf[i-3] == 't')
3272                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3273                     if(!strcmp("c-shouts", chatPartner[p])) {
3274                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3275                         chattingPartner = p; break;
3276                     }
3277                   }
3278                   if(chattingPartner < 0)
3279                   for(p=0; p<MAX_CHAT; p++) {
3280                     if(!strcmp("shouts", chatPartner[p])) {
3281                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3282                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3283                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3284                         chattingPartner = p; break;
3285                     }
3286                   }
3287                 }
3288                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3289                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3290                     talker[0] = 0; Colorize(ColorTell, FALSE);
3291                     chattingPartner = p; break;
3292                 }
3293                 if(chattingPartner<0) i = oldi; else {
3294                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3295                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3296                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3297                     started = STARTED_COMMENT;
3298                     parse_pos = 0; parse[0] = NULLCHAR;
3299                     savingComment = 3 + chattingPartner; // counts as TRUE
3300                     suppressKibitz = TRUE;
3301                     continue;
3302                 }
3303             } // [HGM] chat: end of patch
3304
3305           backup = i;
3306             if (appData.zippyTalk || appData.zippyPlay) {
3307                 /* [DM] Backup address for color zippy lines */
3308 #if ZIPPY
3309                if (loggedOn == TRUE)
3310                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3311                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3312 #endif
3313             } // [DM] 'else { ' deleted
3314                 if (
3315                     /* Regular tells and says */
3316                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3317                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3318                     looking_at(buf, &i, "* says: ") ||
3319                     /* Don't color "message" or "messages" output */
3320                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3321                     looking_at(buf, &i, "*. * at *:*: ") ||
3322                     looking_at(buf, &i, "--* (*:*): ") ||
3323                     /* Message notifications (same color as tells) */
3324                     looking_at(buf, &i, "* has left a message ") ||
3325                     looking_at(buf, &i, "* just sent you a message:\n") ||
3326                     /* Whispers and kibitzes */
3327                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3328                     looking_at(buf, &i, "* kibitzes: ") ||
3329                     /* Channel tells */
3330                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3331
3332                   if (tkind == 1 && strchr(star_match[0], ':')) {
3333                       /* Avoid "tells you:" spoofs in channels */
3334                      tkind = 3;
3335                   }
3336                   if (star_match[0][0] == NULLCHAR ||
3337                       strchr(star_match[0], ' ') ||
3338                       (tkind == 3 && strchr(star_match[1], ' '))) {
3339                     /* Reject bogus matches */
3340                     i = oldi;
3341                   } else {
3342                     if (appData.colorize) {
3343                       if (oldi > next_out) {
3344                         SendToPlayer(&buf[next_out], oldi - next_out);
3345                         next_out = oldi;
3346                       }
3347                       switch (tkind) {
3348                       case 1:
3349                         Colorize(ColorTell, FALSE);
3350                         curColor = ColorTell;
3351                         break;
3352                       case 2:
3353                         Colorize(ColorKibitz, FALSE);
3354                         curColor = ColorKibitz;
3355                         break;
3356                       case 3:
3357                         p = strrchr(star_match[1], '(');
3358                         if (p == NULL) {
3359                           p = star_match[1];
3360                         } else {
3361                           p++;
3362                         }
3363                         if (atoi(p) == 1) {
3364                           Colorize(ColorChannel1, FALSE);
3365                           curColor = ColorChannel1;
3366                         } else {
3367                           Colorize(ColorChannel, FALSE);
3368                           curColor = ColorChannel;
3369                         }
3370                         break;
3371                       case 5:
3372                         curColor = ColorNormal;
3373                         break;
3374                       }
3375                     }
3376                     if (started == STARTED_NONE && appData.autoComment &&
3377                         (gameMode == IcsObserving ||
3378                          gameMode == IcsPlayingWhite ||
3379                          gameMode == IcsPlayingBlack)) {
3380                       parse_pos = i - oldi;
3381                       memcpy(parse, &buf[oldi], parse_pos);
3382                       parse[parse_pos] = NULLCHAR;
3383                       started = STARTED_COMMENT;
3384                       savingComment = TRUE;
3385                     } else {
3386                       started = STARTED_CHATTER;
3387                       savingComment = FALSE;
3388                     }
3389                     loggedOn = TRUE;
3390                     continue;
3391                   }
3392                 }
3393
3394                 if (looking_at(buf, &i, "* s-shouts: ") ||
3395                     looking_at(buf, &i, "* c-shouts: ")) {
3396                     if (appData.colorize) {
3397                         if (oldi > next_out) {
3398                             SendToPlayer(&buf[next_out], oldi - next_out);
3399                             next_out = oldi;
3400                         }
3401                         Colorize(ColorSShout, FALSE);
3402                         curColor = ColorSShout;
3403                     }
3404                     loggedOn = TRUE;
3405                     started = STARTED_CHATTER;
3406                     continue;
3407                 }
3408
3409                 if (looking_at(buf, &i, "--->")) {
3410                     loggedOn = TRUE;
3411                     continue;
3412                 }
3413
3414                 if (looking_at(buf, &i, "* shouts: ") ||
3415                     looking_at(buf, &i, "--> ")) {
3416                     if (appData.colorize) {
3417                         if (oldi > next_out) {
3418                             SendToPlayer(&buf[next_out], oldi - next_out);
3419                             next_out = oldi;
3420                         }
3421                         Colorize(ColorShout, FALSE);
3422                         curColor = ColorShout;
3423                     }
3424                     loggedOn = TRUE;
3425                     started = STARTED_CHATTER;
3426                     continue;
3427                 }
3428
3429                 if (looking_at( buf, &i, "Challenge:")) {
3430                     if (appData.colorize) {
3431                         if (oldi > next_out) {
3432                             SendToPlayer(&buf[next_out], oldi - next_out);
3433                             next_out = oldi;
3434                         }
3435                         Colorize(ColorChallenge, FALSE);
3436                         curColor = ColorChallenge;
3437                     }
3438                     loggedOn = TRUE;
3439                     continue;
3440                 }
3441
3442                 if (looking_at(buf, &i, "* offers you") ||
3443                     looking_at(buf, &i, "* offers to be") ||
3444                     looking_at(buf, &i, "* would like to") ||
3445                     looking_at(buf, &i, "* requests to") ||
3446                     looking_at(buf, &i, "Your opponent offers") ||
3447                     looking_at(buf, &i, "Your opponent requests")) {
3448
3449                     if (appData.colorize) {
3450                         if (oldi > next_out) {
3451                             SendToPlayer(&buf[next_out], oldi - next_out);
3452                             next_out = oldi;
3453                         }
3454                         Colorize(ColorRequest, FALSE);
3455                         curColor = ColorRequest;
3456                     }
3457                     continue;
3458                 }
3459
3460                 if (looking_at(buf, &i, "* (*) seeking")) {
3461                     if (appData.colorize) {
3462                         if (oldi > next_out) {
3463                             SendToPlayer(&buf[next_out], oldi - next_out);
3464                             next_out = oldi;
3465                         }
3466                         Colorize(ColorSeek, FALSE);
3467                         curColor = ColorSeek;
3468                     }
3469                     continue;
3470             }
3471
3472           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3473
3474             if (looking_at(buf, &i, "\\   ")) {
3475                 if (prevColor != ColorNormal) {
3476                     if (oldi > next_out) {
3477                         SendToPlayer(&buf[next_out], oldi - next_out);
3478                         next_out = oldi;
3479                     }
3480                     Colorize(prevColor, TRUE);
3481                     curColor = prevColor;
3482                 }
3483                 if (savingComment) {
3484                     parse_pos = i - oldi;
3485                     memcpy(parse, &buf[oldi], parse_pos);
3486                     parse[parse_pos] = NULLCHAR;
3487                     started = STARTED_COMMENT;
3488                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3489                         chattingPartner = savingComment - 3; // kludge to remember the box
3490                 } else {
3491                     started = STARTED_CHATTER;
3492                 }
3493                 continue;
3494             }
3495
3496             if (looking_at(buf, &i, "Black Strength :") ||
3497                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3498                 looking_at(buf, &i, "<10>") ||
3499                 looking_at(buf, &i, "#@#")) {
3500                 /* Wrong board style */
3501                 loggedOn = TRUE;
3502                 SendToICS(ics_prefix);
3503                 SendToICS("set style 12\n");
3504                 SendToICS(ics_prefix);
3505                 SendToICS("refresh\n");
3506                 continue;
3507             }
3508
3509             if (looking_at(buf, &i, "login:")) {
3510               if (!have_sent_ICS_logon) {
3511                 if(ICSInitScript())
3512                   have_sent_ICS_logon = 1;
3513                 else // no init script was found
3514                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3515               } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3516                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3517               }
3518                 continue;
3519             }
3520
3521             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3522                 (looking_at(buf, &i, "\n<12> ") ||
3523                  looking_at(buf, &i, "<12> "))) {
3524                 loggedOn = TRUE;
3525                 if (oldi > next_out) {
3526                     SendToPlayer(&buf[next_out], oldi - next_out);
3527                 }
3528                 next_out = i;
3529                 started = STARTED_BOARD;
3530                 parse_pos = 0;
3531                 continue;
3532             }
3533
3534             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3535                 looking_at(buf, &i, "<b1> ")) {
3536                 if (oldi > next_out) {
3537                     SendToPlayer(&buf[next_out], oldi - next_out);
3538                 }
3539                 next_out = i;
3540                 started = STARTED_HOLDINGS;
3541                 parse_pos = 0;
3542                 continue;
3543             }
3544
3545             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3546                 loggedOn = TRUE;
3547                 /* Header for a move list -- first line */
3548
3549                 switch (ics_getting_history) {
3550                   case H_FALSE:
3551                     switch (gameMode) {
3552                       case IcsIdle:
3553                       case BeginningOfGame:
3554                         /* User typed "moves" or "oldmoves" while we
3555                            were idle.  Pretend we asked for these
3556                            moves and soak them up so user can step
3557                            through them and/or save them.
3558                            */
3559                         Reset(FALSE, TRUE);
3560                         gameMode = IcsObserving;
3561                         ModeHighlight();
3562                         ics_gamenum = -1;
3563                         ics_getting_history = H_GOT_UNREQ_HEADER;
3564                         break;
3565                       case EditGame: /*?*/
3566                       case EditPosition: /*?*/
3567                         /* Should above feature work in these modes too? */
3568                         /* For now it doesn't */
3569                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3570                         break;
3571                       default:
3572                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3573                         break;
3574                     }
3575                     break;
3576                   case H_REQUESTED:
3577                     /* Is this the right one? */
3578                     if (gameInfo.white && gameInfo.black &&
3579                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3580                         strcmp(gameInfo.black, star_match[2]) == 0) {
3581                         /* All is well */
3582                         ics_getting_history = H_GOT_REQ_HEADER;
3583                     }
3584                     break;
3585                   case H_GOT_REQ_HEADER:
3586                   case H_GOT_UNREQ_HEADER:
3587                   case H_GOT_UNWANTED_HEADER:
3588                   case H_GETTING_MOVES:
3589                     /* Should not happen */
3590                     DisplayError(_("Error gathering move list: two headers"), 0);
3591                     ics_getting_history = H_FALSE;
3592                     break;
3593                 }
3594
3595                 /* Save player ratings into gameInfo if needed */
3596                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3597                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3598                     (gameInfo.whiteRating == -1 ||
3599                      gameInfo.blackRating == -1)) {
3600
3601                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3602                     gameInfo.blackRating = string_to_rating(star_match[3]);
3603                     if (appData.debugMode)
3604                       fprintf(debugFP, "Ratings from header: W %d, B %d\n",
3605                               gameInfo.whiteRating, gameInfo.blackRating);
3606                 }
3607                 continue;
3608             }
3609
3610             if (looking_at(buf, &i,
3611               "* * match, initial time: * minute*, increment: * second")) {
3612                 /* Header for a move list -- second line */
3613                 /* Initial board will follow if this is a wild game */
3614                 if (gameInfo.event != NULL) free(gameInfo.event);
3615                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3616                 gameInfo.event = StrSave(str);
3617                 /* [HGM] we switched variant. Translate boards if needed. */
3618                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3619                 continue;
3620             }
3621
3622             if (looking_at(buf, &i, "Move  ")) {
3623                 /* Beginning of a move list */
3624                 switch (ics_getting_history) {
3625                   case H_FALSE:
3626                     /* Normally should not happen */
3627                     /* Maybe user hit reset while we were parsing */
3628                     break;
3629                   case H_REQUESTED:
3630                     /* Happens if we are ignoring a move list that is not
3631                      * the one we just requested.  Common if the user
3632                      * tries to observe two games without turning off
3633                      * getMoveList */
3634                     break;
3635                   case H_GETTING_MOVES:
3636                     /* Should not happen */
3637                     DisplayError(_("Error gathering move list: nested"), 0);
3638                     ics_getting_history = H_FALSE;
3639                     break;
3640                   case H_GOT_REQ_HEADER:
3641                     ics_getting_history = H_GETTING_MOVES;
3642                     started = STARTED_MOVES;
3643                     parse_pos = 0;
3644                     if (oldi > next_out) {
3645                         SendToPlayer(&buf[next_out], oldi - next_out);
3646                     }
3647                     break;
3648                   case H_GOT_UNREQ_HEADER:
3649                     ics_getting_history = H_GETTING_MOVES;
3650                     started = STARTED_MOVES_NOHIDE;
3651                     parse_pos = 0;
3652                     break;
3653                   case H_GOT_UNWANTED_HEADER:
3654                     ics_getting_history = H_FALSE;
3655                     break;
3656                 }
3657                 continue;
3658             }
3659
3660             if (looking_at(buf, &i, "% ") ||
3661                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3662                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3663                 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3664                     soughtPending = FALSE;
3665                     seekGraphUp = TRUE;
3666                     DrawSeekGraph();
3667                 }
3668                 if(suppressKibitz) next_out = i;
3669                 savingComment = FALSE;
3670                 suppressKibitz = 0;
3671                 switch (started) {
3672                   case STARTED_MOVES:
3673                   case STARTED_MOVES_NOHIDE:
3674                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3675                     parse[parse_pos + i - oldi] = NULLCHAR;
3676                     ParseGameHistory(parse);
3677 #if ZIPPY
3678                     if (appData.zippyPlay && first.initDone) {
3679                         FeedMovesToProgram(&first, forwardMostMove);
3680                         if (gameMode == IcsPlayingWhite) {
3681                             if (WhiteOnMove(forwardMostMove)) {
3682                                 if (first.sendTime) {
3683                                   if (first.useColors) {
3684                                     SendToProgram("black\n", &first);
3685                                   }
3686                                   SendTimeRemaining(&first, TRUE);
3687                                 }
3688                                 if (first.useColors) {
3689                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3690                                 }
3691                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3692                                 first.maybeThinking = TRUE;
3693                             } else {
3694                                 if (first.usePlayother) {
3695                                   if (first.sendTime) {
3696                                     SendTimeRemaining(&first, TRUE);
3697                                   }
3698                                   SendToProgram("playother\n", &first);
3699                                   firstMove = FALSE;
3700                                 } else {
3701                                   firstMove = TRUE;
3702                                 }
3703                             }
3704                         } else if (gameMode == IcsPlayingBlack) {
3705                             if (!WhiteOnMove(forwardMostMove)) {
3706                                 if (first.sendTime) {
3707                                   if (first.useColors) {
3708                                     SendToProgram("white\n", &first);
3709                                   }
3710                                   SendTimeRemaining(&first, FALSE);
3711                                 }
3712                                 if (first.useColors) {
3713                                   SendToProgram("black\n", &first);
3714                                 }
3715                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3716                                 first.maybeThinking = TRUE;
3717                             } else {
3718                                 if (first.usePlayother) {
3719                                   if (first.sendTime) {
3720                                     SendTimeRemaining(&first, FALSE);
3721                                   }
3722                                   SendToProgram("playother\n", &first);
3723                                   firstMove = FALSE;
3724                                 } else {
3725                                   firstMove = TRUE;
3726                                 }
3727                             }
3728                         }
3729                     }
3730 #endif
3731                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3732                         /* Moves came from oldmoves or moves command
3733                            while we weren't doing anything else.
3734                            */
3735                         currentMove = forwardMostMove;
3736                         ClearHighlights();/*!!could figure this out*/
3737                         flipView = appData.flipView;
3738                         DrawPosition(TRUE, boards[currentMove]);
3739                         DisplayBothClocks();
3740                         snprintf(str, MSG_SIZ, "%s %s %s",
3741                                 gameInfo.white, _("vs."),  gameInfo.black);
3742                         DisplayTitle(str);
3743                         gameMode = IcsIdle;
3744                     } else {
3745                         /* Moves were history of an active game */
3746                         if (gameInfo.resultDetails != NULL) {
3747                             free(gameInfo.resultDetails);
3748                             gameInfo.resultDetails = NULL;
3749                         }
3750                     }
3751                     HistorySet(parseList, backwardMostMove,
3752                                forwardMostMove, currentMove-1);
3753                     DisplayMove(currentMove - 1);
3754                     if (started == STARTED_MOVES) next_out = i;
3755                     started = STARTED_NONE;
3756                     ics_getting_history = H_FALSE;
3757                     break;
3758
3759                   case STARTED_OBSERVE:
3760                     started = STARTED_NONE;
3761                     SendToICS(ics_prefix);
3762                     SendToICS("refresh\n");
3763                     break;
3764
3765                   default:
3766                     break;
3767                 }
3768                 if(bookHit) { // [HGM] book: simulate book reply
3769                     static char bookMove[MSG_SIZ]; // a bit generous?
3770
3771                     programStats.nodes = programStats.depth = programStats.time =
3772                     programStats.score = programStats.got_only_move = 0;
3773                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3774
3775                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3776                     strcat(bookMove, bookHit);
3777                     HandleMachineMove(bookMove, &first);
3778                 }
3779                 continue;
3780             }
3781
3782             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3783                  started == STARTED_HOLDINGS ||
3784                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3785                 /* Accumulate characters in move list or board */
3786                 parse[parse_pos++] = buf[i];
3787             }
3788
3789             /* Start of game messages.  Mostly we detect start of game
3790                when the first board image arrives.  On some versions
3791                of the ICS, though, we need to do a "refresh" after starting
3792                to observe in order to get the current board right away. */
3793             if (looking_at(buf, &i, "Adding game * to observation list")) {
3794                 started = STARTED_OBSERVE;
3795                 continue;
3796             }
3797
3798             /* Handle auto-observe */
3799             if (appData.autoObserve &&
3800                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3801                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3802                 char *player;
3803                 /* Choose the player that was highlighted, if any. */
3804                 if (star_match[0][0] == '\033' ||
3805                     star_match[1][0] != '\033') {
3806                     player = star_match[0];
3807                 } else {
3808                     player = star_match[2];
3809                 }
3810                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3811                         ics_prefix, StripHighlightAndTitle(player));
3812                 SendToICS(str);
3813
3814                 /* Save ratings from notify string */
3815                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3816                 player1Rating = string_to_rating(star_match[1]);
3817                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3818                 player2Rating = string_to_rating(star_match[3]);
3819
3820                 if (appData.debugMode)
3821                   fprintf(debugFP,
3822                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3823                           player1Name, player1Rating,
3824                           player2Name, player2Rating);
3825
3826                 continue;
3827             }
3828
3829             /* Deal with automatic examine mode after a game,
3830                and with IcsObserving -> IcsExamining transition */
3831             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3832                 looking_at(buf, &i, "has made you an examiner of game *")) {
3833
3834                 int gamenum = atoi(star_match[0]);
3835                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3836                     gamenum == ics_gamenum) {
3837                     /* We were already playing or observing this game;
3838                        no need to refetch history */
3839                     gameMode = IcsExamining;
3840                     if (pausing) {
3841                         pauseExamForwardMostMove = forwardMostMove;
3842                     } else if (currentMove < forwardMostMove) {
3843                         ForwardInner(forwardMostMove);
3844                     }
3845                 } else {
3846                     /* I don't think this case really can happen */
3847                     SendToICS(ics_prefix);
3848                     SendToICS("refresh\n");
3849                 }
3850                 continue;
3851             }
3852
3853             /* Error messages */
3854 //          if (ics_user_moved) {
3855             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3856                 if (looking_at(buf, &i, "Illegal move") ||
3857                     looking_at(buf, &i, "Not a legal move") ||
3858                     looking_at(buf, &i, "Your king is in check") ||
3859                     looking_at(buf, &i, "It isn't your turn") ||
3860                     looking_at(buf, &i, "It is not your move")) {
3861                     /* Illegal move */
3862                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3863                         currentMove = forwardMostMove-1;
3864                         DisplayMove(currentMove - 1); /* before DMError */
3865                         DrawPosition(FALSE, boards[currentMove]);
3866                         SwitchClocks(forwardMostMove-1); // [HGM] race
3867                         DisplayBothClocks();
3868                     }
3869                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3870                     ics_user_moved = 0;
3871                     continue;
3872                 }
3873             }
3874
3875             if (looking_at(buf, &i, "still have time") ||
3876                 looking_at(buf, &i, "not out of time") ||
3877                 looking_at(buf, &i, "either player is out of time") ||
3878                 looking_at(buf, &i, "has timeseal; checking")) {
3879                 /* We must have called his flag a little too soon */
3880                 whiteFlag = blackFlag = FALSE;
3881                 continue;
3882             }
3883
3884             if (looking_at(buf, &i, "added * seconds to") ||
3885                 looking_at(buf, &i, "seconds were added to")) {
3886                 /* Update the clocks */
3887                 SendToICS(ics_prefix);
3888                 SendToICS("refresh\n");
3889                 continue;
3890             }
3891
3892             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3893                 ics_clock_paused = TRUE;
3894                 StopClocks();
3895                 continue;
3896             }
3897
3898             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3899                 ics_clock_paused = FALSE;
3900                 StartClocks();
3901                 continue;
3902             }
3903
3904             /* Grab player ratings from the Creating: message.
3905                Note we have to check for the special case when
3906                the ICS inserts things like [white] or [black]. */
3907             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3908                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3909                 /* star_matches:
3910                    0    player 1 name (not necessarily white)
3911                    1    player 1 rating
3912                    2    empty, white, or black (IGNORED)
3913                    3    player 2 name (not necessarily black)
3914                    4    player 2 rating
3915
3916                    The names/ratings are sorted out when the game
3917                    actually starts (below).
3918                 */
3919                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3920                 player1Rating = string_to_rating(star_match[1]);
3921                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3922                 player2Rating = string_to_rating(star_match[4]);
3923
3924                 if (appData.debugMode)
3925                   fprintf(debugFP,
3926                           "Ratings from 'Creating:' %s %d, %s %d\n",
3927                           player1Name, player1Rating,
3928                           player2Name, player2Rating);
3929
3930                 continue;
3931             }
3932
3933             /* Improved generic start/end-of-game messages */
3934             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3935                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3936                 /* If tkind == 0: */
3937                 /* star_match[0] is the game number */
3938                 /*           [1] is the white player's name */
3939                 /*           [2] is the black player's name */
3940                 /* For end-of-game: */
3941                 /*           [3] is the reason for the game end */
3942                 /*           [4] is a PGN end game-token, preceded by " " */
3943                 /* For start-of-game: */
3944                 /*           [3] begins with "Creating" or "Continuing" */
3945                 /*           [4] is " *" or empty (don't care). */
3946                 int gamenum = atoi(star_match[0]);
3947                 char *whitename, *blackname, *why, *endtoken;
3948                 ChessMove endtype = EndOfFile;
3949
3950                 if (tkind == 0) {
3951                   whitename = star_match[1];
3952                   blackname = star_match[2];
3953                   why = star_match[3];
3954                   endtoken = star_match[4];
3955                 } else {
3956                   whitename = star_match[1];
3957                   blackname = star_match[3];
3958                   why = star_match[5];
3959                   endtoken = star_match[6];
3960                 }
3961
3962                 /* Game start messages */
3963                 if (strncmp(why, "Creating ", 9) == 0 ||
3964                     strncmp(why, "Continuing ", 11) == 0) {
3965                     gs_gamenum = gamenum;
3966                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3967                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
3968                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3969 #if ZIPPY
3970                     if (appData.zippyPlay) {
3971                         ZippyGameStart(whitename, blackname);
3972                     }
3973 #endif /*ZIPPY*/
3974                     partnerBoardValid = FALSE; // [HGM] bughouse
3975                     continue;
3976                 }
3977
3978                 /* Game end messages */
3979                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3980                     ics_gamenum != gamenum) {
3981                     continue;
3982                 }
3983                 while (endtoken[0] == ' ') endtoken++;
3984                 switch (endtoken[0]) {
3985                   case '*':
3986                   default:
3987                     endtype = GameUnfinished;
3988                     break;
3989                   case '0':
3990                     endtype = BlackWins;
3991                     break;
3992                   case '1':
3993                     if (endtoken[1] == '/')
3994                       endtype = GameIsDrawn;
3995                     else
3996                       endtype = WhiteWins;
3997                     break;
3998                 }
3999                 GameEnds(endtype, why, GE_ICS);
4000 #if ZIPPY
4001                 if (appData.zippyPlay && first.initDone) {
4002                     ZippyGameEnd(endtype, why);
4003                     if (first.pr == NoProc) {
4004                       /* Start the next process early so that we'll
4005                          be ready for the next challenge */
4006                       StartChessProgram(&first);
4007                     }
4008                     /* Send "new" early, in case this command takes
4009                        a long time to finish, so that we'll be ready
4010                        for the next challenge. */
4011                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
4012                     Reset(TRUE, TRUE);
4013                 }
4014 #endif /*ZIPPY*/
4015                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
4016                 continue;
4017             }
4018
4019             if (looking_at(buf, &i, "Removing game * from observation") ||
4020                 looking_at(buf, &i, "no longer observing game *") ||
4021                 looking_at(buf, &i, "Game * (*) has no examiners")) {
4022                 if (gameMode == IcsObserving &&
4023                     atoi(star_match[0]) == ics_gamenum)
4024                   {
4025                       /* icsEngineAnalyze */
4026                       if (appData.icsEngineAnalyze) {
4027                             ExitAnalyzeMode();
4028                             ModeHighlight();
4029                       }
4030                       StopClocks();
4031                       gameMode = IcsIdle;
4032                       ics_gamenum = -1;
4033                       ics_user_moved = FALSE;
4034                   }
4035                 continue;
4036             }
4037
4038             if (looking_at(buf, &i, "no longer examining game *")) {
4039                 if (gameMode == IcsExamining &&
4040                     atoi(star_match[0]) == ics_gamenum)
4041                   {
4042                       gameMode = IcsIdle;
4043                       ics_gamenum = -1;
4044                       ics_user_moved = FALSE;
4045                   }
4046                 continue;
4047             }
4048
4049             /* Advance leftover_start past any newlines we find,
4050                so only partial lines can get reparsed */
4051             if (looking_at(buf, &i, "\n")) {
4052                 prevColor = curColor;
4053                 if (curColor != ColorNormal) {
4054                     if (oldi > next_out) {
4055                         SendToPlayer(&buf[next_out], oldi - next_out);
4056                         next_out = oldi;
4057                     }
4058                     Colorize(ColorNormal, FALSE);
4059                     curColor = ColorNormal;
4060                 }
4061                 if (started == STARTED_BOARD) {
4062                     started = STARTED_NONE;
4063                     parse[parse_pos] = NULLCHAR;
4064                     ParseBoard12(parse);
4065                     ics_user_moved = 0;
4066
4067                     /* Send premove here */
4068                     if (appData.premove) {
4069                       char str[MSG_SIZ];
4070                       if (currentMove == 0 &&
4071                           gameMode == IcsPlayingWhite &&
4072                           appData.premoveWhite) {
4073                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
4074                         if (appData.debugMode)
4075                           fprintf(debugFP, "Sending premove:\n");
4076                         SendToICS(str);
4077                       } else if (currentMove == 1 &&
4078                                  gameMode == IcsPlayingBlack &&
4079                                  appData.premoveBlack) {
4080                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
4081                         if (appData.debugMode)
4082                           fprintf(debugFP, "Sending premove:\n");
4083                         SendToICS(str);
4084                       } else if (gotPremove) {
4085                         gotPremove = 0;
4086                         ClearPremoveHighlights();
4087                         if (appData.debugMode)
4088                           fprintf(debugFP, "Sending premove:\n");
4089                           UserMoveEvent(premoveFromX, premoveFromY,
4090                                         premoveToX, premoveToY,
4091                                         premovePromoChar);
4092                       }
4093                     }
4094
4095                     /* Usually suppress following prompt */
4096                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4097                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4098                         if (looking_at(buf, &i, "*% ")) {
4099                             savingComment = FALSE;
4100                             suppressKibitz = 0;
4101                         }
4102                     }
4103                     next_out = i;
4104                 } else if (started == STARTED_HOLDINGS) {
4105                     int gamenum;
4106                     char new_piece[MSG_SIZ];
4107                     started = STARTED_NONE;
4108                     parse[parse_pos] = NULLCHAR;
4109                     if (appData.debugMode)
4110                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4111                                                         parse, currentMove);
4112                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4113                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4114                         if (gameInfo.variant == VariantNormal) {
4115                           /* [HGM] We seem to switch variant during a game!
4116                            * Presumably no holdings were displayed, so we have
4117                            * to move the position two files to the right to
4118                            * create room for them!
4119                            */
4120                           VariantClass newVariant;
4121                           switch(gameInfo.boardWidth) { // base guess on board width
4122                                 case 9:  newVariant = VariantShogi; break;
4123                                 case 10: newVariant = VariantGreat; break;
4124                                 default: newVariant = VariantCrazyhouse; break;
4125                           }
4126                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4127                           /* Get a move list just to see the header, which
4128                              will tell us whether this is really bug or zh */
4129                           if (ics_getting_history == H_FALSE) {
4130                             ics_getting_history = H_REQUESTED;
4131                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4132                             SendToICS(str);
4133                           }
4134                         }
4135                         new_piece[0] = NULLCHAR;
4136                         sscanf(parse, "game %d white [%s black [%s <- %s",
4137                                &gamenum, white_holding, black_holding,
4138                                new_piece);
4139                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4140                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4141                         /* [HGM] copy holdings to board holdings area */
4142                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4143                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4144                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4145 #if ZIPPY
4146                         if (appData.zippyPlay && first.initDone) {
4147                             ZippyHoldings(white_holding, black_holding,
4148                                           new_piece);
4149                         }
4150 #endif /*ZIPPY*/
4151                         if (tinyLayout || smallLayout) {
4152                             char wh[16], bh[16];
4153                             PackHolding(wh, white_holding);
4154                             PackHolding(bh, black_holding);
4155                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4156                                     gameInfo.white, gameInfo.black);
4157                         } else {
4158                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4159                                     gameInfo.white, white_holding, _("vs."),
4160                                     gameInfo.black, black_holding);
4161                         }
4162                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4163                         DrawPosition(FALSE, boards[currentMove]);
4164                         DisplayTitle(str);
4165                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4166                         sscanf(parse, "game %d white [%s black [%s <- %s",
4167                                &gamenum, white_holding, black_holding,
4168                                new_piece);
4169                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4170                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4171                         /* [HGM] copy holdings to partner-board holdings area */
4172                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4173                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4174                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4175                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4176                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4177                       }
4178                     }
4179                     /* Suppress following prompt */
4180                     if (looking_at(buf, &i, "*% ")) {
4181                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4182                         savingComment = FALSE;
4183                         suppressKibitz = 0;
4184                     }
4185                     next_out = i;
4186                 }
4187                 continue;
4188             }
4189
4190             i++;                /* skip unparsed character and loop back */
4191         }
4192
4193         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4194 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4195 //          SendToPlayer(&buf[next_out], i - next_out);
4196             started != STARTED_HOLDINGS && leftover_start > next_out) {
4197             SendToPlayer(&buf[next_out], leftover_start - next_out);
4198             next_out = i;
4199         }
4200
4201         leftover_len = buf_len - leftover_start;
4202         /* if buffer ends with something we couldn't parse,
4203            reparse it after appending the next read */
4204
4205     } else if (count == 0) {
4206         RemoveInputSource(isr);
4207         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4208     } else {
4209         DisplayFatalError(_("Error reading from ICS"), error, 1);
4210     }
4211 }
4212
4213
4214 /* Board style 12 looks like this:
4215
4216    <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
4217
4218  * The "<12> " is stripped before it gets to this routine.  The two
4219  * trailing 0's (flip state and clock ticking) are later addition, and
4220  * some chess servers may not have them, or may have only the first.
4221  * Additional trailing fields may be added in the future.
4222  */
4223
4224 #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"
4225
4226 #define RELATION_OBSERVING_PLAYED    0
4227 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4228 #define RELATION_PLAYING_MYMOVE      1
4229 #define RELATION_PLAYING_NOTMYMOVE  -1
4230 #define RELATION_EXAMINING           2
4231 #define RELATION_ISOLATED_BOARD     -3
4232 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4233
4234 void
4235 ParseBoard12 (char *string)
4236 {
4237 #if ZIPPY
4238     int i, takeback;
4239     char *bookHit = NULL; // [HGM] book
4240 #endif
4241     GameMode newGameMode;
4242     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
4243     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
4244     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4245     char to_play, board_chars[200];
4246     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4247     char black[32], white[32];
4248     Board board;
4249     int prevMove = currentMove;
4250     int ticking = 2;
4251     ChessMove moveType;
4252     int fromX, fromY, toX, toY;
4253     char promoChar;
4254     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4255     Boolean weird = FALSE, reqFlag = FALSE;
4256
4257     fromX = fromY = toX = toY = -1;
4258
4259     newGame = FALSE;
4260
4261     if (appData.debugMode)
4262       fprintf(debugFP, "Parsing board: %s\n", string);
4263
4264     move_str[0] = NULLCHAR;
4265     elapsed_time[0] = NULLCHAR;
4266     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4267         int  i = 0, j;
4268         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4269             if(string[i] == ' ') { ranks++; files = 0; }
4270             else files++;
4271             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4272             i++;
4273         }
4274         for(j = 0; j <i; j++) board_chars[j] = string[j];
4275         board_chars[i] = '\0';
4276         string += i + 1;
4277     }
4278     n = sscanf(string, PATTERN, &to_play, &double_push,
4279                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4280                &gamenum, white, black, &relation, &basetime, &increment,
4281                &white_stren, &black_stren, &white_time, &black_time,
4282                &moveNum, str, elapsed_time, move_str, &ics_flip,
4283                &ticking);
4284
4285     if (n < 21) {
4286         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4287         DisplayError(str, 0);
4288         return;
4289     }
4290
4291     /* Convert the move number to internal form */
4292     moveNum = (moveNum - 1) * 2;
4293     if (to_play == 'B') moveNum++;
4294     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4295       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4296                         0, 1);
4297       return;
4298     }
4299
4300     switch (relation) {
4301       case RELATION_OBSERVING_PLAYED:
4302       case RELATION_OBSERVING_STATIC:
4303         if (gamenum == -1) {
4304             /* Old ICC buglet */
4305             relation = RELATION_OBSERVING_STATIC;
4306         }
4307         newGameMode = IcsObserving;
4308         break;
4309       case RELATION_PLAYING_MYMOVE:
4310       case RELATION_PLAYING_NOTMYMOVE:
4311         newGameMode =
4312           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4313             IcsPlayingWhite : IcsPlayingBlack;
4314         soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4315         break;
4316       case RELATION_EXAMINING:
4317         newGameMode = IcsExamining;
4318         break;
4319       case RELATION_ISOLATED_BOARD:
4320       default:
4321         /* Just display this board.  If user was doing something else,
4322            we will forget about it until the next board comes. */
4323         newGameMode = IcsIdle;
4324         break;
4325       case RELATION_STARTING_POSITION:
4326         newGameMode = gameMode;
4327         break;
4328     }
4329
4330     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4331         gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4332          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4333       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4334       int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4335       static int lastBgGame = -1;
4336       char *toSqr;
4337       for (k = 0; k < ranks; k++) {
4338         for (j = 0; j < files; j++)
4339           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4340         if(gameInfo.holdingsWidth > 1) {
4341              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4342              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4343         }
4344       }
4345       CopyBoard(partnerBoard, board);
4346       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4347         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4348         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4349       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4350       if(toSqr = strchr(str, '-')) {
4351         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4352         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4353       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4354       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4355       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4356       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4357       if(twoBoards) {
4358           DisplayWhiteClock(white_time*fac, to_play == 'W');
4359           DisplayBlackClock(black_time*fac, to_play != 'W');
4360           activePartner = to_play;
4361           if(gamenum != lastBgGame) {
4362               char buf[MSG_SIZ];
4363               snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4364               DisplayTitle(buf);
4365           }
4366           lastBgGame = gamenum;
4367           activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4368                       partnerUp = 0; flipView = !flipView; } // [HGM] dual
4369       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4370                  (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4371       if(!twoBoards) DisplayMessage(partnerStatus, "");
4372         partnerBoardValid = TRUE;
4373       return;
4374     }
4375
4376     if(appData.dualBoard && appData.bgObserve) {
4377         if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4378             SendToICS(ics_prefix), SendToICS("pobserve\n");
4379         else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4380             char buf[MSG_SIZ];
4381             snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4382             SendToICS(buf);
4383         }
4384     }
4385
4386     /* Modify behavior for initial board display on move listing
4387        of wild games.
4388        */
4389     switch (ics_getting_history) {
4390       case H_FALSE:
4391       case H_REQUESTED:
4392         break;
4393       case H_GOT_REQ_HEADER:
4394       case H_GOT_UNREQ_HEADER:
4395         /* This is the initial position of the current game */
4396         gamenum = ics_gamenum;
4397         moveNum = 0;            /* old ICS bug workaround */
4398         if (to_play == 'B') {
4399           startedFromSetupPosition = TRUE;
4400           blackPlaysFirst = TRUE;
4401           moveNum = 1;
4402           if (forwardMostMove == 0) forwardMostMove = 1;
4403           if (backwardMostMove == 0) backwardMostMove = 1;
4404           if (currentMove == 0) currentMove = 1;
4405         }
4406         newGameMode = gameMode;
4407         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4408         break;
4409       case H_GOT_UNWANTED_HEADER:
4410         /* This is an initial board that we don't want */
4411         return;
4412       case H_GETTING_MOVES:
4413         /* Should not happen */
4414         DisplayError(_("Error gathering move list: extra board"), 0);
4415         ics_getting_history = H_FALSE;
4416         return;
4417     }
4418
4419    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4420                                         move_str[1] == '@' && !gameInfo.holdingsWidth ||
4421                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4422      /* [HGM] We seem to have switched variant unexpectedly
4423       * Try to guess new variant from board size
4424       */
4425           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4426           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4427           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4428           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4429           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4430           if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4431           if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4432           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4433           /* Get a move list just to see the header, which
4434              will tell us whether this is really bug or zh */
4435           if (ics_getting_history == H_FALSE) {
4436             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4437             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4438             SendToICS(str);
4439           }
4440     }
4441
4442     /* Take action if this is the first board of a new game, or of a
4443        different game than is currently being displayed.  */
4444     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4445         relation == RELATION_ISOLATED_BOARD) {
4446
4447         /* Forget the old game and get the history (if any) of the new one */
4448         if (gameMode != BeginningOfGame) {
4449           Reset(TRUE, TRUE);
4450         }
4451         newGame = TRUE;
4452         if (appData.autoRaiseBoard) BoardToTop();
4453         prevMove = -3;
4454         if (gamenum == -1) {
4455             newGameMode = IcsIdle;
4456         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4457                    appData.getMoveList && !reqFlag) {
4458             /* Need to get game history */
4459             ics_getting_history = H_REQUESTED;
4460             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4461             SendToICS(str);
4462         }
4463
4464         /* Initially flip the board to have black on the bottom if playing
4465            black or if the ICS flip flag is set, but let the user change
4466            it with the Flip View button. */
4467         flipView = appData.autoFlipView ?
4468           (newGameMode == IcsPlayingBlack) || ics_flip :
4469           appData.flipView;
4470
4471         /* Done with values from previous mode; copy in new ones */
4472         gameMode = newGameMode;
4473         ModeHighlight();
4474         ics_gamenum = gamenum;
4475         if (gamenum == gs_gamenum) {
4476             int klen = strlen(gs_kind);
4477             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4478             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4479             gameInfo.event = StrSave(str);
4480         } else {
4481             gameInfo.event = StrSave("ICS game");
4482         }
4483         gameInfo.site = StrSave(appData.icsHost);
4484         gameInfo.date = PGNDate();
4485         gameInfo.round = StrSave("-");
4486         gameInfo.white = StrSave(white);
4487         gameInfo.black = StrSave(black);
4488         timeControl = basetime * 60 * 1000;
4489         timeControl_2 = 0;
4490         timeIncrement = increment * 1000;
4491         movesPerSession = 0;
4492         gameInfo.timeControl = TimeControlTagValue();
4493         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4494   if (appData.debugMode) {
4495     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4496     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4497     setbuf(debugFP, NULL);
4498   }
4499
4500         gameInfo.outOfBook = NULL;
4501
4502         /* Do we have the ratings? */
4503         if (strcmp(player1Name, white) == 0 &&
4504             strcmp(player2Name, black) == 0) {
4505             if (appData.debugMode)
4506               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4507                       player1Rating, player2Rating);
4508             gameInfo.whiteRating = player1Rating;
4509             gameInfo.blackRating = player2Rating;
4510         } else if (strcmp(player2Name, white) == 0 &&
4511                    strcmp(player1Name, black) == 0) {
4512             if (appData.debugMode)
4513               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4514                       player2Rating, player1Rating);
4515             gameInfo.whiteRating = player2Rating;
4516             gameInfo.blackRating = player1Rating;
4517         }
4518         player1Name[0] = player2Name[0] = NULLCHAR;
4519
4520         /* Silence shouts if requested */
4521         if (appData.quietPlay &&
4522             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4523             SendToICS(ics_prefix);
4524             SendToICS("set shout 0\n");
4525         }
4526     }
4527
4528     /* Deal with midgame name changes */
4529     if (!newGame) {
4530         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4531             if (gameInfo.white) free(gameInfo.white);
4532             gameInfo.white = StrSave(white);
4533         }
4534         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4535             if (gameInfo.black) free(gameInfo.black);
4536             gameInfo.black = StrSave(black);
4537         }
4538     }
4539
4540     /* Throw away game result if anything actually changes in examine mode */
4541     if (gameMode == IcsExamining && !newGame) {
4542         gameInfo.result = GameUnfinished;
4543         if (gameInfo.resultDetails != NULL) {
4544             free(gameInfo.resultDetails);
4545             gameInfo.resultDetails = NULL;
4546         }
4547     }
4548
4549     /* In pausing && IcsExamining mode, we ignore boards coming
4550        in if they are in a different variation than we are. */
4551     if (pauseExamInvalid) return;
4552     if (pausing && gameMode == IcsExamining) {
4553         if (moveNum <= pauseExamForwardMostMove) {
4554             pauseExamInvalid = TRUE;
4555             forwardMostMove = pauseExamForwardMostMove;
4556             return;
4557         }
4558     }
4559
4560   if (appData.debugMode) {
4561     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4562   }
4563     /* Parse the board */
4564     for (k = 0; k < ranks; k++) {
4565       for (j = 0; j < files; j++)
4566         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4567       if(gameInfo.holdingsWidth > 1) {
4568            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4569            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4570       }
4571     }
4572     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4573       board[5][BOARD_RGHT+1] = WhiteAngel;
4574       board[6][BOARD_RGHT+1] = WhiteMarshall;
4575       board[1][0] = BlackMarshall;
4576       board[2][0] = BlackAngel;
4577       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4578     }
4579     CopyBoard(boards[moveNum], board);
4580     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4581     if (moveNum == 0) {
4582         startedFromSetupPosition =
4583           !CompareBoards(board, initialPosition);
4584         if(startedFromSetupPosition)
4585             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4586     }
4587
4588     /* [HGM] Set castling rights. Take the outermost Rooks,
4589        to make it also work for FRC opening positions. Note that board12
4590        is really defective for later FRC positions, as it has no way to
4591        indicate which Rook can castle if they are on the same side of King.
4592        For the initial position we grant rights to the outermost Rooks,
4593        and remember thos rights, and we then copy them on positions
4594        later in an FRC game. This means WB might not recognize castlings with
4595        Rooks that have moved back to their original position as illegal,
4596        but in ICS mode that is not its job anyway.
4597     */
4598     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4599     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4600
4601         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4602             if(board[0][i] == WhiteRook) j = i;
4603         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4604         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4605             if(board[0][i] == WhiteRook) j = i;
4606         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4607         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4608             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4609         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4610         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4611             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4612         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4613
4614         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4615         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4616         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4617             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4618         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4619             if(board[BOARD_HEIGHT-1][k] == bKing)
4620                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4621         if(gameInfo.variant == VariantTwoKings) {
4622             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4623             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4624             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4625         }
4626     } else { int r;
4627         r = boards[moveNum][CASTLING][0] = initialRights[0];
4628         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4629         r = boards[moveNum][CASTLING][1] = initialRights[1];
4630         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4631         r = boards[moveNum][CASTLING][3] = initialRights[3];
4632         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4633         r = boards[moveNum][CASTLING][4] = initialRights[4];
4634         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4635         /* wildcastle kludge: always assume King has rights */
4636         r = boards[moveNum][CASTLING][2] = initialRights[2];
4637         r = boards[moveNum][CASTLING][5] = initialRights[5];
4638     }
4639     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4640     boards[moveNum][EP_STATUS] = EP_NONE;
4641     if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4642     if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4643     if(double_push !=  -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4644
4645
4646     if (ics_getting_history == H_GOT_REQ_HEADER ||
4647         ics_getting_history == H_GOT_UNREQ_HEADER) {
4648         /* This was an initial position from a move list, not
4649            the current position */
4650         return;
4651     }
4652
4653     /* Update currentMove and known move number limits */
4654     newMove = newGame || moveNum > forwardMostMove;
4655
4656     if (newGame) {
4657         forwardMostMove = backwardMostMove = currentMove = moveNum;
4658         if (gameMode == IcsExamining && moveNum == 0) {
4659           /* Workaround for ICS limitation: we are not told the wild
4660              type when starting to examine a game.  But if we ask for
4661              the move list, the move list header will tell us */
4662             ics_getting_history = H_REQUESTED;
4663             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4664             SendToICS(str);
4665         }
4666     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4667                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4668 #if ZIPPY
4669         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4670         /* [HGM] applied this also to an engine that is silently watching        */
4671         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4672             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4673             gameInfo.variant == currentlyInitializedVariant) {
4674           takeback = forwardMostMove - moveNum;
4675           for (i = 0; i < takeback; i++) {
4676             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4677             SendToProgram("undo\n", &first);
4678           }
4679         }
4680 #endif
4681
4682         forwardMostMove = moveNum;
4683         if (!pausing || currentMove > forwardMostMove)
4684           currentMove = forwardMostMove;
4685     } else {
4686         /* New part of history that is not contiguous with old part */
4687         if (pausing && gameMode == IcsExamining) {
4688             pauseExamInvalid = TRUE;
4689             forwardMostMove = pauseExamForwardMostMove;
4690             return;
4691         }
4692         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4693 #if ZIPPY
4694             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4695                 // [HGM] when we will receive the move list we now request, it will be
4696                 // fed to the engine from the first move on. So if the engine is not
4697                 // in the initial position now, bring it there.
4698                 InitChessProgram(&first, 0);
4699             }
4700 #endif
4701             ics_getting_history = H_REQUESTED;
4702             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4703             SendToICS(str);
4704         }
4705         forwardMostMove = backwardMostMove = currentMove = moveNum;
4706     }
4707
4708     /* Update the clocks */
4709     if (strchr(elapsed_time, '.')) {
4710       /* Time is in ms */
4711       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4712       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4713     } else {
4714       /* Time is in seconds */
4715       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4716       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4717     }
4718
4719
4720 #if ZIPPY
4721     if (appData.zippyPlay && newGame &&
4722         gameMode != IcsObserving && gameMode != IcsIdle &&
4723         gameMode != IcsExamining)
4724       ZippyFirstBoard(moveNum, basetime, increment);
4725 #endif
4726
4727     /* Put the move on the move list, first converting
4728        to canonical algebraic form. */
4729     if (moveNum > 0) {
4730   if (appData.debugMode) {
4731     int f = forwardMostMove;
4732     fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4733             boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4734             boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4735     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4736     fprintf(debugFP, "moveNum = %d\n", moveNum);
4737     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4738     setbuf(debugFP, NULL);
4739   }
4740         if (moveNum <= backwardMostMove) {
4741             /* We don't know what the board looked like before
4742                this move.  Punt. */
4743           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4744             strcat(parseList[moveNum - 1], " ");
4745             strcat(parseList[moveNum - 1], elapsed_time);
4746             moveList[moveNum - 1][0] = NULLCHAR;
4747         } else if (strcmp(move_str, "none") == 0) {
4748             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4749             /* Again, we don't know what the board looked like;
4750                this is really the start of the game. */
4751             parseList[moveNum - 1][0] = NULLCHAR;
4752             moveList[moveNum - 1][0] = NULLCHAR;
4753             backwardMostMove = moveNum;
4754             startedFromSetupPosition = TRUE;
4755             fromX = fromY = toX = toY = -1;
4756         } else {
4757           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4758           //                 So we parse the long-algebraic move string in stead of the SAN move
4759           int valid; char buf[MSG_SIZ], *prom;
4760
4761           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4762                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4763           // str looks something like "Q/a1-a2"; kill the slash
4764           if(str[1] == '/')
4765             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4766           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4767           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4768                 strcat(buf, prom); // long move lacks promo specification!
4769           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4770                 if(appData.debugMode)
4771                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4772                 safeStrCpy(move_str, buf, MSG_SIZ);
4773           }
4774           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4775                                 &fromX, &fromY, &toX, &toY, &promoChar)
4776                || ParseOneMove(buf, moveNum - 1, &moveType,
4777                                 &fromX, &fromY, &toX, &toY, &promoChar);
4778           // end of long SAN patch
4779           if (valid) {
4780             (void) CoordsToAlgebraic(boards[moveNum - 1],
4781                                      PosFlags(moveNum - 1),
4782                                      fromY, fromX, toY, toX, promoChar,
4783                                      parseList[moveNum-1]);
4784             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4785               case MT_NONE:
4786               case MT_STALEMATE:
4787               default:
4788                 break;
4789               case MT_CHECK:
4790                 if(gameInfo.variant != VariantShogi)
4791                     strcat(parseList[moveNum - 1], "+");
4792                 break;
4793               case MT_CHECKMATE:
4794               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4795                 strcat(parseList[moveNum - 1], "#");
4796                 break;
4797             }
4798             strcat(parseList[moveNum - 1], " ");
4799             strcat(parseList[moveNum - 1], elapsed_time);
4800             /* currentMoveString is set as a side-effect of ParseOneMove */
4801             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4802             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4803             strcat(moveList[moveNum - 1], "\n");
4804
4805             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4806                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4807               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4808                 ChessSquare old, new = boards[moveNum][k][j];
4809                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4810                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4811                   if(old == new) continue;
4812                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4813                   else if(new == WhiteWazir || new == BlackWazir) {
4814                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4815                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4816                       else boards[moveNum][k][j] = old; // preserve type of Gold
4817                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4818                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4819               }
4820           } else {
4821             /* Move from ICS was illegal!?  Punt. */
4822             if (appData.debugMode) {
4823               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4824               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4825             }
4826             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4827             strcat(parseList[moveNum - 1], " ");
4828             strcat(parseList[moveNum - 1], elapsed_time);
4829             moveList[moveNum - 1][0] = NULLCHAR;
4830             fromX = fromY = toX = toY = -1;
4831           }
4832         }
4833   if (appData.debugMode) {
4834     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4835     setbuf(debugFP, NULL);
4836   }
4837
4838 #if ZIPPY
4839         /* Send move to chess program (BEFORE animating it). */
4840         if (appData.zippyPlay && !newGame && newMove &&
4841            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4842
4843             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4844                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4845                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4846                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4847                             move_str);
4848                     DisplayError(str, 0);
4849                 } else {
4850                     if (first.sendTime) {
4851                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4852                     }
4853                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4854                     if (firstMove && !bookHit) {
4855                         firstMove = FALSE;
4856                         if (first.useColors) {
4857                           SendToProgram(gameMode == IcsPlayingWhite ?
4858                                         "white\ngo\n" :
4859                                         "black\ngo\n", &first);
4860                         } else {
4861                           SendToProgram("go\n", &first);
4862                         }
4863                         first.maybeThinking = TRUE;
4864                     }
4865                 }
4866             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4867               if (moveList[moveNum - 1][0] == NULLCHAR) {
4868                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4869                 DisplayError(str, 0);
4870               } else {
4871                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4872                 SendMoveToProgram(moveNum - 1, &first);
4873               }
4874             }
4875         }
4876 #endif
4877     }
4878
4879     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4880         /* If move comes from a remote source, animate it.  If it
4881            isn't remote, it will have already been animated. */
4882         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4883             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4884         }
4885         if (!pausing && appData.highlightLastMove) {
4886             SetHighlights(fromX, fromY, toX, toY);
4887         }
4888     }
4889
4890     /* Start the clocks */
4891     whiteFlag = blackFlag = FALSE;
4892     appData.clockMode = !(basetime == 0 && increment == 0);
4893     if (ticking == 0) {
4894       ics_clock_paused = TRUE;
4895       StopClocks();
4896     } else if (ticking == 1) {
4897       ics_clock_paused = FALSE;
4898     }
4899     if (gameMode == IcsIdle ||
4900         relation == RELATION_OBSERVING_STATIC ||
4901         relation == RELATION_EXAMINING ||
4902         ics_clock_paused)
4903       DisplayBothClocks();
4904     else
4905       StartClocks();
4906
4907     /* Display opponents and material strengths */
4908     if (gameInfo.variant != VariantBughouse &&
4909         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4910         if (tinyLayout || smallLayout) {
4911             if(gameInfo.variant == VariantNormal)
4912               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4913                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4914                     basetime, increment);
4915             else
4916               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4917                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4918                     basetime, increment, (int) gameInfo.variant);
4919         } else {
4920             if(gameInfo.variant == VariantNormal)
4921               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4922                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4923                     basetime, increment);
4924             else
4925               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
4926                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4927                     basetime, increment, VariantName(gameInfo.variant));
4928         }
4929         DisplayTitle(str);
4930   if (appData.debugMode) {
4931     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4932   }
4933     }
4934
4935
4936     /* Display the board */
4937     if (!pausing && !appData.noGUI) {
4938
4939       if (appData.premove)
4940           if (!gotPremove ||
4941              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4942              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4943               ClearPremoveHighlights();
4944
4945       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4946         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4947       DrawPosition(j, boards[currentMove]);
4948
4949       DisplayMove(moveNum - 1);
4950       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4951             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4952               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4953         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4954       }
4955     }
4956
4957     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4958 #if ZIPPY
4959     if(bookHit) { // [HGM] book: simulate book reply
4960         static char bookMove[MSG_SIZ]; // a bit generous?
4961
4962         programStats.nodes = programStats.depth = programStats.time =
4963         programStats.score = programStats.got_only_move = 0;
4964         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4965
4966         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4967         strcat(bookMove, bookHit);
4968         HandleMachineMove(bookMove, &first);
4969     }
4970 #endif
4971 }
4972
4973 void
4974 GetMoveListEvent ()
4975 {
4976     char buf[MSG_SIZ];
4977     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4978         ics_getting_history = H_REQUESTED;
4979         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4980         SendToICS(buf);
4981     }
4982 }
4983
4984 void
4985 SendToBoth (char *msg)
4986 {   // to make it easy to keep two engines in step in dual analysis
4987     SendToProgram(msg, &first);
4988     if(second.analyzing) SendToProgram(msg, &second);
4989 }
4990
4991 void
4992 AnalysisPeriodicEvent (int force)
4993 {
4994     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4995          && !force) || !appData.periodicUpdates)
4996       return;
4997
4998     /* Send . command to Crafty to collect stats */
4999     SendToBoth(".\n");
5000
5001     /* Don't send another until we get a response (this makes
5002        us stop sending to old Crafty's which don't understand
5003        the "." command (sending illegal cmds resets node count & time,
5004        which looks bad)) */
5005     programStats.ok_to_send = 0;
5006 }
5007
5008 void
5009 ics_update_width (int new_width)
5010 {
5011         ics_printf("set width %d\n", new_width);
5012 }
5013
5014 void
5015 SendMoveToProgram (int moveNum, ChessProgramState *cps)
5016 {
5017     char buf[MSG_SIZ];
5018
5019     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
5020         if(gameInfo.variant == VariantLion || gameInfo.variant == VariantChu) {
5021             sprintf(buf, "%s@@@@\n", cps->useUsermove ? "usermove " : "");
5022             SendToProgram(buf, cps);
5023             return;
5024         }
5025         // null move in variant where engine does not understand it (for analysis purposes)
5026         SendBoard(cps, moveNum + 1); // send position after move in stead.
5027         return;
5028     }
5029     if (cps->useUsermove) {
5030       SendToProgram("usermove ", cps);
5031     }
5032     if (cps->useSAN) {
5033       char *space;
5034       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
5035         int len = space - parseList[moveNum];
5036         memcpy(buf, parseList[moveNum], len);
5037         buf[len++] = '\n';
5038         buf[len] = NULLCHAR;
5039       } else {
5040         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
5041       }
5042       SendToProgram(buf, cps);
5043     } else {
5044       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
5045         AlphaRank(moveList[moveNum], 4);
5046         SendToProgram(moveList[moveNum], cps);
5047         AlphaRank(moveList[moveNum], 4); // and back
5048       } else
5049       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
5050        * the engine. It would be nice to have a better way to identify castle
5051        * moves here. */
5052       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
5053                                                                          && cps->useOOCastle) {
5054         int fromX = moveList[moveNum][0] - AAA;
5055         int fromY = moveList[moveNum][1] - ONE;
5056         int toX = moveList[moveNum][2] - AAA;
5057         int toY = moveList[moveNum][3] - ONE;
5058         if((boards[moveNum][fromY][fromX] == WhiteKing
5059             && boards[moveNum][toY][toX] == WhiteRook)
5060            || (boards[moveNum][fromY][fromX] == BlackKing
5061                && boards[moveNum][toY][toX] == BlackRook)) {
5062           if(toX > fromX) SendToProgram("O-O\n", cps);
5063           else SendToProgram("O-O-O\n", cps);
5064         }
5065         else SendToProgram(moveList[moveNum], cps);
5066       } else
5067       if(moveList[moveNum][4] == ';') { // [HGM] lion: move is double-step over intermediate square
5068           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", moveList[moveNum][0], moveList[moveNum][1] - '0', // convert to two moves
5069                                                moveList[moveNum][5], moveList[moveNum][6] - '0',
5070                                                moveList[moveNum][5], moveList[moveNum][6] - '0',
5071                                                moveList[moveNum][2], moveList[moveNum][3] - '0');
5072           SendToProgram(buf, cps);
5073       } else
5074       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5075         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5076           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5077           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5078                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5079         } else
5080           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5081                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5082         SendToProgram(buf, cps);
5083       }
5084       else SendToProgram(moveList[moveNum], cps);
5085       /* End of additions by Tord */
5086     }
5087
5088     /* [HGM] setting up the opening has brought engine in force mode! */
5089     /*       Send 'go' if we are in a mode where machine should play. */
5090     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5091         (gameMode == TwoMachinesPlay   ||
5092 #if ZIPPY
5093          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
5094 #endif
5095          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5096         SendToProgram("go\n", cps);
5097   if (appData.debugMode) {
5098     fprintf(debugFP, "(extra)\n");
5099   }
5100     }
5101     setboardSpoiledMachineBlack = 0;
5102 }
5103
5104 void
5105 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5106 {
5107     char user_move[MSG_SIZ];
5108     char suffix[4];
5109
5110     if(gameInfo.variant == VariantSChess && promoChar) {
5111         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5112         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5113     } else suffix[0] = NULLCHAR;
5114
5115     switch (moveType) {
5116       default:
5117         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5118                 (int)moveType, fromX, fromY, toX, toY);
5119         DisplayError(user_move + strlen("say "), 0);
5120         break;
5121       case WhiteKingSideCastle:
5122       case BlackKingSideCastle:
5123       case WhiteQueenSideCastleWild:
5124       case BlackQueenSideCastleWild:
5125       /* PUSH Fabien */
5126       case WhiteHSideCastleFR:
5127       case BlackHSideCastleFR:
5128       /* POP Fabien */
5129         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5130         break;
5131       case WhiteQueenSideCastle:
5132       case BlackQueenSideCastle:
5133       case WhiteKingSideCastleWild:
5134       case BlackKingSideCastleWild:
5135       /* PUSH Fabien */
5136       case WhiteASideCastleFR:
5137       case BlackASideCastleFR:
5138       /* POP Fabien */
5139         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5140         break;
5141       case WhiteNonPromotion:
5142       case BlackNonPromotion:
5143         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5144         break;
5145       case WhitePromotion:
5146       case BlackPromotion:
5147         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
5148            gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
5149           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5150                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5151                 PieceToChar(WhiteFerz));
5152         else if(gameInfo.variant == VariantGreat)
5153           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5154                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5155                 PieceToChar(WhiteMan));
5156         else
5157           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5158                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5159                 promoChar);
5160         break;
5161       case WhiteDrop:
5162       case BlackDrop:
5163       drop:
5164         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5165                  ToUpper(PieceToChar((ChessSquare) fromX)),
5166                  AAA + toX, ONE + toY);
5167         break;
5168       case IllegalMove:  /* could be a variant we don't quite understand */
5169         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5170       case NormalMove:
5171       case WhiteCapturesEnPassant:
5172       case BlackCapturesEnPassant:
5173         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5174                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5175         break;
5176     }
5177     SendToICS(user_move);
5178     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5179         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5180 }
5181
5182 void
5183 UploadGameEvent ()
5184 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5185     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5186     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5187     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5188       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5189       return;
5190     }
5191     if(gameMode != IcsExamining) { // is this ever not the case?
5192         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5193
5194         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5195           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5196         } else { // on FICS we must first go to general examine mode
5197           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5198         }
5199         if(gameInfo.variant != VariantNormal) {
5200             // try figure out wild number, as xboard names are not always valid on ICS
5201             for(i=1; i<=36; i++) {
5202               snprintf(buf, MSG_SIZ, "wild/%d", i);
5203                 if(StringToVariant(buf) == gameInfo.variant) break;
5204             }
5205             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5206             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5207             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5208         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5209         SendToICS(ics_prefix);
5210         SendToICS(buf);
5211         if(startedFromSetupPosition || backwardMostMove != 0) {
5212           fen = PositionToFEN(backwardMostMove, NULL, 1);
5213           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5214             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5215             SendToICS(buf);
5216           } else { // FICS: everything has to set by separate bsetup commands
5217             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5218             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5219             SendToICS(buf);
5220             if(!WhiteOnMove(backwardMostMove)) {
5221                 SendToICS("bsetup tomove black\n");
5222             }
5223             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5224             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5225             SendToICS(buf);
5226             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5227             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5228             SendToICS(buf);
5229             i = boards[backwardMostMove][EP_STATUS];
5230             if(i >= 0) { // set e.p.
5231               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5232                 SendToICS(buf);
5233             }
5234             bsetup++;
5235           }
5236         }
5237       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5238             SendToICS("bsetup done\n"); // switch to normal examining.
5239     }
5240     for(i = backwardMostMove; i<last; i++) {
5241         char buf[20];
5242         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5243         if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5244             int len = strlen(moveList[i]);
5245             snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5246             if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5247         }
5248         SendToICS(buf);
5249     }
5250     SendToICS(ics_prefix);
5251     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5252 }
5253
5254 int killX = -1, killY = -1; // [HGM] lion: used for passing e.p. capture square to MakeMove
5255
5256 void
5257 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5258 {
5259     if (rf == DROP_RANK) {
5260       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5261       sprintf(move, "%c@%c%c\n",
5262                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5263     } else {
5264         if (promoChar == 'x' || promoChar == NULLCHAR) {
5265           sprintf(move, "%c%c%c%c\n",
5266                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5267           if(killX >= 0 && killY >= 0) sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY);
5268         } else {
5269             sprintf(move, "%c%c%c%c%c\n",
5270                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5271         }
5272     }
5273 }
5274
5275 void
5276 ProcessICSInitScript (FILE *f)
5277 {
5278     char buf[MSG_SIZ];
5279
5280     while (fgets(buf, MSG_SIZ, f)) {
5281         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5282     }
5283
5284     fclose(f);
5285 }
5286
5287
5288 static int lastX, lastY, lastLeftX, lastLeftY, selectFlag;
5289 int dragging;
5290 static ClickType lastClickType;
5291
5292 void
5293 Sweep (int step)
5294 {
5295     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5296     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5297     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5298     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5299     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5300     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5301     do {
5302         promoSweep -= step;
5303         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5304         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5305         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5306         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5307         if(!step) step = -1;
5308     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5309             appData.testLegality && (promoSweep == king || promoSweep == WhiteLion || promoSweep == BlackLion) ||
5310             IS_SHOGI(gameInfo.variant) && promoSweep != CHUPROMOTED last && last != CHUPROMOTED promoSweep && last != promoSweep);
5311     if(toX >= 0) {
5312         int victim = boards[currentMove][toY][toX];
5313         boards[currentMove][toY][toX] = promoSweep;
5314         DrawPosition(FALSE, boards[currentMove]);
5315         boards[currentMove][toY][toX] = victim;
5316     } else
5317     ChangeDragPiece(promoSweep);
5318 }
5319
5320 int
5321 PromoScroll (int x, int y)
5322 {
5323   int step = 0;
5324
5325   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5326   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5327   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5328   if(!step) return FALSE;
5329   lastX = x; lastY = y;
5330   if((promoSweep < BlackPawn) == flipView) step = -step;
5331   if(step > 0) selectFlag = 1;
5332   if(!selectFlag) Sweep(step);
5333   return FALSE;
5334 }
5335
5336 void
5337 NextPiece (int step)
5338 {
5339     ChessSquare piece = boards[currentMove][toY][toX];
5340     do {
5341         pieceSweep -= step;
5342         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5343         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5344         if(!step) step = -1;
5345     } while(PieceToChar(pieceSweep) == '.');
5346     boards[currentMove][toY][toX] = pieceSweep;
5347     DrawPosition(FALSE, boards[currentMove]);
5348     boards[currentMove][toY][toX] = piece;
5349 }
5350 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5351 void
5352 AlphaRank (char *move, int n)
5353 {
5354 //    char *p = move, c; int x, y;
5355
5356     if (appData.debugMode) {
5357         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5358     }
5359
5360     if(move[1]=='*' &&
5361        move[2]>='0' && move[2]<='9' &&
5362        move[3]>='a' && move[3]<='x'    ) {
5363         move[1] = '@';
5364         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5365         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5366     } else
5367     if(move[0]>='0' && move[0]<='9' &&
5368        move[1]>='a' && move[1]<='x' &&
5369        move[2]>='0' && move[2]<='9' &&
5370        move[3]>='a' && move[3]<='x'    ) {
5371         /* input move, Shogi -> normal */
5372         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5373         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5374         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5375         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5376     } else
5377     if(move[1]=='@' &&
5378        move[3]>='0' && move[3]<='9' &&
5379        move[2]>='a' && move[2]<='x'    ) {
5380         move[1] = '*';
5381         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5382         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5383     } else
5384     if(
5385        move[0]>='a' && move[0]<='x' &&
5386        move[3]>='0' && move[3]<='9' &&
5387        move[2]>='a' && move[2]<='x'    ) {
5388          /* output move, normal -> Shogi */
5389         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5390         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5391         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5392         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5393         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5394     }
5395     if (appData.debugMode) {
5396         fprintf(debugFP, "   out = '%s'\n", move);
5397     }
5398 }
5399
5400 char yy_textstr[8000];
5401
5402 /* Parser for moves from gnuchess, ICS, or user typein box */
5403 Boolean
5404 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5405 {
5406     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5407
5408     switch (*moveType) {
5409       case WhitePromotion:
5410       case BlackPromotion:
5411       case WhiteNonPromotion:
5412       case BlackNonPromotion:
5413       case NormalMove:
5414       case FirstLeg:
5415       case WhiteCapturesEnPassant:
5416       case BlackCapturesEnPassant:
5417       case WhiteKingSideCastle:
5418       case WhiteQueenSideCastle:
5419       case BlackKingSideCastle:
5420       case BlackQueenSideCastle:
5421       case WhiteKingSideCastleWild:
5422       case WhiteQueenSideCastleWild:
5423       case BlackKingSideCastleWild:
5424       case BlackQueenSideCastleWild:
5425       /* Code added by Tord: */
5426       case WhiteHSideCastleFR:
5427       case WhiteASideCastleFR:
5428       case BlackHSideCastleFR:
5429       case BlackASideCastleFR:
5430       /* End of code added by Tord */
5431       case IllegalMove:         /* bug or odd chess variant */
5432         *fromX = currentMoveString[0] - AAA;
5433         *fromY = currentMoveString[1] - ONE;
5434         *toX = currentMoveString[2] - AAA;
5435         *toY = currentMoveString[3] - ONE;
5436         *promoChar = currentMoveString[4];
5437         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5438             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5439     if (appData.debugMode) {
5440         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5441     }
5442             *fromX = *fromY = *toX = *toY = 0;
5443             return FALSE;
5444         }
5445         if (appData.testLegality) {
5446           return (*moveType != IllegalMove);
5447         } else {
5448           return !(*fromX == *toX && *fromY == *toY && killX < 0) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5449                          // [HGM] lion: if this is a double move we are less critical
5450                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5451         }
5452
5453       case WhiteDrop:
5454       case BlackDrop:
5455         *fromX = *moveType == WhiteDrop ?
5456           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5457           (int) CharToPiece(ToLower(currentMoveString[0]));
5458         *fromY = DROP_RANK;
5459         *toX = currentMoveString[2] - AAA;
5460         *toY = currentMoveString[3] - ONE;
5461         *promoChar = NULLCHAR;
5462         return TRUE;
5463
5464       case AmbiguousMove:
5465       case ImpossibleMove:
5466       case EndOfFile:
5467       case ElapsedTime:
5468       case Comment:
5469       case PGNTag:
5470       case NAG:
5471       case WhiteWins:
5472       case BlackWins:
5473       case GameIsDrawn:
5474       default:
5475     if (appData.debugMode) {
5476         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5477     }
5478         /* bug? */
5479         *fromX = *fromY = *toX = *toY = 0;
5480         *promoChar = NULLCHAR;
5481         return FALSE;
5482     }
5483 }
5484
5485 Boolean pushed = FALSE;
5486 char *lastParseAttempt;
5487
5488 void
5489 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5490 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5491   int fromX, fromY, toX, toY; char promoChar;
5492   ChessMove moveType;
5493   Boolean valid;
5494   int nr = 0;
5495
5496   lastParseAttempt = pv; if(!*pv) return;    // turns out we crash when we parse an empty PV
5497   if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5498     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5499     pushed = TRUE;
5500   }
5501   endPV = forwardMostMove;
5502   do {
5503     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5504     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5505     lastParseAttempt = pv;
5506     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5507     if(!valid && nr == 0 &&
5508        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5509         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5510         // Hande case where played move is different from leading PV move
5511         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5512         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5513         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5514         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5515           endPV += 2; // if position different, keep this
5516           moveList[endPV-1][0] = fromX + AAA;
5517           moveList[endPV-1][1] = fromY + ONE;
5518           moveList[endPV-1][2] = toX + AAA;
5519           moveList[endPV-1][3] = toY + ONE;
5520           parseList[endPV-1][0] = NULLCHAR;
5521           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5522         }
5523       }
5524     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5525     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5526     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5527     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5528         valid++; // allow comments in PV
5529         continue;
5530     }
5531     nr++;
5532     if(endPV+1 > framePtr) break; // no space, truncate
5533     if(!valid) break;
5534     endPV++;
5535     CopyBoard(boards[endPV], boards[endPV-1]);
5536     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5537     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5538     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5539     CoordsToAlgebraic(boards[endPV - 1],
5540                              PosFlags(endPV - 1),
5541                              fromY, fromX, toY, toX, promoChar,
5542                              parseList[endPV - 1]);
5543   } while(valid);
5544   if(atEnd == 2) return; // used hidden, for PV conversion
5545   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5546   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5547   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5548                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5549   DrawPosition(TRUE, boards[currentMove]);
5550 }
5551
5552 int
5553 MultiPV (ChessProgramState *cps)
5554 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5555         int i;
5556         for(i=0; i<cps->nrOptions; i++)
5557             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5558                 return i;
5559         return -1;
5560 }
5561
5562 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
5563
5564 Boolean
5565 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5566 {
5567         int startPV, multi, lineStart, origIndex = index;
5568         char *p, buf2[MSG_SIZ];
5569         ChessProgramState *cps = (pane ? &second : &first);
5570
5571         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5572         lastX = x; lastY = y;
5573         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5574         lineStart = startPV = index;
5575         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5576         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5577         index = startPV;
5578         do{ while(buf[index] && buf[index] != '\n') index++;
5579         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5580         buf[index] = 0;
5581         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(cps)) >= 0) {
5582                 int n = cps->option[multi].value;
5583                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5584                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5585                 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5586                 cps->option[multi].value = n;
5587                 *start = *end = 0;
5588                 return FALSE;
5589         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5590                 ExcludeClick(origIndex - lineStart);
5591                 return FALSE;
5592         }
5593         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5594         *start = startPV; *end = index-1;
5595         extendGame = (gameMode == AnalyzeMode && appData.autoExtend);
5596         return TRUE;
5597 }
5598
5599 char *
5600 PvToSAN (char *pv)
5601 {
5602         static char buf[10*MSG_SIZ];
5603         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5604         *buf = NULLCHAR;
5605         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5606         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5607         for(i = forwardMostMove; i<endPV; i++){
5608             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5609             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5610             k += strlen(buf+k);
5611         }
5612         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5613         if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5614         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5615         endPV = savedEnd;
5616         return buf;
5617 }
5618
5619 Boolean
5620 LoadPV (int x, int y)
5621 { // called on right mouse click to load PV
5622   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5623   lastX = x; lastY = y;
5624   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5625   extendGame = FALSE;
5626   return TRUE;
5627 }
5628
5629 void
5630 UnLoadPV ()
5631 {
5632   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5633   if(endPV < 0) return;
5634   if(appData.autoCopyPV) CopyFENToClipboard();
5635   endPV = -1;
5636   if(extendGame && currentMove > forwardMostMove) {
5637         Boolean saveAnimate = appData.animate;
5638         if(pushed) {
5639             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5640                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5641             } else storedGames--; // abandon shelved tail of original game
5642         }
5643         pushed = FALSE;
5644         forwardMostMove = currentMove;
5645         currentMove = oldFMM;
5646         appData.animate = FALSE;
5647         ToNrEvent(forwardMostMove);
5648         appData.animate = saveAnimate;
5649   }
5650   currentMove = forwardMostMove;
5651   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5652   ClearPremoveHighlights();
5653   DrawPosition(TRUE, boards[currentMove]);
5654 }
5655
5656 void
5657 MovePV (int x, int y, int h)
5658 { // step through PV based on mouse coordinates (called on mouse move)
5659   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5660
5661   // we must somehow check if right button is still down (might be released off board!)
5662   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5663   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5664   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5665   if(!step) return;
5666   lastX = x; lastY = y;
5667
5668   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5669   if(endPV < 0) return;
5670   if(y < margin) step = 1; else
5671   if(y > h - margin) step = -1;
5672   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5673   currentMove += step;
5674   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5675   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5676                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5677   DrawPosition(FALSE, boards[currentMove]);
5678 }
5679
5680
5681 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5682 // All positions will have equal probability, but the current method will not provide a unique
5683 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5684 #define DARK 1
5685 #define LITE 2
5686 #define ANY 3
5687
5688 int squaresLeft[4];
5689 int piecesLeft[(int)BlackPawn];
5690 int seed, nrOfShuffles;
5691
5692 void
5693 GetPositionNumber ()
5694 {       // sets global variable seed
5695         int i;
5696
5697         seed = appData.defaultFrcPosition;
5698         if(seed < 0) { // randomize based on time for negative FRC position numbers
5699                 for(i=0; i<50; i++) seed += random();
5700                 seed = random() ^ random() >> 8 ^ random() << 8;
5701                 if(seed<0) seed = -seed;
5702         }
5703 }
5704
5705 int
5706 put (Board board, int pieceType, int rank, int n, int shade)
5707 // put the piece on the (n-1)-th empty squares of the given shade
5708 {
5709         int i;
5710
5711         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5712                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5713                         board[rank][i] = (ChessSquare) pieceType;
5714                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5715                         squaresLeft[ANY]--;
5716                         piecesLeft[pieceType]--;
5717                         return i;
5718                 }
5719         }
5720         return -1;
5721 }
5722
5723
5724 void
5725 AddOnePiece (Board board, int pieceType, int rank, int shade)
5726 // calculate where the next piece goes, (any empty square), and put it there
5727 {
5728         int i;
5729
5730         i = seed % squaresLeft[shade];
5731         nrOfShuffles *= squaresLeft[shade];
5732         seed /= squaresLeft[shade];
5733         put(board, pieceType, rank, i, shade);
5734 }
5735
5736 void
5737 AddTwoPieces (Board board, int pieceType, int rank)
5738 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5739 {
5740         int i, n=squaresLeft[ANY], j=n-1, k;
5741
5742         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5743         i = seed % k;  // pick one
5744         nrOfShuffles *= k;
5745         seed /= k;
5746         while(i >= j) i -= j--;
5747         j = n - 1 - j; i += j;
5748         put(board, pieceType, rank, j, ANY);
5749         put(board, pieceType, rank, i, ANY);
5750 }
5751
5752 void
5753 SetUpShuffle (Board board, int number)
5754 {
5755         int i, p, first=1;
5756
5757         GetPositionNumber(); nrOfShuffles = 1;
5758
5759         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5760         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5761         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5762
5763         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5764
5765         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5766             p = (int) board[0][i];
5767             if(p < (int) BlackPawn) piecesLeft[p] ++;
5768             board[0][i] = EmptySquare;
5769         }
5770
5771         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5772             // shuffles restricted to allow normal castling put KRR first
5773             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5774                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5775             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5776                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5777             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5778                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5779             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5780                 put(board, WhiteRook, 0, 0, ANY);
5781             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5782         }
5783
5784         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5785             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5786             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5787                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5788                 while(piecesLeft[p] >= 2) {
5789                     AddOnePiece(board, p, 0, LITE);
5790                     AddOnePiece(board, p, 0, DARK);
5791                 }
5792                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5793             }
5794
5795         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5796             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5797             // but we leave King and Rooks for last, to possibly obey FRC restriction
5798             if(p == (int)WhiteRook) continue;
5799             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5800             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5801         }
5802
5803         // now everything is placed, except perhaps King (Unicorn) and Rooks
5804
5805         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5806             // Last King gets castling rights
5807             while(piecesLeft[(int)WhiteUnicorn]) {
5808                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5809                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5810             }
5811
5812             while(piecesLeft[(int)WhiteKing]) {
5813                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5814                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5815             }
5816
5817
5818         } else {
5819             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5820             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5821         }
5822
5823         // Only Rooks can be left; simply place them all
5824         while(piecesLeft[(int)WhiteRook]) {
5825                 i = put(board, WhiteRook, 0, 0, ANY);
5826                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5827                         if(first) {
5828                                 first=0;
5829                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5830                         }
5831                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5832                 }
5833         }
5834         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5835             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5836         }
5837
5838         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5839 }
5840
5841 int
5842 SetCharTable (char *table, const char * map)
5843 /* [HGM] moved here from winboard.c because of its general usefulness */
5844 /*       Basically a safe strcpy that uses the last character as King */
5845 {
5846     int result = FALSE; int NrPieces;
5847
5848     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5849                     && NrPieces >= 12 && !(NrPieces&1)) {
5850         int i; /* [HGM] Accept even length from 12 to 34 */
5851
5852         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5853         for( i=0; i<NrPieces/2-1; i++ ) {
5854             table[i] = map[i];
5855             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5856         }
5857         table[(int) WhiteKing]  = map[NrPieces/2-1];
5858         table[(int) BlackKing]  = map[NrPieces-1];
5859
5860         result = TRUE;
5861     }
5862
5863     return result;
5864 }
5865
5866 void
5867 Prelude (Board board)
5868 {       // [HGM] superchess: random selection of exo-pieces
5869         int i, j, k; ChessSquare p;
5870         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5871
5872         GetPositionNumber(); // use FRC position number
5873
5874         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5875             SetCharTable(pieceToChar, appData.pieceToCharTable);
5876             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5877                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5878         }
5879
5880         j = seed%4;                 seed /= 4;
5881         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5882         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5883         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5884         j = seed%3 + (seed%3 >= j); seed /= 3;
5885         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5886         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5887         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5888         j = seed%3;                 seed /= 3;
5889         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5890         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5891         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5892         j = seed%2 + (seed%2 >= j); seed /= 2;
5893         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5894         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5895         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5896         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5897         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5898         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5899         put(board, exoPieces[0],    0, 0, ANY);
5900         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5901 }
5902
5903 void
5904 InitPosition (int redraw)
5905 {
5906     ChessSquare (* pieces)[BOARD_FILES];
5907     int i, j, pawnRow=1, pieceRows=1, overrule,
5908     oldx = gameInfo.boardWidth,
5909     oldy = gameInfo.boardHeight,
5910     oldh = gameInfo.holdingsWidth;
5911     static int oldv;
5912
5913     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5914
5915     /* [AS] Initialize pv info list [HGM] and game status */
5916     {
5917         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5918             pvInfoList[i].depth = 0;
5919             boards[i][EP_STATUS] = EP_NONE;
5920             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5921         }
5922
5923         initialRulePlies = 0; /* 50-move counter start */
5924
5925         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5926         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5927     }
5928
5929
5930     /* [HGM] logic here is completely changed. In stead of full positions */
5931     /* the initialized data only consist of the two backranks. The switch */
5932     /* selects which one we will use, which is than copied to the Board   */
5933     /* initialPosition, which for the rest is initialized by Pawns and    */
5934     /* empty squares. This initial position is then copied to boards[0],  */
5935     /* possibly after shuffling, so that it remains available.            */
5936
5937     gameInfo.holdingsWidth = 0; /* default board sizes */
5938     gameInfo.boardWidth    = 8;
5939     gameInfo.boardHeight   = 8;
5940     gameInfo.holdingsSize  = 0;
5941     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5942     for(i=0; i<BOARD_FILES-2; i++)
5943       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5944     initialPosition[EP_STATUS] = EP_NONE;
5945     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5946     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5947          SetCharTable(pieceNickName, appData.pieceNickNames);
5948     else SetCharTable(pieceNickName, "............");
5949     pieces = FIDEArray;
5950
5951     switch (gameInfo.variant) {
5952     case VariantFischeRandom:
5953       shuffleOpenings = TRUE;
5954     default:
5955       break;
5956     case VariantShatranj:
5957       pieces = ShatranjArray;
5958       nrCastlingRights = 0;
5959       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5960       break;
5961     case VariantMakruk:
5962       pieces = makrukArray;
5963       nrCastlingRights = 0;
5964       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5965       break;
5966     case VariantASEAN:
5967       pieces = aseanArray;
5968       nrCastlingRights = 0;
5969       SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk");
5970       break;
5971     case VariantTwoKings:
5972       pieces = twoKingsArray;
5973       break;
5974     case VariantGrand:
5975       pieces = GrandArray;
5976       nrCastlingRights = 0;
5977       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5978       gameInfo.boardWidth = 10;
5979       gameInfo.boardHeight = 10;
5980       gameInfo.holdingsSize = 7;
5981       break;
5982     case VariantCapaRandom:
5983       shuffleOpenings = TRUE;
5984     case VariantCapablanca:
5985       pieces = CapablancaArray;
5986       gameInfo.boardWidth = 10;
5987       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5988       break;
5989     case VariantGothic:
5990       pieces = GothicArray;
5991       gameInfo.boardWidth = 10;
5992       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5993       break;
5994     case VariantSChess:
5995       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5996       gameInfo.holdingsSize = 7;
5997       for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
5998       break;
5999     case VariantJanus:
6000       pieces = JanusArray;
6001       gameInfo.boardWidth = 10;
6002       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
6003       nrCastlingRights = 6;
6004         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6005         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6006         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
6007         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6008         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6009         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
6010       break;
6011     case VariantFalcon:
6012       pieces = FalconArray;
6013       gameInfo.boardWidth = 10;
6014       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
6015       break;
6016     case VariantXiangqi:
6017       pieces = XiangqiArray;
6018       gameInfo.boardWidth  = 9;
6019       gameInfo.boardHeight = 10;
6020       nrCastlingRights = 0;
6021       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
6022       break;
6023     case VariantShogi:
6024       pieces = ShogiArray;
6025       gameInfo.boardWidth  = 9;
6026       gameInfo.boardHeight = 9;
6027       gameInfo.holdingsSize = 7;
6028       nrCastlingRights = 0;
6029       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
6030       break;
6031     case VariantChu:
6032       pieces = ChuArray; pieceRows = 3;
6033       gameInfo.boardWidth  = 12;
6034       gameInfo.boardHeight = 12;
6035       nrCastlingRights = 0;
6036       SetCharTable(pieceToChar, "P.BRQSEXOGCATHD.VMLIFN+.++.++++++++++.+++++K"
6037                                 "p.brqsexogcathd.vmlifn+.++.++++++++++.+++++k");
6038       break;
6039     case VariantCourier:
6040       pieces = CourierArray;
6041       gameInfo.boardWidth  = 12;
6042       nrCastlingRights = 0;
6043       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
6044       break;
6045     case VariantKnightmate:
6046       pieces = KnightmateArray;
6047       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
6048       break;
6049     case VariantSpartan:
6050       pieces = SpartanArray;
6051       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
6052       break;
6053     case VariantLion:
6054       pieces = lionArray;
6055       SetCharTable(pieceToChar, "PNBRQ................LKpnbrq................lk");
6056       break;
6057     case VariantFairy:
6058       pieces = fairyArray;
6059       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
6060       break;
6061     case VariantGreat:
6062       pieces = GreatArray;
6063       gameInfo.boardWidth = 10;
6064       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
6065       gameInfo.holdingsSize = 8;
6066       break;
6067     case VariantSuper:
6068       pieces = FIDEArray;
6069       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
6070       gameInfo.holdingsSize = 8;
6071       startedFromSetupPosition = TRUE;
6072       break;
6073     case VariantCrazyhouse:
6074     case VariantBughouse:
6075       pieces = FIDEArray;
6076       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
6077       gameInfo.holdingsSize = 5;
6078       break;
6079     case VariantWildCastle:
6080       pieces = FIDEArray;
6081       /* !!?shuffle with kings guaranteed to be on d or e file */
6082       shuffleOpenings = 1;
6083       break;
6084     case VariantNoCastle:
6085       pieces = FIDEArray;
6086       nrCastlingRights = 0;
6087       /* !!?unconstrained back-rank shuffle */
6088       shuffleOpenings = 1;
6089       break;
6090     }
6091
6092     overrule = 0;
6093     if(appData.NrFiles >= 0) {
6094         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6095         gameInfo.boardWidth = appData.NrFiles;
6096     }
6097     if(appData.NrRanks >= 0) {
6098         gameInfo.boardHeight = appData.NrRanks;
6099     }
6100     if(appData.holdingsSize >= 0) {
6101         i = appData.holdingsSize;
6102         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6103         gameInfo.holdingsSize = i;
6104     }
6105     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6106     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6107         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6108
6109     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6110     if(pawnRow < 1) pawnRow = 1;
6111     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN || gameInfo.variant == VariantGrand) pawnRow = 2;
6112     if(gameInfo.variant == VariantChu) pawnRow = 3;
6113
6114     /* User pieceToChar list overrules defaults */
6115     if(appData.pieceToCharTable != NULL)
6116         SetCharTable(pieceToChar, appData.pieceToCharTable);
6117
6118     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6119
6120         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6121             s = (ChessSquare) 0; /* account holding counts in guard band */
6122         for( i=0; i<BOARD_HEIGHT; i++ )
6123             initialPosition[i][j] = s;
6124
6125         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6126         initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
6127         initialPosition[pawnRow][j] = WhitePawn;
6128         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6129         if(gameInfo.variant == VariantXiangqi) {
6130             if(j&1) {
6131                 initialPosition[pawnRow][j] =
6132                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6133                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6134                    initialPosition[2][j] = WhiteCannon;
6135                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6136                 }
6137             }
6138         }
6139         if(gameInfo.variant == VariantChu) {
6140              if(j == (BOARD_WIDTH-2)/3 || j == BOARD_WIDTH - (BOARD_WIDTH+1)/3)
6141                initialPosition[pawnRow+1][j] = WhiteCobra,
6142                initialPosition[BOARD_HEIGHT-pawnRow-2][j] = BlackCobra;
6143              for(i=1; i<pieceRows; i++) {
6144                initialPosition[i][j] = pieces[2*i][j-gameInfo.holdingsWidth];
6145                initialPosition[BOARD_HEIGHT-1-i][j] =  pieces[2*i+1][j-gameInfo.holdingsWidth];
6146              }
6147         }
6148         if(gameInfo.variant == VariantGrand) {
6149             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6150                initialPosition[0][j] = WhiteRook;
6151                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6152             }
6153         }
6154         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] =  pieces[1][j-gameInfo.holdingsWidth];
6155     }
6156     if( (gameInfo.variant == VariantShogi) && !overrule ) {
6157
6158             j=BOARD_LEFT+1;
6159             initialPosition[1][j] = WhiteBishop;
6160             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6161             j=BOARD_RGHT-2;
6162             initialPosition[1][j] = WhiteRook;
6163             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6164     }
6165
6166     if( nrCastlingRights == -1) {
6167         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6168         /*       This sets default castling rights from none to normal corners   */
6169         /* Variants with other castling rights must set them themselves above    */
6170         nrCastlingRights = 6;
6171
6172         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6173         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6174         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6175         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6176         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6177         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6178      }
6179
6180      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6181      if(gameInfo.variant == VariantGreat) { // promotion commoners
6182         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6183         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6184         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6185         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6186      }
6187      if( gameInfo.variant == VariantSChess ) {
6188       initialPosition[1][0] = BlackMarshall;
6189       initialPosition[2][0] = BlackAngel;
6190       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6191       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6192       initialPosition[1][1] = initialPosition[2][1] =
6193       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6194      }
6195   if (appData.debugMode) {
6196     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6197   }
6198     if(shuffleOpenings) {
6199         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6200         startedFromSetupPosition = TRUE;
6201     }
6202     if(startedFromPositionFile) {
6203       /* [HGM] loadPos: use PositionFile for every new game */
6204       CopyBoard(initialPosition, filePosition);
6205       for(i=0; i<nrCastlingRights; i++)
6206           initialRights[i] = filePosition[CASTLING][i];
6207       startedFromSetupPosition = TRUE;
6208     }
6209
6210     CopyBoard(boards[0], initialPosition);
6211
6212     if(oldx != gameInfo.boardWidth ||
6213        oldy != gameInfo.boardHeight ||
6214        oldv != gameInfo.variant ||
6215        oldh != gameInfo.holdingsWidth
6216                                          )
6217             InitDrawingSizes(-2 ,0);
6218
6219     oldv = gameInfo.variant;
6220     if (redraw)
6221       DrawPosition(TRUE, boards[currentMove]);
6222 }
6223
6224 void
6225 SendBoard (ChessProgramState *cps, int moveNum)
6226 {
6227     char message[MSG_SIZ];
6228
6229     if (cps->useSetboard) {
6230       char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6231       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6232       SendToProgram(message, cps);
6233       free(fen);
6234
6235     } else {
6236       ChessSquare *bp;
6237       int i, j, left=0, right=BOARD_WIDTH;
6238       /* Kludge to set black to move, avoiding the troublesome and now
6239        * deprecated "black" command.
6240        */
6241       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6242         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6243
6244       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6245
6246       SendToProgram("edit\n", cps);
6247       SendToProgram("#\n", cps);
6248       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6249         bp = &boards[moveNum][i][left];
6250         for (j = left; j < right; j++, bp++) {
6251           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6252           if ((int) *bp < (int) BlackPawn) {
6253             if(j == BOARD_RGHT+1)
6254                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6255             else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6256             if(message[0] == '+' || message[0] == '~') {
6257               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6258                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6259                         AAA + j, ONE + i);
6260             }
6261             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6262                 message[1] = BOARD_RGHT   - 1 - j + '1';
6263                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6264             }
6265             SendToProgram(message, cps);
6266           }
6267         }
6268       }
6269
6270       SendToProgram("c\n", cps);
6271       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6272         bp = &boards[moveNum][i][left];
6273         for (j = left; j < right; j++, bp++) {
6274           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6275           if (((int) *bp != (int) EmptySquare)
6276               && ((int) *bp >= (int) BlackPawn)) {
6277             if(j == BOARD_LEFT-2)
6278                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6279             else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6280                     AAA + j, ONE + i);
6281             if(message[0] == '+' || message[0] == '~') {
6282               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6283                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6284                         AAA + j, ONE + i);
6285             }
6286             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6287                 message[1] = BOARD_RGHT   - 1 - j + '1';
6288                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6289             }
6290             SendToProgram(message, cps);
6291           }
6292         }
6293       }
6294
6295       SendToProgram(".\n", cps);
6296     }
6297     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6298 }
6299
6300 char exclusionHeader[MSG_SIZ];
6301 int exCnt, excludePtr;
6302 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6303 static Exclusion excluTab[200];
6304 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6305
6306 static void
6307 WriteMap (int s)
6308 {
6309     int j;
6310     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6311     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6312 }
6313
6314 static void
6315 ClearMap ()
6316 {
6317     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6318     excludePtr = 24; exCnt = 0;
6319     WriteMap(0);
6320 }
6321
6322 static void
6323 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6324 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6325     char buf[2*MOVE_LEN], *p;
6326     Exclusion *e = excluTab;
6327     int i;
6328     for(i=0; i<exCnt; i++)
6329         if(e[i].ff == fromX && e[i].fr == fromY &&
6330            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6331     if(i == exCnt) { // was not in exclude list; add it
6332         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6333         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6334             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6335             return; // abort
6336         }
6337         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6338         excludePtr++; e[i].mark = excludePtr++;
6339         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6340         exCnt++;
6341     }
6342     exclusionHeader[e[i].mark] = state;
6343 }
6344
6345 static int
6346 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6347 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6348     char buf[MSG_SIZ];
6349     int j, k;
6350     ChessMove moveType;
6351     if((signed char)promoChar == -1) { // kludge to indicate best move
6352         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6353             return 1; // if unparsable, abort
6354     }
6355     // update exclusion map (resolving toggle by consulting existing state)
6356     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6357     j = k%8; k >>= 3;
6358     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6359     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6360          excludeMap[k] |=   1<<j;
6361     else excludeMap[k] &= ~(1<<j);
6362     // update header
6363     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6364     // inform engine
6365     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6366     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6367     SendToBoth(buf);
6368     return (state == '+');
6369 }
6370
6371 static void
6372 ExcludeClick (int index)
6373 {
6374     int i, j;
6375     Exclusion *e = excluTab;
6376     if(index < 25) { // none, best or tail clicked
6377         if(index < 13) { // none: include all
6378             WriteMap(0); // clear map
6379             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6380             SendToBoth("include all\n"); // and inform engine
6381         } else if(index > 18) { // tail
6382             if(exclusionHeader[19] == '-') { // tail was excluded
6383                 SendToBoth("include all\n");
6384                 WriteMap(0); // clear map completely
6385                 // now re-exclude selected moves
6386                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6387                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6388             } else { // tail was included or in mixed state
6389                 SendToBoth("exclude all\n");
6390                 WriteMap(0xFF); // fill map completely
6391                 // now re-include selected moves
6392                 j = 0; // count them
6393                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6394                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6395                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6396             }
6397         } else { // best
6398             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6399         }
6400     } else {
6401         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6402             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6403             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6404             break;
6405         }
6406     }
6407 }
6408
6409 ChessSquare
6410 DefaultPromoChoice (int white)
6411 {
6412     ChessSquare result;
6413     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6414        gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
6415         result = WhiteFerz; // no choice
6416     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6417         result= WhiteKing; // in Suicide Q is the last thing we want
6418     else if(gameInfo.variant == VariantSpartan)
6419         result = white ? WhiteQueen : WhiteAngel;
6420     else result = WhiteQueen;
6421     if(!white) result = WHITE_TO_BLACK result;
6422     return result;
6423 }
6424
6425 static int autoQueen; // [HGM] oneclick
6426
6427 int
6428 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6429 {
6430     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6431     /* [HGM] add Shogi promotions */
6432     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6433     ChessSquare piece;
6434     ChessMove moveType;
6435     Boolean premove;
6436
6437     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6438     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6439
6440     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6441       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6442         return FALSE;
6443
6444     piece = boards[currentMove][fromY][fromX];
6445     if(gameInfo.variant == VariantChu) {
6446         int p = piece >= BlackPawn ? BLACK_TO_WHITE piece : piece;
6447         promotionZoneSize = BOARD_HEIGHT/3;
6448         highestPromotingPiece = (p >= WhiteLion || PieceToChar(piece + 22) == '.') ? WhitePawn : WhiteLion;
6449     } else if(gameInfo.variant == VariantShogi) {
6450         promotionZoneSize = BOARD_HEIGHT/3;
6451         highestPromotingPiece = (int)WhiteAlfil;
6452     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6453         promotionZoneSize = 3;
6454     }
6455
6456     // Treat Lance as Pawn when it is not representing Amazon
6457     if(gameInfo.variant != VariantSuper) {
6458         if(piece == WhiteLance) piece = WhitePawn; else
6459         if(piece == BlackLance) piece = BlackPawn;
6460     }
6461
6462     // next weed out all moves that do not touch the promotion zone at all
6463     if((int)piece >= BlackPawn) {
6464         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6465              return FALSE;
6466         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6467     } else {
6468         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6469            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6470     }
6471
6472     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6473
6474     // weed out mandatory Shogi promotions
6475     if(gameInfo.variant == VariantShogi) {
6476         if(piece >= BlackPawn) {
6477             if(toY == 0 && piece == BlackPawn ||
6478                toY == 0 && piece == BlackQueen ||
6479                toY <= 1 && piece == BlackKnight) {
6480                 *promoChoice = '+';
6481                 return FALSE;
6482             }
6483         } else {
6484             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6485                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6486                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6487                 *promoChoice = '+';
6488                 return FALSE;
6489             }
6490         }
6491     }
6492
6493     // weed out obviously illegal Pawn moves
6494     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6495         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6496         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6497         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6498         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6499         // note we are not allowed to test for valid (non-)capture, due to premove
6500     }
6501
6502     // we either have a choice what to promote to, or (in Shogi) whether to promote
6503     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6504        gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN) {
6505         *promoChoice = PieceToChar(BlackFerz);  // no choice
6506         return FALSE;
6507     }
6508     // no sense asking what we must promote to if it is going to explode...
6509     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6510         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6511         return FALSE;
6512     }
6513     // give caller the default choice even if we will not make it
6514     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6515     if(IS_SHOGI(gameInfo.variant)) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6516     if(        sweepSelect && gameInfo.variant != VariantGreat
6517                            && gameInfo.variant != VariantGrand
6518                            && gameInfo.variant != VariantSuper) return FALSE;
6519     if(autoQueen) return FALSE; // predetermined
6520
6521     // suppress promotion popup on illegal moves that are not premoves
6522     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6523               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6524     if(appData.testLegality && !premove) {
6525         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6526                         fromY, fromX, toY, toX, IS_SHOGI(gameInfo.variant) ? '+' : NULLCHAR);
6527         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6528             return FALSE;
6529     }
6530
6531     return TRUE;
6532 }
6533
6534 int
6535 InPalace (int row, int column)
6536 {   /* [HGM] for Xiangqi */
6537     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6538          column < (BOARD_WIDTH + 4)/2 &&
6539          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6540     return FALSE;
6541 }
6542
6543 int
6544 PieceForSquare (int x, int y)
6545 {
6546   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6547      return -1;
6548   else
6549      return boards[currentMove][y][x];
6550 }
6551
6552 int
6553 OKToStartUserMove (int x, int y)
6554 {
6555     ChessSquare from_piece;
6556     int white_piece;
6557
6558     if (matchMode) return FALSE;
6559     if (gameMode == EditPosition) return TRUE;
6560
6561     if (x >= 0 && y >= 0)
6562       from_piece = boards[currentMove][y][x];
6563     else
6564       from_piece = EmptySquare;
6565
6566     if (from_piece == EmptySquare) return FALSE;
6567
6568     white_piece = (int)from_piece >= (int)WhitePawn &&
6569       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6570
6571     switch (gameMode) {
6572       case AnalyzeFile:
6573       case TwoMachinesPlay:
6574       case EndOfGame:
6575         return FALSE;
6576
6577       case IcsObserving:
6578       case IcsIdle:
6579         return FALSE;
6580
6581       case MachinePlaysWhite:
6582       case IcsPlayingBlack:
6583         if (appData.zippyPlay) return FALSE;
6584         if (white_piece) {
6585             DisplayMoveError(_("You are playing Black"));
6586             return FALSE;
6587         }
6588         break;
6589
6590       case MachinePlaysBlack:
6591       case IcsPlayingWhite:
6592         if (appData.zippyPlay) return FALSE;
6593         if (!white_piece) {
6594             DisplayMoveError(_("You are playing White"));
6595             return FALSE;
6596         }
6597         break;
6598
6599       case PlayFromGameFile:
6600             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6601       case EditGame:
6602         if (!white_piece && WhiteOnMove(currentMove)) {
6603             DisplayMoveError(_("It is White's turn"));
6604             return FALSE;
6605         }
6606         if (white_piece && !WhiteOnMove(currentMove)) {
6607             DisplayMoveError(_("It is Black's turn"));
6608             return FALSE;
6609         }
6610         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6611             /* Editing correspondence game history */
6612             /* Could disallow this or prompt for confirmation */
6613             cmailOldMove = -1;
6614         }
6615         break;
6616
6617       case BeginningOfGame:
6618         if (appData.icsActive) return FALSE;
6619         if (!appData.noChessProgram) {
6620             if (!white_piece) {
6621                 DisplayMoveError(_("You are playing White"));
6622                 return FALSE;
6623             }
6624         }
6625         break;
6626
6627       case Training:
6628         if (!white_piece && WhiteOnMove(currentMove)) {
6629             DisplayMoveError(_("It is White's turn"));
6630             return FALSE;
6631         }
6632         if (white_piece && !WhiteOnMove(currentMove)) {
6633             DisplayMoveError(_("It is Black's turn"));
6634             return FALSE;
6635         }
6636         break;
6637
6638       default:
6639       case IcsExamining:
6640         break;
6641     }
6642     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6643         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6644         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6645         && gameMode != AnalyzeFile && gameMode != Training) {
6646         DisplayMoveError(_("Displayed position is not current"));
6647         return FALSE;
6648     }
6649     return TRUE;
6650 }
6651
6652 Boolean
6653 OnlyMove (int *x, int *y, Boolean captures)
6654 {
6655     DisambiguateClosure cl;
6656     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6657     switch(gameMode) {
6658       case MachinePlaysBlack:
6659       case IcsPlayingWhite:
6660       case BeginningOfGame:
6661         if(!WhiteOnMove(currentMove)) return FALSE;
6662         break;
6663       case MachinePlaysWhite:
6664       case IcsPlayingBlack:
6665         if(WhiteOnMove(currentMove)) return FALSE;
6666         break;
6667       case EditGame:
6668         break;
6669       default:
6670         return FALSE;
6671     }
6672     cl.pieceIn = EmptySquare;
6673     cl.rfIn = *y;
6674     cl.ffIn = *x;
6675     cl.rtIn = -1;
6676     cl.ftIn = -1;
6677     cl.promoCharIn = NULLCHAR;
6678     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6679     if( cl.kind == NormalMove ||
6680         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6681         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6682         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6683       fromX = cl.ff;
6684       fromY = cl.rf;
6685       *x = cl.ft;
6686       *y = cl.rt;
6687       return TRUE;
6688     }
6689     if(cl.kind != ImpossibleMove) return FALSE;
6690     cl.pieceIn = EmptySquare;
6691     cl.rfIn = -1;
6692     cl.ffIn = -1;
6693     cl.rtIn = *y;
6694     cl.ftIn = *x;
6695     cl.promoCharIn = NULLCHAR;
6696     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6697     if( cl.kind == NormalMove ||
6698         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6699         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6700         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6701       fromX = cl.ff;
6702       fromY = cl.rf;
6703       *x = cl.ft;
6704       *y = cl.rt;
6705       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6706       return TRUE;
6707     }
6708     return FALSE;
6709 }
6710
6711 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6712 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6713 int lastLoadGameUseList = FALSE;
6714 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6715 ChessMove lastLoadGameStart = EndOfFile;
6716 int doubleClick;
6717
6718 void
6719 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6720 {
6721     ChessMove moveType;
6722     ChessSquare pup;
6723     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6724
6725     /* Check if the user is playing in turn.  This is complicated because we
6726        let the user "pick up" a piece before it is his turn.  So the piece he
6727        tried to pick up may have been captured by the time he puts it down!
6728        Therefore we use the color the user is supposed to be playing in this
6729        test, not the color of the piece that is currently on the starting
6730        square---except in EditGame mode, where the user is playing both
6731        sides; fortunately there the capture race can't happen.  (It can
6732        now happen in IcsExamining mode, but that's just too bad.  The user
6733        will get a somewhat confusing message in that case.)
6734        */
6735
6736     switch (gameMode) {
6737       case AnalyzeFile:
6738       case TwoMachinesPlay:
6739       case EndOfGame:
6740       case IcsObserving:
6741       case IcsIdle:
6742         /* We switched into a game mode where moves are not accepted,
6743            perhaps while the mouse button was down. */
6744         return;
6745
6746       case MachinePlaysWhite:
6747         /* User is moving for Black */
6748         if (WhiteOnMove(currentMove)) {
6749             DisplayMoveError(_("It is White's turn"));
6750             return;
6751         }
6752         break;
6753
6754       case MachinePlaysBlack:
6755         /* User is moving for White */
6756         if (!WhiteOnMove(currentMove)) {
6757             DisplayMoveError(_("It is Black's turn"));
6758             return;
6759         }
6760         break;
6761
6762       case PlayFromGameFile:
6763             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6764       case EditGame:
6765       case IcsExamining:
6766       case BeginningOfGame:
6767       case AnalyzeMode:
6768       case Training:
6769         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6770         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6771             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6772             /* User is moving for Black */
6773             if (WhiteOnMove(currentMove)) {
6774                 DisplayMoveError(_("It is White's turn"));
6775                 return;
6776             }
6777         } else {
6778             /* User is moving for White */
6779             if (!WhiteOnMove(currentMove)) {
6780                 DisplayMoveError(_("It is Black's turn"));
6781                 return;
6782             }
6783         }
6784         break;
6785
6786       case IcsPlayingBlack:
6787         /* User is moving for Black */
6788         if (WhiteOnMove(currentMove)) {
6789             if (!appData.premove) {
6790                 DisplayMoveError(_("It is White's turn"));
6791             } else if (toX >= 0 && toY >= 0) {
6792                 premoveToX = toX;
6793                 premoveToY = toY;
6794                 premoveFromX = fromX;
6795                 premoveFromY = fromY;
6796                 premovePromoChar = promoChar;
6797                 gotPremove = 1;
6798                 if (appData.debugMode)
6799                     fprintf(debugFP, "Got premove: fromX %d,"
6800                             "fromY %d, toX %d, toY %d\n",
6801                             fromX, fromY, toX, toY);
6802             }
6803             return;
6804         }
6805         break;
6806
6807       case IcsPlayingWhite:
6808         /* User is moving for White */
6809         if (!WhiteOnMove(currentMove)) {
6810             if (!appData.premove) {
6811                 DisplayMoveError(_("It is Black's turn"));
6812             } else if (toX >= 0 && toY >= 0) {
6813                 premoveToX = toX;
6814                 premoveToY = toY;
6815                 premoveFromX = fromX;
6816                 premoveFromY = fromY;
6817                 premovePromoChar = promoChar;
6818                 gotPremove = 1;
6819                 if (appData.debugMode)
6820                     fprintf(debugFP, "Got premove: fromX %d,"
6821                             "fromY %d, toX %d, toY %d\n",
6822                             fromX, fromY, toX, toY);
6823             }
6824             return;
6825         }
6826         break;
6827
6828       default:
6829         break;
6830
6831       case EditPosition:
6832         /* EditPosition, empty square, or different color piece;
6833            click-click move is possible */
6834         if (toX == -2 || toY == -2) {
6835             boards[0][fromY][fromX] = EmptySquare;
6836             DrawPosition(FALSE, boards[currentMove]);
6837             return;
6838         } else if (toX >= 0 && toY >= 0) {
6839             boards[0][toY][toX] = boards[0][fromY][fromX];
6840             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6841                 if(boards[0][fromY][0] != EmptySquare) {
6842                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6843                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6844                 }
6845             } else
6846             if(fromX == BOARD_RGHT+1) {
6847                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6848                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6849                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6850                 }
6851             } else
6852             boards[0][fromY][fromX] = gatingPiece;
6853             DrawPosition(FALSE, boards[currentMove]);
6854             return;
6855         }
6856         return;
6857     }
6858
6859     if(toX < 0 || toY < 0) return;
6860     pup = boards[currentMove][toY][toX];
6861
6862     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6863     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6864          if( pup != EmptySquare ) return;
6865          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6866            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6867                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6868            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6869            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6870            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6871            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6872          fromY = DROP_RANK;
6873     }
6874
6875     /* [HGM] always test for legality, to get promotion info */
6876     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6877                                          fromY, fromX, toY, toX, promoChar);
6878
6879     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6880
6881     /* [HGM] but possibly ignore an IllegalMove result */
6882     if (appData.testLegality) {
6883         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6884             DisplayMoveError(_("Illegal move"));
6885             return;
6886         }
6887     }
6888
6889     if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
6890         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
6891              ClearPremoveHighlights(); // was included
6892         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
6893         return;
6894     }
6895
6896     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6897 }
6898
6899 /* Common tail of UserMoveEvent and DropMenuEvent */
6900 int
6901 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
6902 {
6903     char *bookHit = 0;
6904
6905     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6906         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6907         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6908         if(WhiteOnMove(currentMove)) {
6909             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6910         } else {
6911             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6912         }
6913     }
6914
6915     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6916        move type in caller when we know the move is a legal promotion */
6917     if(moveType == NormalMove && promoChar)
6918         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6919
6920     /* [HGM] <popupFix> The following if has been moved here from
6921        UserMoveEvent(). Because it seemed to belong here (why not allow
6922        piece drops in training games?), and because it can only be
6923        performed after it is known to what we promote. */
6924     if (gameMode == Training) {
6925       /* compare the move played on the board to the next move in the
6926        * game. If they match, display the move and the opponent's response.
6927        * If they don't match, display an error message.
6928        */
6929       int saveAnimate;
6930       Board testBoard;
6931       CopyBoard(testBoard, boards[currentMove]);
6932       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6933
6934       if (CompareBoards(testBoard, boards[currentMove+1])) {
6935         ForwardInner(currentMove+1);
6936
6937         /* Autoplay the opponent's response.
6938          * if appData.animate was TRUE when Training mode was entered,
6939          * the response will be animated.
6940          */
6941         saveAnimate = appData.animate;
6942         appData.animate = animateTraining;
6943         ForwardInner(currentMove+1);
6944         appData.animate = saveAnimate;
6945
6946         /* check for the end of the game */
6947         if (currentMove >= forwardMostMove) {
6948           gameMode = PlayFromGameFile;
6949           ModeHighlight();
6950           SetTrainingModeOff();
6951           DisplayInformation(_("End of game"));
6952         }
6953       } else {
6954         DisplayError(_("Incorrect move"), 0);
6955       }
6956       return 1;
6957     }
6958
6959   /* Ok, now we know that the move is good, so we can kill
6960      the previous line in Analysis Mode */
6961   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6962                                 && currentMove < forwardMostMove) {
6963     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6964     else forwardMostMove = currentMove;
6965   }
6966
6967   ClearMap();
6968
6969   /* If we need the chess program but it's dead, restart it */
6970   ResurrectChessProgram();
6971
6972   /* A user move restarts a paused game*/
6973   if (pausing)
6974     PauseEvent();
6975
6976   thinkOutput[0] = NULLCHAR;
6977
6978   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6979
6980   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6981     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6982     return 1;
6983   }
6984
6985   if (gameMode == BeginningOfGame) {
6986     if (appData.noChessProgram) {
6987       gameMode = EditGame;
6988       SetGameInfo();
6989     } else {
6990       char buf[MSG_SIZ];
6991       gameMode = MachinePlaysBlack;
6992       StartClocks();
6993       SetGameInfo();
6994       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
6995       DisplayTitle(buf);
6996       if (first.sendName) {
6997         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6998         SendToProgram(buf, &first);
6999       }
7000       StartClocks();
7001     }
7002     ModeHighlight();
7003   }
7004
7005   /* Relay move to ICS or chess engine */
7006   if (appData.icsActive) {
7007     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
7008         gameMode == IcsExamining) {
7009       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7010         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7011         SendToICS("draw ");
7012         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7013       }
7014       // also send plain move, in case ICS does not understand atomic claims
7015       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7016       ics_user_moved = 1;
7017     }
7018   } else {
7019     if (first.sendTime && (gameMode == BeginningOfGame ||
7020                            gameMode == MachinePlaysWhite ||
7021                            gameMode == MachinePlaysBlack)) {
7022       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
7023     }
7024     if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
7025          // [HGM] book: if program might be playing, let it use book
7026         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
7027         first.maybeThinking = TRUE;
7028     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
7029         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
7030         SendBoard(&first, currentMove+1);
7031         if(second.analyzing) {
7032             if(!second.useSetboard) SendToProgram("undo\n", &second);
7033             SendBoard(&second, currentMove+1);
7034         }
7035     } else {
7036         SendMoveToProgram(forwardMostMove-1, &first);
7037         if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
7038     }
7039     if (currentMove == cmailOldMove + 1) {
7040       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
7041     }
7042   }
7043
7044   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7045
7046   switch (gameMode) {
7047   case EditGame:
7048     if(appData.testLegality)
7049     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
7050     case MT_NONE:
7051     case MT_CHECK:
7052       break;
7053     case MT_CHECKMATE:
7054     case MT_STAINMATE:
7055       if (WhiteOnMove(currentMove)) {
7056         GameEnds(BlackWins, "Black mates", GE_PLAYER);
7057       } else {
7058         GameEnds(WhiteWins, "White mates", GE_PLAYER);
7059       }
7060       break;
7061     case MT_STALEMATE:
7062       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
7063       break;
7064     }
7065     break;
7066
7067   case MachinePlaysBlack:
7068   case MachinePlaysWhite:
7069     /* disable certain menu options while machine is thinking */
7070     SetMachineThinkingEnables();
7071     break;
7072
7073   default:
7074     break;
7075   }
7076
7077   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7078   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7079
7080   if(bookHit) { // [HGM] book: simulate book reply
7081         static char bookMove[MSG_SIZ]; // a bit generous?
7082
7083         programStats.nodes = programStats.depth = programStats.time =
7084         programStats.score = programStats.got_only_move = 0;
7085         sprintf(programStats.movelist, "%s (xbook)", bookHit);
7086
7087         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7088         strcat(bookMove, bookHit);
7089         HandleMachineMove(bookMove, &first);
7090   }
7091   return 1;
7092 }
7093
7094 void
7095 MarkByFEN(char *fen)
7096 {
7097         int r, f;
7098         if(!appData.markers || !appData.highlightDragging) return;
7099         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 0;
7100         r=BOARD_HEIGHT-1; f=BOARD_LEFT;
7101         while(*fen) {
7102             int s = 0;
7103             marker[r][f] = 0;
7104             if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7105             if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 1; else
7106             if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7107             if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7108             if(*fen == 'T') marker[r][f++] = 0; else
7109             if(*fen == 'Y') marker[r][f++] = 1; else
7110             if(*fen == 'G') marker[r][f++] = 3; else
7111             if(*fen == 'B') marker[r][f++] = 4; else
7112             if(*fen == 'C') marker[r][f++] = 5; else
7113             if(*fen == 'M') marker[r][f++] = 6; else
7114             if(*fen == 'W') marker[r][f++] = 7; else
7115             if(*fen == 'D') marker[r][f++] = 8; else
7116             if(*fen == 'R') marker[r][f++] = 2; else {
7117                 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7118               f += s; fen -= s>0;
7119             }
7120             while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7121             if(r < 0) break;
7122             fen++;
7123         }
7124         DrawPosition(TRUE, NULL);
7125 }
7126
7127 static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7128
7129 void
7130 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7131 {
7132     typedef char Markers[BOARD_RANKS][BOARD_FILES];
7133     Markers *m = (Markers *) closure;
7134     if(rf == fromY && ff == fromX && (killX < 0 && !(rt == rf && ft == ff) || abs(ft-killX) < 2 && abs(rt-killY) < 2))
7135         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7136                          || kind == WhiteCapturesEnPassant
7137                          || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && killX < 0);
7138     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
7139 }
7140
7141 void
7142 MarkTargetSquares (int clear)
7143 {
7144   int x, y, sum=0;
7145   if(clear) { // no reason to ever suppress clearing
7146     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = baseMarker[y][x] = 0;
7147     if(!sum) return; // nothing was cleared,no redraw needed
7148   } else {
7149     int capt = 0;
7150     if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7151        !appData.testLegality || gameMode == EditPosition) return;
7152     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7153     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7154       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7155       if(capt)
7156       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7157     }
7158   }
7159   DrawPosition(FALSE, NULL);
7160 }
7161
7162 int
7163 Explode (Board board, int fromX, int fromY, int toX, int toY)
7164 {
7165     if(gameInfo.variant == VariantAtomic &&
7166        (board[toY][toX] != EmptySquare ||                     // capture?
7167         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
7168                          board[fromY][fromX] == BlackPawn   )
7169       )) {
7170         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7171         return TRUE;
7172     }
7173     return FALSE;
7174 }
7175
7176 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7177
7178 int
7179 CanPromote (ChessSquare piece, int y)
7180 {
7181         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7182         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7183         if(IS_SHOGI(gameInfo.variant)          || gameInfo.variant == VariantXiangqi ||
7184            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7185            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7186          gameInfo.variant == VariantMakruk   || gameInfo.variant == VariantASEAN) return FALSE;
7187         return (piece == BlackPawn && y == 1 ||
7188                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
7189                 piece == BlackLance && y == 1 ||
7190                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
7191 }
7192
7193 void
7194 HoverEvent (int xPix, int yPix, int x, int y)
7195 {
7196         static int oldX = -1, oldY = -1, oldFromX = -1, oldFromY = -1;
7197         int r, f;
7198         if(!first.highlight) return;
7199         if(fromX != oldFromX || fromY != oldFromY)  oldX = oldY = -1; // kludge to fake entry on from-click
7200         if(x == oldX && y == oldY) return; // only do something if we enter new square
7201         oldFromX = fromX; oldFromY = fromY;
7202         if(oldX == -1 && oldY == -1 && x == fromX && y == fromY) // record markings after from-change
7203           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7204             baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7205         else if(oldX != x || oldY != y) {
7206           // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7207           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7208             marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7209           if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7210             char buf[MSG_SIZ];
7211             snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7212             SendToProgram(buf, &first);
7213           }
7214           oldX = x; oldY = y;
7215 //        SetHighlights(fromX, fromY, x, y);
7216         }
7217 }
7218
7219 void ReportClick(char *action, int x, int y)
7220 {
7221         char buf[MSG_SIZ]; // Inform engine of what user does
7222         int r, f;
7223         if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7224           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 1, marker[r][f] = 0;
7225         if(!first.highlight || gameMode == EditPosition) return;
7226         snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7227         SendToProgram(buf, &first);
7228 }
7229
7230 void
7231 LeftClick (ClickType clickType, int xPix, int yPix)
7232 {
7233     int x, y;
7234     Boolean saveAnimate;
7235     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7236     char promoChoice = NULLCHAR;
7237     ChessSquare piece;
7238     static TimeMark lastClickTime, prevClickTime;
7239
7240     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7241
7242     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7243
7244     if (clickType == Press) ErrorPopDown();
7245     lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7246
7247     x = EventToSquare(xPix, BOARD_WIDTH);
7248     y = EventToSquare(yPix, BOARD_HEIGHT);
7249     if (!flipView && y >= 0) {
7250         y = BOARD_HEIGHT - 1 - y;
7251     }
7252     if (flipView && x >= 0) {
7253         x = BOARD_WIDTH - 1 - x;
7254     }
7255
7256     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7257         defaultPromoChoice = promoSweep;
7258         promoSweep = EmptySquare;   // terminate sweep
7259         promoDefaultAltered = TRUE;
7260         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7261     }
7262
7263     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7264         if(clickType == Release) return; // ignore upclick of click-click destination
7265         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7266         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7267         if(gameInfo.holdingsWidth &&
7268                 (WhiteOnMove(currentMove)
7269                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7270                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7271             // click in right holdings, for determining promotion piece
7272             ChessSquare p = boards[currentMove][y][x];
7273             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7274             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7275             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7276                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7277                 fromX = fromY = -1;
7278                 return;
7279             }
7280         }
7281         DrawPosition(FALSE, boards[currentMove]);
7282         return;
7283     }
7284
7285     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7286     if(clickType == Press
7287             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7288               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7289               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7290         return;
7291
7292     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7293         // could be static click on premove from-square: abort premove
7294         gotPremove = 0;
7295         ClearPremoveHighlights();
7296     }
7297
7298     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7299         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7300
7301     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7302         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7303                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7304         defaultPromoChoice = DefaultPromoChoice(side);
7305     }
7306
7307     autoQueen = appData.alwaysPromoteToQueen;
7308
7309     if (fromX == -1) {
7310       int originalY = y;
7311       gatingPiece = EmptySquare;
7312       if (clickType != Press) {
7313         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7314             DragPieceEnd(xPix, yPix); dragging = 0;
7315             DrawPosition(FALSE, NULL);
7316         }
7317         return;
7318       }
7319       doubleClick = FALSE;
7320       if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7321         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7322       }
7323       fromX = x; fromY = y; toX = toY = killX = killY = -1;
7324       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7325          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7326          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7327             /* First square */
7328             if (OKToStartUserMove(fromX, fromY)) {
7329                 second = 0;
7330                 ReportClick("lift", x, y);
7331                 MarkTargetSquares(0);
7332                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7333                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7334                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7335                     promoSweep = defaultPromoChoice;
7336                     selectFlag = 0; lastX = xPix; lastY = yPix;
7337                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7338                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7339                 }
7340                 if (appData.highlightDragging) {
7341                     SetHighlights(fromX, fromY, -1, -1);
7342                 } else {
7343                     ClearHighlights();
7344                 }
7345             } else fromX = fromY = -1;
7346             return;
7347         }
7348     }
7349
7350     /* fromX != -1 */
7351     if (clickType == Press && gameMode != EditPosition) {
7352         ChessSquare fromP;
7353         ChessSquare toP;
7354         int frc;
7355
7356         // ignore off-board to clicks
7357         if(y < 0 || x < 0) return;
7358
7359         /* Check if clicking again on the same color piece */
7360         fromP = boards[currentMove][fromY][fromX];
7361         toP = boards[currentMove][y][x];
7362         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
7363         if( (killX < 0 || x != fromX || y != fromY) && // [HGM] lion: do not interpret igui as deselect!
7364            ((WhitePawn <= fromP && fromP <= WhiteKing &&
7365              WhitePawn <= toP && toP <= WhiteKing &&
7366              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7367              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7368             (BlackPawn <= fromP && fromP <= BlackKing &&
7369              BlackPawn <= toP && toP <= BlackKing &&
7370              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7371              !(fromP == BlackKing && toP == BlackRook && frc)))) {
7372             /* Clicked again on same color piece -- changed his mind */
7373             second = (x == fromX && y == fromY);
7374             killX = killY = -1;
7375             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7376                 second = FALSE; // first double-click rather than scond click
7377                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7378             }
7379             promoDefaultAltered = FALSE;
7380             MarkTargetSquares(1);
7381            if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7382             if (appData.highlightDragging) {
7383                 SetHighlights(x, y, -1, -1);
7384             } else {
7385                 ClearHighlights();
7386             }
7387             if (OKToStartUserMove(x, y)) {
7388                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7389                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7390                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7391                  gatingPiece = boards[currentMove][fromY][fromX];
7392                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7393                 fromX = x;
7394                 fromY = y; dragging = 1;
7395                 ReportClick("lift", x, y);
7396                 MarkTargetSquares(0);
7397                 DragPieceBegin(xPix, yPix, FALSE);
7398                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7399                     promoSweep = defaultPromoChoice;
7400                     selectFlag = 0; lastX = xPix; lastY = yPix;
7401                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7402                 }
7403             }
7404            }
7405            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7406            second = FALSE;
7407         }
7408         // ignore clicks on holdings
7409         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7410     }
7411
7412     if (clickType == Release && x == fromX && y == fromY && killX < 0) {
7413         DragPieceEnd(xPix, yPix); dragging = 0;
7414         if(clearFlag) {
7415             // a deferred attempt to click-click move an empty square on top of a piece
7416             boards[currentMove][y][x] = EmptySquare;
7417             ClearHighlights();
7418             DrawPosition(FALSE, boards[currentMove]);
7419             fromX = fromY = -1; clearFlag = 0;
7420             return;
7421         }
7422         if (appData.animateDragging) {
7423             /* Undo animation damage if any */
7424             DrawPosition(FALSE, NULL);
7425         }
7426         if (second || sweepSelecting) {
7427             /* Second up/down in same square; just abort move */
7428             if(sweepSelecting) DrawPosition(FALSE, boards[currentMove]);
7429             second = sweepSelecting = 0;
7430             fromX = fromY = -1;
7431             gatingPiece = EmptySquare;
7432             MarkTargetSquares(1);
7433             ClearHighlights();
7434             gotPremove = 0;
7435             ClearPremoveHighlights();
7436         } else {
7437             /* First upclick in same square; start click-click mode */
7438             SetHighlights(x, y, -1, -1);
7439         }
7440         return;
7441     }
7442
7443     clearFlag = 0;
7444
7445     if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] && (x != killX || y != killY) && !sweepSelecting) {
7446         if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7447         DisplayMessage(_("only marked squares are legal"),"");
7448         DrawPosition(TRUE, NULL);
7449         return; // ignore to-click
7450     }
7451
7452     /* we now have a different from- and (possibly off-board) to-square */
7453     /* Completed move */
7454     if(!sweepSelecting) {
7455         toX = x;
7456         toY = y;
7457     }
7458
7459     saveAnimate = appData.animate;
7460     if (clickType == Press) {
7461         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7462             // must be Edit Position mode with empty-square selected
7463             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7464             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7465             return;
7466         }
7467         if(dragging == 2) {  // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job
7468             return;
7469         }
7470         if(x == killX && y == killY) {              // second click on this square, which was selected as first-leg target
7471             killX = killY = -1;                     // this informs us no second leg is coming, so treat as to-click without intermediate
7472         } else
7473         if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
7474         if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7475           if(appData.sweepSelect) {
7476             ChessSquare piece = boards[currentMove][fromY][fromX];
7477             promoSweep = defaultPromoChoice;
7478             if(PieceToChar(CHUPROMOTED piece) == '+') promoSweep = CHUPROMOTED piece;
7479             selectFlag = 0; lastX = xPix; lastY = yPix;
7480             Sweep(0); // Pawn that is going to promote: preview promotion piece
7481             sweepSelecting = 1;
7482             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7483             MarkTargetSquares(1);
7484           }
7485           return; // promo popup appears on up-click
7486         }
7487         /* Finish clickclick move */
7488         if (appData.animate || appData.highlightLastMove) {
7489             SetHighlights(fromX, fromY, toX, toY);
7490         } else {
7491             ClearHighlights();
7492         }
7493     } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
7494         sweepSelecting = 0;
7495         if (appData.animate || appData.highlightLastMove) {
7496             SetHighlights(fromX, fromY, toX, toY);
7497         } else {
7498             ClearHighlights();
7499         }
7500     } else {
7501 #if 0
7502 // [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
7503         /* Finish drag move */
7504         if (appData.highlightLastMove) {
7505             SetHighlights(fromX, fromY, toX, toY);
7506         } else {
7507             ClearHighlights();
7508         }
7509 #endif
7510         if(marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
7511           dragging *= 2;            // flag button-less dragging if we are dragging
7512           MarkTargetSquares(1);
7513           if(x == killX && y == killY) killX = killY = -1; else {
7514             killX = x; killY = y;     //remeber this square as intermediate
7515             ReportClick("put", x, y); // and inform engine
7516             ReportClick("lift", x, y);
7517             MarkTargetSquares(0);
7518             return;
7519           }
7520         }
7521         DragPieceEnd(xPix, yPix); dragging = 0;
7522         /* Don't animate move and drag both */
7523         appData.animate = FALSE;
7524     }
7525
7526     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7527     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7528         ChessSquare piece = boards[currentMove][fromY][fromX];
7529         if(gameMode == EditPosition && piece != EmptySquare &&
7530            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7531             int n;
7532
7533             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7534                 n = PieceToNumber(piece - (int)BlackPawn);
7535                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7536                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7537                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7538             } else
7539             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7540                 n = PieceToNumber(piece);
7541                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7542                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7543                 boards[currentMove][n][BOARD_WIDTH-2]++;
7544             }
7545             boards[currentMove][fromY][fromX] = EmptySquare;
7546         }
7547         ClearHighlights();
7548         fromX = fromY = -1;
7549         MarkTargetSquares(1);
7550         DrawPosition(TRUE, boards[currentMove]);
7551         return;
7552     }
7553
7554     // off-board moves should not be highlighted
7555     if(x < 0 || y < 0) ClearHighlights();
7556     else ReportClick("put", x, y);
7557
7558     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7559
7560     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7561         SetHighlights(fromX, fromY, toX, toY);
7562         MarkTargetSquares(1);
7563         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7564             // [HGM] super: promotion to captured piece selected from holdings
7565             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7566             promotionChoice = TRUE;
7567             // kludge follows to temporarily execute move on display, without promoting yet
7568             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7569             boards[currentMove][toY][toX] = p;
7570             DrawPosition(FALSE, boards[currentMove]);
7571             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7572             boards[currentMove][toY][toX] = q;
7573             DisplayMessage("Click in holdings to choose piece", "");
7574             return;
7575         }
7576         PromotionPopUp();
7577     } else {
7578         int oldMove = currentMove;
7579         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7580         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7581         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7582         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7583            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7584             DrawPosition(TRUE, boards[currentMove]);
7585         MarkTargetSquares(1);
7586         fromX = fromY = -1;
7587     }
7588     appData.animate = saveAnimate;
7589     if (appData.animate || appData.animateDragging) {
7590         /* Undo animation damage if needed */
7591         DrawPosition(FALSE, NULL);
7592     }
7593 }
7594
7595 int
7596 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7597 {   // front-end-free part taken out of PieceMenuPopup
7598     int whichMenu; int xSqr, ySqr;
7599
7600     if(seekGraphUp) { // [HGM] seekgraph
7601         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7602         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7603         return -2;
7604     }
7605
7606     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7607          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7608         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7609         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7610         if(action == Press)   {
7611             originalFlip = flipView;
7612             flipView = !flipView; // temporarily flip board to see game from partners perspective
7613             DrawPosition(TRUE, partnerBoard);
7614             DisplayMessage(partnerStatus, "");
7615             partnerUp = TRUE;
7616         } else if(action == Release) {
7617             flipView = originalFlip;
7618             DrawPosition(TRUE, boards[currentMove]);
7619             partnerUp = FALSE;
7620         }
7621         return -2;
7622     }
7623
7624     xSqr = EventToSquare(x, BOARD_WIDTH);
7625     ySqr = EventToSquare(y, BOARD_HEIGHT);
7626     if (action == Release) {
7627         if(pieceSweep != EmptySquare) {
7628             EditPositionMenuEvent(pieceSweep, toX, toY);
7629             pieceSweep = EmptySquare;
7630         } else UnLoadPV(); // [HGM] pv
7631     }
7632     if (action != Press) return -2; // return code to be ignored
7633     switch (gameMode) {
7634       case IcsExamining:
7635         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7636       case EditPosition:
7637         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7638         if (xSqr < 0 || ySqr < 0) return -1;
7639         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7640         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7641         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7642         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7643         NextPiece(0);
7644         return 2; // grab
7645       case IcsObserving:
7646         if(!appData.icsEngineAnalyze) return -1;
7647       case IcsPlayingWhite:
7648       case IcsPlayingBlack:
7649         if(!appData.zippyPlay) goto noZip;
7650       case AnalyzeMode:
7651       case AnalyzeFile:
7652       case MachinePlaysWhite:
7653       case MachinePlaysBlack:
7654       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7655         if (!appData.dropMenu) {
7656           LoadPV(x, y);
7657           return 2; // flag front-end to grab mouse events
7658         }
7659         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7660            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7661       case EditGame:
7662       noZip:
7663         if (xSqr < 0 || ySqr < 0) return -1;
7664         if (!appData.dropMenu || appData.testLegality &&
7665             gameInfo.variant != VariantBughouse &&
7666             gameInfo.variant != VariantCrazyhouse) return -1;
7667         whichMenu = 1; // drop menu
7668         break;
7669       default:
7670         return -1;
7671     }
7672
7673     if (((*fromX = xSqr) < 0) ||
7674         ((*fromY = ySqr) < 0)) {
7675         *fromX = *fromY = -1;
7676         return -1;
7677     }
7678     if (flipView)
7679       *fromX = BOARD_WIDTH - 1 - *fromX;
7680     else
7681       *fromY = BOARD_HEIGHT - 1 - *fromY;
7682
7683     return whichMenu;
7684 }
7685
7686 void
7687 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7688 {
7689 //    char * hint = lastHint;
7690     FrontEndProgramStats stats;
7691
7692     stats.which = cps == &first ? 0 : 1;
7693     stats.depth = cpstats->depth;
7694     stats.nodes = cpstats->nodes;
7695     stats.score = cpstats->score;
7696     stats.time = cpstats->time;
7697     stats.pv = cpstats->movelist;
7698     stats.hint = lastHint;
7699     stats.an_move_index = 0;
7700     stats.an_move_count = 0;
7701
7702     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7703         stats.hint = cpstats->move_name;
7704         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7705         stats.an_move_count = cpstats->nr_moves;
7706     }
7707
7708     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
7709
7710     SetProgramStats( &stats );
7711 }
7712
7713 void
7714 ClearEngineOutputPane (int which)
7715 {
7716     static FrontEndProgramStats dummyStats;
7717     dummyStats.which = which;
7718     dummyStats.pv = "#";
7719     SetProgramStats( &dummyStats );
7720 }
7721
7722 #define MAXPLAYERS 500
7723
7724 char *
7725 TourneyStandings (int display)
7726 {
7727     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7728     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7729     char result, *p, *names[MAXPLAYERS];
7730
7731     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7732         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7733     names[0] = p = strdup(appData.participants);
7734     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7735
7736     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7737
7738     while(result = appData.results[nr]) {
7739         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7740         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7741         wScore = bScore = 0;
7742         switch(result) {
7743           case '+': wScore = 2; break;
7744           case '-': bScore = 2; break;
7745           case '=': wScore = bScore = 1; break;
7746           case ' ':
7747           case '*': return strdup("busy"); // tourney not finished
7748         }
7749         score[w] += wScore;
7750         score[b] += bScore;
7751         games[w]++;
7752         games[b]++;
7753         nr++;
7754     }
7755     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7756     for(w=0; w<nPlayers; w++) {
7757         bScore = -1;
7758         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7759         ranking[w] = b; points[w] = bScore; score[b] = -2;
7760     }
7761     p = malloc(nPlayers*34+1);
7762     for(w=0; w<nPlayers && w<display; w++)
7763         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7764     free(names[0]);
7765     return p;
7766 }
7767
7768 void
7769 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7770 {       // count all piece types
7771         int p, f, r;
7772         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7773         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7774         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7775                 p = board[r][f];
7776                 pCnt[p]++;
7777                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7778                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7779                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7780                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7781                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7782                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7783         }
7784 }
7785
7786 int
7787 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7788 {
7789         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7790         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7791
7792         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7793         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7794         if(myPawns == 2 && nMine == 3) // KPP
7795             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7796         if(myPawns == 1 && nMine == 2) // KP
7797             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7798         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7799             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7800         if(myPawns) return FALSE;
7801         if(pCnt[WhiteRook+side])
7802             return pCnt[BlackRook-side] ||
7803                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7804                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7805                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7806         if(pCnt[WhiteCannon+side]) {
7807             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7808             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7809         }
7810         if(pCnt[WhiteKnight+side])
7811             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7812         return FALSE;
7813 }
7814
7815 int
7816 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7817 {
7818         VariantClass v = gameInfo.variant;
7819
7820         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7821         if(v == VariantShatranj) return TRUE; // always winnable through baring
7822         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7823         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7824
7825         if(v == VariantXiangqi) {
7826                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7827
7828                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7829                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7830                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7831                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7832                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7833                 if(stale) // we have at least one last-rank P plus perhaps C
7834                     return majors // KPKX
7835                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7836                 else // KCA*E*
7837                     return pCnt[WhiteFerz+side] // KCAK
7838                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7839                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7840                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7841
7842         } else if(v == VariantKnightmate) {
7843                 if(nMine == 1) return FALSE;
7844                 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
7845         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7846                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7847
7848                 if(nMine == 1) return FALSE; // bare King
7849                 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
7850                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7851                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7852                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7853                 if(pCnt[WhiteKnight+side])
7854                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7855                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7856                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7857                 if(nBishops)
7858                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7859                 if(pCnt[WhiteAlfil+side])
7860                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7861                 if(pCnt[WhiteWazir+side])
7862                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7863         }
7864
7865         return TRUE;
7866 }
7867
7868 int
7869 CompareWithRights (Board b1, Board b2)
7870 {
7871     int rights = 0;
7872     if(!CompareBoards(b1, b2)) return FALSE;
7873     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7874     /* compare castling rights */
7875     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7876            rights++; /* King lost rights, while rook still had them */
7877     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7878         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7879            rights++; /* but at least one rook lost them */
7880     }
7881     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7882            rights++;
7883     if( b1[CASTLING][5] != NoRights ) {
7884         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7885            rights++;
7886     }
7887     return rights == 0;
7888 }
7889
7890 int
7891 Adjudicate (ChessProgramState *cps)
7892 {       // [HGM] some adjudications useful with buggy engines
7893         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7894         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7895         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7896         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7897         int k, drop, count = 0; static int bare = 1;
7898         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7899         Boolean canAdjudicate = !appData.icsActive;
7900
7901         // most tests only when we understand the game, i.e. legality-checking on
7902             if( appData.testLegality )
7903             {   /* [HGM] Some more adjudications for obstinate engines */
7904                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7905                 static int moveCount = 6;
7906                 ChessMove result;
7907                 char *reason = NULL;
7908
7909                 /* Count what is on board. */
7910                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7911
7912                 /* Some material-based adjudications that have to be made before stalemate test */
7913                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7914                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7915                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7916                      if(canAdjudicate && appData.checkMates) {
7917                          if(engineOpponent)
7918                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7919                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7920                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7921                          return 1;
7922                      }
7923                 }
7924
7925                 /* Bare King in Shatranj (loses) or Losers (wins) */
7926                 if( nrW == 1 || nrB == 1) {
7927                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7928                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7929                      if(canAdjudicate && appData.checkMates) {
7930                          if(engineOpponent)
7931                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7932                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7933                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7934                          return 1;
7935                      }
7936                   } else
7937                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7938                   {    /* bare King */
7939                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7940                         if(canAdjudicate && appData.checkMates) {
7941                             /* but only adjudicate if adjudication enabled */
7942                             if(engineOpponent)
7943                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7944                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7945                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7946                             return 1;
7947                         }
7948                   }
7949                 } else bare = 1;
7950
7951
7952             // don't wait for engine to announce game end if we can judge ourselves
7953             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7954               case MT_CHECK:
7955                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7956                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7957                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7958                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7959                             checkCnt++;
7960                         if(checkCnt >= 2) {
7961                             reason = "Xboard adjudication: 3rd check";
7962                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7963                             break;
7964                         }
7965                     }
7966                 }
7967               case MT_NONE:
7968               default:
7969                 break;
7970               case MT_STALEMATE:
7971               case MT_STAINMATE:
7972                 reason = "Xboard adjudication: Stalemate";
7973                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7974                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7975                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7976                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7977                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7978                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7979                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7980                                                                         EP_CHECKMATE : EP_WINS);
7981                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
7982                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7983                 }
7984                 break;
7985               case MT_CHECKMATE:
7986                 reason = "Xboard adjudication: Checkmate";
7987                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7988                 if(gameInfo.variant == VariantShogi) {
7989                     if(forwardMostMove > backwardMostMove
7990                        && moveList[forwardMostMove-1][1] == '@'
7991                        && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
7992                         reason = "XBoard adjudication: pawn-drop mate";
7993                         boards[forwardMostMove][EP_STATUS] = EP_WINS;
7994                     }
7995                 }
7996                 break;
7997             }
7998
7999                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
8000                     case EP_STALEMATE:
8001                         result = GameIsDrawn; break;
8002                     case EP_CHECKMATE:
8003                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
8004                     case EP_WINS:
8005                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
8006                     default:
8007                         result = EndOfFile;
8008                 }
8009                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
8010                     if(engineOpponent)
8011                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8012                     GameEnds( result, reason, GE_XBOARD );
8013                     return 1;
8014                 }
8015
8016                 /* Next absolutely insufficient mating material. */
8017                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
8018                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
8019                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
8020
8021                      /* always flag draws, for judging claims */
8022                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
8023
8024                      if(canAdjudicate && appData.materialDraws) {
8025                          /* but only adjudicate them if adjudication enabled */
8026                          if(engineOpponent) {
8027                            SendToProgram("force\n", engineOpponent); // suppress reply
8028                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
8029                          }
8030                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
8031                          return 1;
8032                      }
8033                 }
8034
8035                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
8036                 if(gameInfo.variant == VariantXiangqi ?
8037                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
8038                  : nrW + nrB == 4 &&
8039                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
8040                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
8041                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
8042                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
8043                    ) ) {
8044                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
8045                      {    /* if the first 3 moves do not show a tactical win, declare draw */
8046                           if(engineOpponent) {
8047                             SendToProgram("force\n", engineOpponent); // suppress reply
8048                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8049                           }
8050                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
8051                           return 1;
8052                      }
8053                 } else moveCount = 6;
8054             }
8055
8056         // Repetition draws and 50-move rule can be applied independently of legality testing
8057
8058                 /* Check for rep-draws */
8059                 count = 0;
8060                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
8061                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
8062                 for(k = forwardMostMove-2;
8063                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
8064                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
8065                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
8066                     k-=2)
8067                 {   int rights=0;
8068                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
8069                         /* compare castling rights */
8070                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8071                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8072                                 rights++; /* King lost rights, while rook still had them */
8073                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8074                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8075                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8076                                    rights++; /* but at least one rook lost them */
8077                         }
8078                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8079                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8080                                 rights++;
8081                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8082                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8083                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8084                                    rights++;
8085                         }
8086                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8087                             && appData.drawRepeats > 1) {
8088                              /* adjudicate after user-specified nr of repeats */
8089                              int result = GameIsDrawn;
8090                              char *details = "XBoard adjudication: repetition draw";
8091                              if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8092                                 // [HGM] xiangqi: check for forbidden perpetuals
8093                                 int m, ourPerpetual = 1, hisPerpetual = 1;
8094                                 for(m=forwardMostMove; m>k; m-=2) {
8095                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8096                                         ourPerpetual = 0; // the current mover did not always check
8097                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8098                                         hisPerpetual = 0; // the opponent did not always check
8099                                 }
8100                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8101                                                                         ourPerpetual, hisPerpetual);
8102                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8103                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8104                                     details = "Xboard adjudication: perpetual checking";
8105                                 } else
8106                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8107                                     break; // (or we would have caught him before). Abort repetition-checking loop.
8108                                 } else
8109                                 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8110                                     if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8111                                         result = BlackWins;
8112                                         details = "Xboard adjudication: repetition";
8113                                     }
8114                                 } else // it must be XQ
8115                                 // Now check for perpetual chases
8116                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8117                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
8118                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8119                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8120                                         static char resdet[MSG_SIZ];
8121                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8122                                         details = resdet;
8123                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8124                                     } else
8125                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
8126                                         break; // Abort repetition-checking loop.
8127                                 }
8128                                 // if neither of us is checking or chasing all the time, or both are, it is draw
8129                              }
8130                              if(engineOpponent) {
8131                                SendToProgram("force\n", engineOpponent); // suppress reply
8132                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8133                              }
8134                              GameEnds( result, details, GE_XBOARD );
8135                              return 1;
8136                         }
8137                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8138                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8139                     }
8140                 }
8141
8142                 /* Now we test for 50-move draws. Determine ply count */
8143                 count = forwardMostMove;
8144                 /* look for last irreversble move */
8145                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8146                     count--;
8147                 /* if we hit starting position, add initial plies */
8148                 if( count == backwardMostMove )
8149                     count -= initialRulePlies;
8150                 count = forwardMostMove - count;
8151                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8152                         // adjust reversible move counter for checks in Xiangqi
8153                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
8154                         if(i < backwardMostMove) i = backwardMostMove;
8155                         while(i <= forwardMostMove) {
8156                                 lastCheck = inCheck; // check evasion does not count
8157                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8158                                 if(inCheck || lastCheck) count--; // check does not count
8159                                 i++;
8160                         }
8161                 }
8162                 if( count >= 100)
8163                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8164                          /* this is used to judge if draw claims are legal */
8165                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8166                          if(engineOpponent) {
8167                            SendToProgram("force\n", engineOpponent); // suppress reply
8168                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8169                          }
8170                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8171                          return 1;
8172                 }
8173
8174                 /* if draw offer is pending, treat it as a draw claim
8175                  * when draw condition present, to allow engines a way to
8176                  * claim draws before making their move to avoid a race
8177                  * condition occurring after their move
8178                  */
8179                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8180                          char *p = NULL;
8181                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8182                              p = "Draw claim: 50-move rule";
8183                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8184                              p = "Draw claim: 3-fold repetition";
8185                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8186                              p = "Draw claim: insufficient mating material";
8187                          if( p != NULL && canAdjudicate) {
8188                              if(engineOpponent) {
8189                                SendToProgram("force\n", engineOpponent); // suppress reply
8190                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8191                              }
8192                              GameEnds( GameIsDrawn, p, GE_XBOARD );
8193                              return 1;
8194                          }
8195                 }
8196
8197                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8198                     if(engineOpponent) {
8199                       SendToProgram("force\n", engineOpponent); // suppress reply
8200                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8201                     }
8202                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8203                     return 1;
8204                 }
8205         return 0;
8206 }
8207
8208 char *
8209 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8210 {   // [HGM] book: this routine intercepts moves to simulate book replies
8211     char *bookHit = NULL;
8212
8213     //first determine if the incoming move brings opponent into his book
8214     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8215         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8216     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8217     if(bookHit != NULL && !cps->bookSuspend) {
8218         // make sure opponent is not going to reply after receiving move to book position
8219         SendToProgram("force\n", cps);
8220         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8221     }
8222     if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8223     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8224     // now arrange restart after book miss
8225     if(bookHit) {
8226         // after a book hit we never send 'go', and the code after the call to this routine
8227         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8228         char buf[MSG_SIZ], *move = bookHit;
8229         if(cps->useSAN) {
8230             int fromX, fromY, toX, toY;
8231             char promoChar;
8232             ChessMove moveType;
8233             move = buf + 30;
8234             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8235                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8236                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8237                                     PosFlags(forwardMostMove),
8238                                     fromY, fromX, toY, toX, promoChar, move);
8239             } else {
8240                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8241                 bookHit = NULL;
8242             }
8243         }
8244         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8245         SendToProgram(buf, cps);
8246         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8247     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8248         SendToProgram("go\n", cps);
8249         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8250     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8251         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8252             SendToProgram("go\n", cps);
8253         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8254     }
8255     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8256 }
8257
8258 int
8259 LoadError (char *errmess, ChessProgramState *cps)
8260 {   // unloads engine and switches back to -ncp mode if it was first
8261     if(cps->initDone) return FALSE;
8262     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8263     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8264     cps->pr = NoProc;
8265     if(cps == &first) {
8266         appData.noChessProgram = TRUE;
8267         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8268         gameMode = BeginningOfGame; ModeHighlight();
8269         SetNCPMode();
8270     }
8271     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8272     DisplayMessage("", ""); // erase waiting message
8273     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8274     return TRUE;
8275 }
8276
8277 char *savedMessage;
8278 ChessProgramState *savedState;
8279 void
8280 DeferredBookMove (void)
8281 {
8282         if(savedState->lastPing != savedState->lastPong)
8283                     ScheduleDelayedEvent(DeferredBookMove, 10);
8284         else
8285         HandleMachineMove(savedMessage, savedState);
8286 }
8287
8288 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8289 static ChessProgramState *stalledEngine;
8290 static char stashedInputMove[MSG_SIZ];
8291
8292 void
8293 HandleMachineMove (char *message, ChessProgramState *cps)
8294 {
8295     static char firstLeg[20];
8296     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8297     char realname[MSG_SIZ];
8298     int fromX, fromY, toX, toY;
8299     ChessMove moveType;
8300     char promoChar, roar;
8301     char *p, *pv=buf1;
8302     int machineWhite, oldError;
8303     char *bookHit;
8304
8305     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8306         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8307         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8308             DisplayError(_("Invalid pairing from pairing engine"), 0);
8309             return;
8310         }
8311         pairingReceived = 1;
8312         NextMatchGame();
8313         return; // Skim the pairing messages here.
8314     }
8315
8316     oldError = cps->userError; cps->userError = 0;
8317
8318 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8319     /*
8320      * Kludge to ignore BEL characters
8321      */
8322     while (*message == '\007') message++;
8323
8324     /*
8325      * [HGM] engine debug message: ignore lines starting with '#' character
8326      */
8327     if(cps->debug && *message == '#') return;
8328
8329     /*
8330      * Look for book output
8331      */
8332     if (cps == &first && bookRequested) {
8333         if (message[0] == '\t' || message[0] == ' ') {
8334             /* Part of the book output is here; append it */
8335             strcat(bookOutput, message);
8336             strcat(bookOutput, "  \n");
8337             return;
8338         } else if (bookOutput[0] != NULLCHAR) {
8339             /* All of book output has arrived; display it */
8340             char *p = bookOutput;
8341             while (*p != NULLCHAR) {
8342                 if (*p == '\t') *p = ' ';
8343                 p++;
8344             }
8345             DisplayInformation(bookOutput);
8346             bookRequested = FALSE;
8347             /* Fall through to parse the current output */
8348         }
8349     }
8350
8351     /*
8352      * Look for machine move.
8353      */
8354     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8355         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8356     {
8357         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8358             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8359             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8360             stalledEngine = cps;
8361             if(appData.ponderNextMove) { // bring opponent out of ponder
8362                 if(gameMode == TwoMachinesPlay) {
8363                     if(cps->other->pause)
8364                         PauseEngine(cps->other);
8365                     else
8366                         SendToProgram("easy\n", cps->other);
8367                 }
8368             }
8369             StopClocks();
8370             return;
8371         }
8372
8373         /* This method is only useful on engines that support ping */
8374         if (cps->lastPing != cps->lastPong) {
8375           if (gameMode == BeginningOfGame) {
8376             /* Extra move from before last new; ignore */
8377             if (appData.debugMode) {
8378                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8379             }
8380           } else {
8381             if (appData.debugMode) {
8382                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8383                         cps->which, gameMode);
8384             }
8385
8386             SendToProgram("undo\n", cps);
8387           }
8388           return;
8389         }
8390
8391         switch (gameMode) {
8392           case BeginningOfGame:
8393             /* Extra move from before last reset; ignore */
8394             if (appData.debugMode) {
8395                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8396             }
8397             return;
8398
8399           case EndOfGame:
8400           case IcsIdle:
8401           default:
8402             /* Extra move after we tried to stop.  The mode test is
8403                not a reliable way of detecting this problem, but it's
8404                the best we can do on engines that don't support ping.
8405             */
8406             if (appData.debugMode) {
8407                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8408                         cps->which, gameMode);
8409             }
8410             SendToProgram("undo\n", cps);
8411             return;
8412
8413           case MachinePlaysWhite:
8414           case IcsPlayingWhite:
8415             machineWhite = TRUE;
8416             break;
8417
8418           case MachinePlaysBlack:
8419           case IcsPlayingBlack:
8420             machineWhite = FALSE;
8421             break;
8422
8423           case TwoMachinesPlay:
8424             machineWhite = (cps->twoMachinesColor[0] == 'w');
8425             break;
8426         }
8427         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8428             if (appData.debugMode) {
8429                 fprintf(debugFP,
8430                         "Ignoring move out of turn by %s, gameMode %d"
8431                         ", forwardMost %d\n",
8432                         cps->which, gameMode, forwardMostMove);
8433             }
8434             return;
8435         }
8436
8437         if(cps->alphaRank) AlphaRank(machineMove, 4);
8438
8439         // [HGM] lion: (some very limited) support for Alien protocol
8440         killX = killY = -1;
8441         if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
8442             safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
8443             return;
8444         } else if(firstLeg[0]) { // there was a previous leg;
8445             // only support case where same piece makes two step (and don't even test that!)
8446             char buf[20], *p = machineMove+1, *q = buf+1, f;
8447             safeStrCpy(buf, machineMove, 20);
8448             while(isdigit(*q)) q++; // find start of to-square
8449             safeStrCpy(machineMove, firstLeg, 20);
8450             while(isdigit(*p)) p++;
8451             safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
8452             sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
8453             firstLeg[0] = NULLCHAR;
8454         }
8455
8456         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8457                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8458             /* Machine move could not be parsed; ignore it. */
8459           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8460                     machineMove, _(cps->which));
8461             DisplayMoveError(buf1);
8462             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c via %c%c) res=%d",
8463                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, moveType);
8464             if (gameMode == TwoMachinesPlay) {
8465               GameEnds(machineWhite ? BlackWins : WhiteWins,
8466                        buf1, GE_XBOARD);
8467             }
8468             return;
8469         }
8470
8471         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8472         /* So we have to redo legality test with true e.p. status here,  */
8473         /* to make sure an illegal e.p. capture does not slip through,   */
8474         /* to cause a forfeit on a justified illegal-move complaint      */
8475         /* of the opponent.                                              */
8476         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8477            ChessMove moveType;
8478            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8479                              fromY, fromX, toY, toX, promoChar);
8480             if(moveType == IllegalMove) {
8481               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8482                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8483                 GameEnds(machineWhite ? BlackWins : WhiteWins,
8484                            buf1, GE_XBOARD);
8485                 return;
8486            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
8487            /* [HGM] Kludge to handle engines that send FRC-style castling
8488               when they shouldn't (like TSCP-Gothic) */
8489            switch(moveType) {
8490              case WhiteASideCastleFR:
8491              case BlackASideCastleFR:
8492                toX+=2;
8493                currentMoveString[2]++;
8494                break;
8495              case WhiteHSideCastleFR:
8496              case BlackHSideCastleFR:
8497                toX--;
8498                currentMoveString[2]--;
8499                break;
8500              default: ; // nothing to do, but suppresses warning of pedantic compilers
8501            }
8502         }
8503         hintRequested = FALSE;
8504         lastHint[0] = NULLCHAR;
8505         bookRequested = FALSE;
8506         /* Program may be pondering now */
8507         cps->maybeThinking = TRUE;
8508         if (cps->sendTime == 2) cps->sendTime = 1;
8509         if (cps->offeredDraw) cps->offeredDraw--;
8510
8511         /* [AS] Save move info*/
8512         pvInfoList[ forwardMostMove ].score = programStats.score;
8513         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8514         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8515
8516         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8517
8518         /* Test suites abort the 'game' after one move */
8519         if(*appData.finger) {
8520            static FILE *f;
8521            char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8522            if(!f) f = fopen(appData.finger, "w");
8523            if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8524            else { DisplayFatalError("Bad output file", errno, 0); return; }
8525            free(fen);
8526            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8527         }
8528
8529         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8530         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8531             int count = 0;
8532
8533             while( count < adjudicateLossPlies ) {
8534                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8535
8536                 if( count & 1 ) {
8537                     score = -score; /* Flip score for winning side */
8538                 }
8539
8540                 if( score > adjudicateLossThreshold ) {
8541                     break;
8542                 }
8543
8544                 count++;
8545             }
8546
8547             if( count >= adjudicateLossPlies ) {
8548                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8549
8550                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8551                     "Xboard adjudication",
8552                     GE_XBOARD );
8553
8554                 return;
8555             }
8556         }
8557
8558         if(Adjudicate(cps)) {
8559             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8560             return; // [HGM] adjudicate: for all automatic game ends
8561         }
8562
8563 #if ZIPPY
8564         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8565             first.initDone) {
8566           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8567                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8568                 SendToICS("draw ");
8569                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8570           }
8571           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8572           ics_user_moved = 1;
8573           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8574                 char buf[3*MSG_SIZ];
8575
8576                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8577                         programStats.score / 100.,
8578                         programStats.depth,
8579                         programStats.time / 100.,
8580                         (unsigned int)programStats.nodes,
8581                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8582                         programStats.movelist);
8583                 SendToICS(buf);
8584 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8585           }
8586         }
8587 #endif
8588
8589         /* [AS] Clear stats for next move */
8590         ClearProgramStats();
8591         thinkOutput[0] = NULLCHAR;
8592         hiddenThinkOutputState = 0;
8593
8594         bookHit = NULL;
8595         if (gameMode == TwoMachinesPlay) {
8596             /* [HGM] relaying draw offers moved to after reception of move */
8597             /* and interpreting offer as claim if it brings draw condition */
8598             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8599                 SendToProgram("draw\n", cps->other);
8600             }
8601             if (cps->other->sendTime) {
8602                 SendTimeRemaining(cps->other,
8603                                   cps->other->twoMachinesColor[0] == 'w');
8604             }
8605             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8606             if (firstMove && !bookHit) {
8607                 firstMove = FALSE;
8608                 if (cps->other->useColors) {
8609                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8610                 }
8611                 SendToProgram("go\n", cps->other);
8612             }
8613             cps->other->maybeThinking = TRUE;
8614         }
8615
8616         roar = (killX >= 0 && IS_LION(boards[forwardMostMove][toY][toX]));
8617
8618         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8619
8620         if (!pausing && appData.ringBellAfterMoves) {
8621             if(!roar) RingBell();
8622         }
8623
8624         /*
8625          * Reenable menu items that were disabled while
8626          * machine was thinking
8627          */
8628         if (gameMode != TwoMachinesPlay)
8629             SetUserThinkingEnables();
8630
8631         // [HGM] book: after book hit opponent has received move and is now in force mode
8632         // force the book reply into it, and then fake that it outputted this move by jumping
8633         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8634         if(bookHit) {
8635                 static char bookMove[MSG_SIZ]; // a bit generous?
8636
8637                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8638                 strcat(bookMove, bookHit);
8639                 message = bookMove;
8640                 cps = cps->other;
8641                 programStats.nodes = programStats.depth = programStats.time =
8642                 programStats.score = programStats.got_only_move = 0;
8643                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8644
8645                 if(cps->lastPing != cps->lastPong) {
8646                     savedMessage = message; // args for deferred call
8647                     savedState = cps;
8648                     ScheduleDelayedEvent(DeferredBookMove, 10);
8649                     return;
8650                 }
8651                 goto FakeBookMove;
8652         }
8653
8654         return;
8655     }
8656
8657     /* Set special modes for chess engines.  Later something general
8658      *  could be added here; for now there is just one kludge feature,
8659      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8660      *  when "xboard" is given as an interactive command.
8661      */
8662     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8663         cps->useSigint = FALSE;
8664         cps->useSigterm = FALSE;
8665     }
8666     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8667       ParseFeatures(message+8, cps);
8668       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8669     }
8670
8671     if (!strncmp(message, "setup ", 6) && 
8672         (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown ||
8673           NonStandardBoardSize(gameInfo.variant, gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize))
8674                                         ) { // [HGM] allow first engine to define opening position
8675       int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
8676       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8677       *buf = NULLCHAR;
8678       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8679       if(startedFromSetupPosition) return;
8680       dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
8681       if(dummy >= 3) {
8682         while(message[s] && message[s++] != ' ');
8683         if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
8684            dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
8685             appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
8686             if(dummy == 4) gameInfo.variant = StringToVariant(varName);     // parent variant
8687           InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
8688           if(*buf) SetCharTable(pieceToChar, buf); // do again, for it was spoiled by InitPosition
8689         }
8690       }
8691       ParseFEN(boards[0], &dummy, message+s, FALSE);
8692       DrawPosition(TRUE, boards[0]);
8693       startedFromSetupPosition = TRUE;
8694       return;
8695     }
8696     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8697      * want this, I was asked to put it in, and obliged.
8698      */
8699     if (!strncmp(message, "setboard ", 9)) {
8700         Board initial_position;
8701
8702         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8703
8704         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
8705             DisplayError(_("Bad FEN received from engine"), 0);
8706             return ;
8707         } else {
8708            Reset(TRUE, FALSE);
8709            CopyBoard(boards[0], initial_position);
8710            initialRulePlies = FENrulePlies;
8711            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8712            else gameMode = MachinePlaysBlack;
8713            DrawPosition(FALSE, boards[currentMove]);
8714         }
8715         return;
8716     }
8717
8718     /*
8719      * Look for communication commands
8720      */
8721     if (!strncmp(message, "telluser ", 9)) {
8722         if(message[9] == '\\' && message[10] == '\\')
8723             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8724         PlayTellSound();
8725         DisplayNote(message + 9);
8726         return;
8727     }
8728     if (!strncmp(message, "tellusererror ", 14)) {
8729         cps->userError = 1;
8730         if(message[14] == '\\' && message[15] == '\\')
8731             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8732         PlayTellSound();
8733         DisplayError(message + 14, 0);
8734         return;
8735     }
8736     if (!strncmp(message, "tellopponent ", 13)) {
8737       if (appData.icsActive) {
8738         if (loggedOn) {
8739           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8740           SendToICS(buf1);
8741         }
8742       } else {
8743         DisplayNote(message + 13);
8744       }
8745       return;
8746     }
8747     if (!strncmp(message, "tellothers ", 11)) {
8748       if (appData.icsActive) {
8749         if (loggedOn) {
8750           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8751           SendToICS(buf1);
8752         }
8753       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
8754       return;
8755     }
8756     if (!strncmp(message, "tellall ", 8)) {
8757       if (appData.icsActive) {
8758         if (loggedOn) {
8759           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8760           SendToICS(buf1);
8761         }
8762       } else {
8763         DisplayNote(message + 8);
8764       }
8765       return;
8766     }
8767     if (strncmp(message, "warning", 7) == 0) {
8768         /* Undocumented feature, use tellusererror in new code */
8769         DisplayError(message, 0);
8770         return;
8771     }
8772     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8773         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8774         strcat(realname, " query");
8775         AskQuestion(realname, buf2, buf1, cps->pr);
8776         return;
8777     }
8778     /* Commands from the engine directly to ICS.  We don't allow these to be
8779      *  sent until we are logged on. Crafty kibitzes have been known to
8780      *  interfere with the login process.
8781      */
8782     if (loggedOn) {
8783         if (!strncmp(message, "tellics ", 8)) {
8784             SendToICS(message + 8);
8785             SendToICS("\n");
8786             return;
8787         }
8788         if (!strncmp(message, "tellicsnoalias ", 15)) {
8789             SendToICS(ics_prefix);
8790             SendToICS(message + 15);
8791             SendToICS("\n");
8792             return;
8793         }
8794         /* The following are for backward compatibility only */
8795         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8796             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8797             SendToICS(ics_prefix);
8798             SendToICS(message);
8799             SendToICS("\n");
8800             return;
8801         }
8802     }
8803     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8804         if(initPing == cps->lastPong) {
8805             if(gameInfo.variant == VariantUnknown) {
8806                 DisplayError(_("Engine did not send setup for non-standard variant"), 0);
8807                 *engineVariant = NULLCHAR; appData.variant = VariantNormal; // back to normal as error recovery?
8808                 GameEnds(GameUnfinished, NULL, GE_XBOARD);
8809             }
8810             initPing = -1;
8811         }
8812         return;
8813     }
8814     if(!strncmp(message, "highlight ", 10)) {
8815         if(appData.testLegality && appData.markers) return;
8816         MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
8817         return;
8818     }
8819     if(!strncmp(message, "click ", 6)) {
8820         char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
8821         if(appData.testLegality || !appData.oneClick) return;
8822         sscanf(message+6, "%c%d%c", &f, &y, &c);
8823         x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
8824         if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
8825         x = x*squareSize + (x+1)*lineGap + squareSize/2;
8826         y = y*squareSize + (y+1)*lineGap + squareSize/2;
8827         f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
8828         if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
8829             LeftClick(Release, lastLeftX, lastLeftY);
8830         controlKey  = (c == ',');
8831         LeftClick(Press, x, y);
8832         LeftClick(Release, x, y);
8833         first.highlight = f;
8834         return;
8835     }
8836     /*
8837      * If the move is illegal, cancel it and redraw the board.
8838      * Also deal with other error cases.  Matching is rather loose
8839      * here to accommodate engines written before the spec.
8840      */
8841     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8842         strncmp(message, "Error", 5) == 0) {
8843         if (StrStr(message, "name") ||
8844             StrStr(message, "rating") || StrStr(message, "?") ||
8845             StrStr(message, "result") || StrStr(message, "board") ||
8846             StrStr(message, "bk") || StrStr(message, "computer") ||
8847             StrStr(message, "variant") || StrStr(message, "hint") ||
8848             StrStr(message, "random") || StrStr(message, "depth") ||
8849             StrStr(message, "accepted")) {
8850             return;
8851         }
8852         if (StrStr(message, "protover")) {
8853           /* Program is responding to input, so it's apparently done
8854              initializing, and this error message indicates it is
8855              protocol version 1.  So we don't need to wait any longer
8856              for it to initialize and send feature commands. */
8857           FeatureDone(cps, 1);
8858           cps->protocolVersion = 1;
8859           return;
8860         }
8861         cps->maybeThinking = FALSE;
8862
8863         if (StrStr(message, "draw")) {
8864             /* Program doesn't have "draw" command */
8865             cps->sendDrawOffers = 0;
8866             return;
8867         }
8868         if (cps->sendTime != 1 &&
8869             (StrStr(message, "time") || StrStr(message, "otim"))) {
8870           /* Program apparently doesn't have "time" or "otim" command */
8871           cps->sendTime = 0;
8872           return;
8873         }
8874         if (StrStr(message, "analyze")) {
8875             cps->analysisSupport = FALSE;
8876             cps->analyzing = FALSE;
8877 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8878             EditGameEvent(); // [HGM] try to preserve loaded game
8879             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8880             DisplayError(buf2, 0);
8881             return;
8882         }
8883         if (StrStr(message, "(no matching move)st")) {
8884           /* Special kludge for GNU Chess 4 only */
8885           cps->stKludge = TRUE;
8886           SendTimeControl(cps, movesPerSession, timeControl,
8887                           timeIncrement, appData.searchDepth,
8888                           searchTime);
8889           return;
8890         }
8891         if (StrStr(message, "(no matching move)sd")) {
8892           /* Special kludge for GNU Chess 4 only */
8893           cps->sdKludge = TRUE;
8894           SendTimeControl(cps, movesPerSession, timeControl,
8895                           timeIncrement, appData.searchDepth,
8896                           searchTime);
8897           return;
8898         }
8899         if (!StrStr(message, "llegal")) {
8900             return;
8901         }
8902         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8903             gameMode == IcsIdle) return;
8904         if (forwardMostMove <= backwardMostMove) return;
8905         if (pausing) PauseEvent();
8906       if(appData.forceIllegal) {
8907             // [HGM] illegal: machine refused move; force position after move into it
8908           SendToProgram("force\n", cps);
8909           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8910                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8911                 // when black is to move, while there might be nothing on a2 or black
8912                 // might already have the move. So send the board as if white has the move.
8913                 // But first we must change the stm of the engine, as it refused the last move
8914                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8915                 if(WhiteOnMove(forwardMostMove)) {
8916                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8917                     SendBoard(cps, forwardMostMove); // kludgeless board
8918                 } else {
8919                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8920                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8921                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8922                 }
8923           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8924             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8925                  gameMode == TwoMachinesPlay)
8926               SendToProgram("go\n", cps);
8927             return;
8928       } else
8929         if (gameMode == PlayFromGameFile) {
8930             /* Stop reading this game file */
8931             gameMode = EditGame;
8932             ModeHighlight();
8933         }
8934         /* [HGM] illegal-move claim should forfeit game when Xboard */
8935         /* only passes fully legal moves                            */
8936         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8937             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8938                                 "False illegal-move claim", GE_XBOARD );
8939             return; // do not take back move we tested as valid
8940         }
8941         currentMove = forwardMostMove-1;
8942         DisplayMove(currentMove-1); /* before DisplayMoveError */
8943         SwitchClocks(forwardMostMove-1); // [HGM] race
8944         DisplayBothClocks();
8945         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8946                 parseList[currentMove], _(cps->which));
8947         DisplayMoveError(buf1);
8948         DrawPosition(FALSE, boards[currentMove]);
8949
8950         SetUserThinkingEnables();
8951         return;
8952     }
8953     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8954         /* Program has a broken "time" command that
8955            outputs a string not ending in newline.
8956            Don't use it. */
8957         cps->sendTime = 0;
8958     }
8959
8960     /*
8961      * If chess program startup fails, exit with an error message.
8962      * Attempts to recover here are futile. [HGM] Well, we try anyway
8963      */
8964     if ((StrStr(message, "unknown host") != NULL)
8965         || (StrStr(message, "No remote directory") != NULL)
8966         || (StrStr(message, "not found") != NULL)
8967         || (StrStr(message, "No such file") != NULL)
8968         || (StrStr(message, "can't alloc") != NULL)
8969         || (StrStr(message, "Permission denied") != NULL)) {
8970
8971         cps->maybeThinking = FALSE;
8972         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8973                 _(cps->which), cps->program, cps->host, message);
8974         RemoveInputSource(cps->isr);
8975         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8976             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
8977             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
8978         }
8979         return;
8980     }
8981
8982     /*
8983      * Look for hint output
8984      */
8985     if (sscanf(message, "Hint: %s", buf1) == 1) {
8986         if (cps == &first && hintRequested) {
8987             hintRequested = FALSE;
8988             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8989                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8990                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8991                                     PosFlags(forwardMostMove),
8992                                     fromY, fromX, toY, toX, promoChar, buf1);
8993                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8994                 DisplayInformation(buf2);
8995             } else {
8996                 /* Hint move could not be parsed!? */
8997               snprintf(buf2, sizeof(buf2),
8998                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8999                         buf1, _(cps->which));
9000                 DisplayError(buf2, 0);
9001             }
9002         } else {
9003           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
9004         }
9005         return;
9006     }
9007
9008     /*
9009      * Ignore other messages if game is not in progress
9010      */
9011     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9012         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
9013
9014     /*
9015      * look for win, lose, draw, or draw offer
9016      */
9017     if (strncmp(message, "1-0", 3) == 0) {
9018         char *p, *q, *r = "";
9019         p = strchr(message, '{');
9020         if (p) {
9021             q = strchr(p, '}');
9022             if (q) {
9023                 *q = NULLCHAR;
9024                 r = p + 1;
9025             }
9026         }
9027         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
9028         return;
9029     } else if (strncmp(message, "0-1", 3) == 0) {
9030         char *p, *q, *r = "";
9031         p = strchr(message, '{');
9032         if (p) {
9033             q = strchr(p, '}');
9034             if (q) {
9035                 *q = NULLCHAR;
9036                 r = p + 1;
9037             }
9038         }
9039         /* Kludge for Arasan 4.1 bug */
9040         if (strcmp(r, "Black resigns") == 0) {
9041             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
9042             return;
9043         }
9044         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
9045         return;
9046     } else if (strncmp(message, "1/2", 3) == 0) {
9047         char *p, *q, *r = "";
9048         p = strchr(message, '{');
9049         if (p) {
9050             q = strchr(p, '}');
9051             if (q) {
9052                 *q = NULLCHAR;
9053                 r = p + 1;
9054             }
9055         }
9056
9057         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9058         return;
9059
9060     } else if (strncmp(message, "White resign", 12) == 0) {
9061         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9062         return;
9063     } else if (strncmp(message, "Black resign", 12) == 0) {
9064         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9065         return;
9066     } else if (strncmp(message, "White matches", 13) == 0 ||
9067                strncmp(message, "Black matches", 13) == 0   ) {
9068         /* [HGM] ignore GNUShogi noises */
9069         return;
9070     } else if (strncmp(message, "White", 5) == 0 &&
9071                message[5] != '(' &&
9072                StrStr(message, "Black") == NULL) {
9073         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9074         return;
9075     } else if (strncmp(message, "Black", 5) == 0 &&
9076                message[5] != '(') {
9077         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9078         return;
9079     } else if (strcmp(message, "resign") == 0 ||
9080                strcmp(message, "computer resigns") == 0) {
9081         switch (gameMode) {
9082           case MachinePlaysBlack:
9083           case IcsPlayingBlack:
9084             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9085             break;
9086           case MachinePlaysWhite:
9087           case IcsPlayingWhite:
9088             GameEnds(BlackWins, "White resigns", GE_ENGINE);
9089             break;
9090           case TwoMachinesPlay:
9091             if (cps->twoMachinesColor[0] == 'w')
9092               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9093             else
9094               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9095             break;
9096           default:
9097             /* can't happen */
9098             break;
9099         }
9100         return;
9101     } else if (strncmp(message, "opponent mates", 14) == 0) {
9102         switch (gameMode) {
9103           case MachinePlaysBlack:
9104           case IcsPlayingBlack:
9105             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9106             break;
9107           case MachinePlaysWhite:
9108           case IcsPlayingWhite:
9109             GameEnds(BlackWins, "Black mates", GE_ENGINE);
9110             break;
9111           case TwoMachinesPlay:
9112             if (cps->twoMachinesColor[0] == 'w')
9113               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9114             else
9115               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9116             break;
9117           default:
9118             /* can't happen */
9119             break;
9120         }
9121         return;
9122     } else if (strncmp(message, "computer mates", 14) == 0) {
9123         switch (gameMode) {
9124           case MachinePlaysBlack:
9125           case IcsPlayingBlack:
9126             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9127             break;
9128           case MachinePlaysWhite:
9129           case IcsPlayingWhite:
9130             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9131             break;
9132           case TwoMachinesPlay:
9133             if (cps->twoMachinesColor[0] == 'w')
9134               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9135             else
9136               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9137             break;
9138           default:
9139             /* can't happen */
9140             break;
9141         }
9142         return;
9143     } else if (strncmp(message, "checkmate", 9) == 0) {
9144         if (WhiteOnMove(forwardMostMove)) {
9145             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9146         } else {
9147             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9148         }
9149         return;
9150     } else if (strstr(message, "Draw") != NULL ||
9151                strstr(message, "game is a draw") != NULL) {
9152         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9153         return;
9154     } else if (strstr(message, "offer") != NULL &&
9155                strstr(message, "draw") != NULL) {
9156 #if ZIPPY
9157         if (appData.zippyPlay && first.initDone) {
9158             /* Relay offer to ICS */
9159             SendToICS(ics_prefix);
9160             SendToICS("draw\n");
9161         }
9162 #endif
9163         cps->offeredDraw = 2; /* valid until this engine moves twice */
9164         if (gameMode == TwoMachinesPlay) {
9165             if (cps->other->offeredDraw) {
9166                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9167             /* [HGM] in two-machine mode we delay relaying draw offer      */
9168             /* until after we also have move, to see if it is really claim */
9169             }
9170         } else if (gameMode == MachinePlaysWhite ||
9171                    gameMode == MachinePlaysBlack) {
9172           if (userOfferedDraw) {
9173             DisplayInformation(_("Machine accepts your draw offer"));
9174             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9175           } else {
9176             DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9177           }
9178         }
9179     }
9180
9181
9182     /*
9183      * Look for thinking output
9184      */
9185     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9186           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9187                                 ) {
9188         int plylev, mvleft, mvtot, curscore, time;
9189         char mvname[MOVE_LEN];
9190         u64 nodes; // [DM]
9191         char plyext;
9192         int ignore = FALSE;
9193         int prefixHint = FALSE;
9194         mvname[0] = NULLCHAR;
9195
9196         switch (gameMode) {
9197           case MachinePlaysBlack:
9198           case IcsPlayingBlack:
9199             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9200             break;
9201           case MachinePlaysWhite:
9202           case IcsPlayingWhite:
9203             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9204             break;
9205           case AnalyzeMode:
9206           case AnalyzeFile:
9207             break;
9208           case IcsObserving: /* [DM] icsEngineAnalyze */
9209             if (!appData.icsEngineAnalyze) ignore = TRUE;
9210             break;
9211           case TwoMachinesPlay:
9212             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9213                 ignore = TRUE;
9214             }
9215             break;
9216           default:
9217             ignore = TRUE;
9218             break;
9219         }
9220
9221         if (!ignore) {
9222             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9223             buf1[0] = NULLCHAR;
9224             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9225                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9226
9227                 if (plyext != ' ' && plyext != '\t') {
9228                     time *= 100;
9229                 }
9230
9231                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9232                 if( cps->scoreIsAbsolute &&
9233                     ( gameMode == MachinePlaysBlack ||
9234                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9235                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
9236                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9237                      !WhiteOnMove(currentMove)
9238                     ) )
9239                 {
9240                     curscore = -curscore;
9241                 }
9242
9243                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9244
9245                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9246                         char buf[MSG_SIZ];
9247                         FILE *f;
9248                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9249                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9250                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9251                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9252                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
9253                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9254                                 fclose(f);
9255                         }
9256                         else
9257                           /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9258                           DisplayError(_("failed writing PV"), 0);
9259                 }
9260
9261                 tempStats.depth = plylev;
9262                 tempStats.nodes = nodes;
9263                 tempStats.time = time;
9264                 tempStats.score = curscore;
9265                 tempStats.got_only_move = 0;
9266
9267                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9268                         int ticklen;
9269
9270                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
9271                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9272                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9273                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9274                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9275                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9276                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9277                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9278                 }
9279
9280                 /* Buffer overflow protection */
9281                 if (pv[0] != NULLCHAR) {
9282                     if (strlen(pv) >= sizeof(tempStats.movelist)
9283                         && appData.debugMode) {
9284                         fprintf(debugFP,
9285                                 "PV is too long; using the first %u bytes.\n",
9286                                 (unsigned) sizeof(tempStats.movelist) - 1);
9287                     }
9288
9289                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9290                 } else {
9291                     sprintf(tempStats.movelist, " no PV\n");
9292                 }
9293
9294                 if (tempStats.seen_stat) {
9295                     tempStats.ok_to_send = 1;
9296                 }
9297
9298                 if (strchr(tempStats.movelist, '(') != NULL) {
9299                     tempStats.line_is_book = 1;
9300                     tempStats.nr_moves = 0;
9301                     tempStats.moves_left = 0;
9302                 } else {
9303                     tempStats.line_is_book = 0;
9304                 }
9305
9306                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9307                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9308
9309                 SendProgramStatsToFrontend( cps, &tempStats );
9310
9311                 /*
9312                     [AS] Protect the thinkOutput buffer from overflow... this
9313                     is only useful if buf1 hasn't overflowed first!
9314                 */
9315                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
9316                          plylev,
9317                          (gameMode == TwoMachinesPlay ?
9318                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9319                          ((double) curscore) / 100.0,
9320                          prefixHint ? lastHint : "",
9321                          prefixHint ? " " : "" );
9322
9323                 if( buf1[0] != NULLCHAR ) {
9324                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9325
9326                     if( strlen(pv) > max_len ) {
9327                         if( appData.debugMode) {
9328                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9329                         }
9330                         pv[max_len+1] = '\0';
9331                     }
9332
9333                     strcat( thinkOutput, pv);
9334                 }
9335
9336                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9337                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9338                     DisplayMove(currentMove - 1);
9339                 }
9340                 return;
9341
9342             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9343                 /* crafty (9.25+) says "(only move) <move>"
9344                  * if there is only 1 legal move
9345                  */
9346                 sscanf(p, "(only move) %s", buf1);
9347                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9348                 sprintf(programStats.movelist, "%s (only move)", buf1);
9349                 programStats.depth = 1;
9350                 programStats.nr_moves = 1;
9351                 programStats.moves_left = 1;
9352                 programStats.nodes = 1;
9353                 programStats.time = 1;
9354                 programStats.got_only_move = 1;
9355
9356                 /* Not really, but we also use this member to
9357                    mean "line isn't going to change" (Crafty
9358                    isn't searching, so stats won't change) */
9359                 programStats.line_is_book = 1;
9360
9361                 SendProgramStatsToFrontend( cps, &programStats );
9362
9363                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9364                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9365                     DisplayMove(currentMove - 1);
9366                 }
9367                 return;
9368             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9369                               &time, &nodes, &plylev, &mvleft,
9370                               &mvtot, mvname) >= 5) {
9371                 /* The stat01: line is from Crafty (9.29+) in response
9372                    to the "." command */
9373                 programStats.seen_stat = 1;
9374                 cps->maybeThinking = TRUE;
9375
9376                 if (programStats.got_only_move || !appData.periodicUpdates)
9377                   return;
9378
9379                 programStats.depth = plylev;
9380                 programStats.time = time;
9381                 programStats.nodes = nodes;
9382                 programStats.moves_left = mvleft;
9383                 programStats.nr_moves = mvtot;
9384                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9385                 programStats.ok_to_send = 1;
9386                 programStats.movelist[0] = '\0';
9387
9388                 SendProgramStatsToFrontend( cps, &programStats );
9389
9390                 return;
9391
9392             } else if (strncmp(message,"++",2) == 0) {
9393                 /* Crafty 9.29+ outputs this */
9394                 programStats.got_fail = 2;
9395                 return;
9396
9397             } else if (strncmp(message,"--",2) == 0) {
9398                 /* Crafty 9.29+ outputs this */
9399                 programStats.got_fail = 1;
9400                 return;
9401
9402             } else if (thinkOutput[0] != NULLCHAR &&
9403                        strncmp(message, "    ", 4) == 0) {
9404                 unsigned message_len;
9405
9406                 p = message;
9407                 while (*p && *p == ' ') p++;
9408
9409                 message_len = strlen( p );
9410
9411                 /* [AS] Avoid buffer overflow */
9412                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9413                     strcat(thinkOutput, " ");
9414                     strcat(thinkOutput, p);
9415                 }
9416
9417                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9418                     strcat(programStats.movelist, " ");
9419                     strcat(programStats.movelist, p);
9420                 }
9421
9422                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9423                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9424                     DisplayMove(currentMove - 1);
9425                 }
9426                 return;
9427             }
9428         }
9429         else {
9430             buf1[0] = NULLCHAR;
9431
9432             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9433                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9434             {
9435                 ChessProgramStats cpstats;
9436
9437                 if (plyext != ' ' && plyext != '\t') {
9438                     time *= 100;
9439                 }
9440
9441                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9442                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9443                     curscore = -curscore;
9444                 }
9445
9446                 cpstats.depth = plylev;
9447                 cpstats.nodes = nodes;
9448                 cpstats.time = time;
9449                 cpstats.score = curscore;
9450                 cpstats.got_only_move = 0;
9451                 cpstats.movelist[0] = '\0';
9452
9453                 if (buf1[0] != NULLCHAR) {
9454                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9455                 }
9456
9457                 cpstats.ok_to_send = 0;
9458                 cpstats.line_is_book = 0;
9459                 cpstats.nr_moves = 0;
9460                 cpstats.moves_left = 0;
9461
9462                 SendProgramStatsToFrontend( cps, &cpstats );
9463             }
9464         }
9465     }
9466 }
9467
9468
9469 /* Parse a game score from the character string "game", and
9470    record it as the history of the current game.  The game
9471    score is NOT assumed to start from the standard position.
9472    The display is not updated in any way.
9473    */
9474 void
9475 ParseGameHistory (char *game)
9476 {
9477     ChessMove moveType;
9478     int fromX, fromY, toX, toY, boardIndex;
9479     char promoChar;
9480     char *p, *q;
9481     char buf[MSG_SIZ];
9482
9483     if (appData.debugMode)
9484       fprintf(debugFP, "Parsing game history: %s\n", game);
9485
9486     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9487     gameInfo.site = StrSave(appData.icsHost);
9488     gameInfo.date = PGNDate();
9489     gameInfo.round = StrSave("-");
9490
9491     /* Parse out names of players */
9492     while (*game == ' ') game++;
9493     p = buf;
9494     while (*game != ' ') *p++ = *game++;
9495     *p = NULLCHAR;
9496     gameInfo.white = StrSave(buf);
9497     while (*game == ' ') game++;
9498     p = buf;
9499     while (*game != ' ' && *game != '\n') *p++ = *game++;
9500     *p = NULLCHAR;
9501     gameInfo.black = StrSave(buf);
9502
9503     /* Parse moves */
9504     boardIndex = blackPlaysFirst ? 1 : 0;
9505     yynewstr(game);
9506     for (;;) {
9507         yyboardindex = boardIndex;
9508         moveType = (ChessMove) Myylex();
9509         switch (moveType) {
9510           case IllegalMove:             /* maybe suicide chess, etc. */
9511   if (appData.debugMode) {
9512     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9513     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9514     setbuf(debugFP, NULL);
9515   }
9516           case WhitePromotion:
9517           case BlackPromotion:
9518           case WhiteNonPromotion:
9519           case BlackNonPromotion:
9520           case NormalMove:
9521           case FirstLeg:
9522           case WhiteCapturesEnPassant:
9523           case BlackCapturesEnPassant:
9524           case WhiteKingSideCastle:
9525           case WhiteQueenSideCastle:
9526           case BlackKingSideCastle:
9527           case BlackQueenSideCastle:
9528           case WhiteKingSideCastleWild:
9529           case WhiteQueenSideCastleWild:
9530           case BlackKingSideCastleWild:
9531           case BlackQueenSideCastleWild:
9532           /* PUSH Fabien */
9533           case WhiteHSideCastleFR:
9534           case WhiteASideCastleFR:
9535           case BlackHSideCastleFR:
9536           case BlackASideCastleFR:
9537           /* POP Fabien */
9538             fromX = currentMoveString[0] - AAA;
9539             fromY = currentMoveString[1] - ONE;
9540             toX = currentMoveString[2] - AAA;
9541             toY = currentMoveString[3] - ONE;
9542             promoChar = currentMoveString[4];
9543             break;
9544           case WhiteDrop:
9545           case BlackDrop:
9546             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9547             fromX = moveType == WhiteDrop ?
9548               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9549             (int) CharToPiece(ToLower(currentMoveString[0]));
9550             fromY = DROP_RANK;
9551             toX = currentMoveString[2] - AAA;
9552             toY = currentMoveString[3] - ONE;
9553             promoChar = NULLCHAR;
9554             break;
9555           case AmbiguousMove:
9556             /* bug? */
9557             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9558   if (appData.debugMode) {
9559     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9560     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9561     setbuf(debugFP, NULL);
9562   }
9563             DisplayError(buf, 0);
9564             return;
9565           case ImpossibleMove:
9566             /* bug? */
9567             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9568   if (appData.debugMode) {
9569     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9570     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9571     setbuf(debugFP, NULL);
9572   }
9573             DisplayError(buf, 0);
9574             return;
9575           case EndOfFile:
9576             if (boardIndex < backwardMostMove) {
9577                 /* Oops, gap.  How did that happen? */
9578                 DisplayError(_("Gap in move list"), 0);
9579                 return;
9580             }
9581             backwardMostMove =  blackPlaysFirst ? 1 : 0;
9582             if (boardIndex > forwardMostMove) {
9583                 forwardMostMove = boardIndex;
9584             }
9585             return;
9586           case ElapsedTime:
9587             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9588                 strcat(parseList[boardIndex-1], " ");
9589                 strcat(parseList[boardIndex-1], yy_text);
9590             }
9591             continue;
9592           case Comment:
9593           case PGNTag:
9594           case NAG:
9595           default:
9596             /* ignore */
9597             continue;
9598           case WhiteWins:
9599           case BlackWins:
9600           case GameIsDrawn:
9601           case GameUnfinished:
9602             if (gameMode == IcsExamining) {
9603                 if (boardIndex < backwardMostMove) {
9604                     /* Oops, gap.  How did that happen? */
9605                     return;
9606                 }
9607                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9608                 return;
9609             }
9610             gameInfo.result = moveType;
9611             p = strchr(yy_text, '{');
9612             if (p == NULL) p = strchr(yy_text, '(');
9613             if (p == NULL) {
9614                 p = yy_text;
9615                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9616             } else {
9617                 q = strchr(p, *p == '{' ? '}' : ')');
9618                 if (q != NULL) *q = NULLCHAR;
9619                 p++;
9620             }
9621             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9622             gameInfo.resultDetails = StrSave(p);
9623             continue;
9624         }
9625         if (boardIndex >= forwardMostMove &&
9626             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9627             backwardMostMove = blackPlaysFirst ? 1 : 0;
9628             return;
9629         }
9630         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9631                                  fromY, fromX, toY, toX, promoChar,
9632                                  parseList[boardIndex]);
9633         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9634         /* currentMoveString is set as a side-effect of yylex */
9635         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9636         strcat(moveList[boardIndex], "\n");
9637         boardIndex++;
9638         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9639         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9640           case MT_NONE:
9641           case MT_STALEMATE:
9642           default:
9643             break;
9644           case MT_CHECK:
9645             if(gameInfo.variant != VariantShogi)
9646                 strcat(parseList[boardIndex - 1], "+");
9647             break;
9648           case MT_CHECKMATE:
9649           case MT_STAINMATE:
9650             strcat(parseList[boardIndex - 1], "#");
9651             break;
9652         }
9653     }
9654 }
9655
9656
9657 /* Apply a move to the given board  */
9658 void
9659 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9660 {
9661   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9662   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9663
9664     /* [HGM] compute & store e.p. status and castling rights for new position */
9665     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9666
9667       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9668       oldEP = (signed char)board[EP_STATUS];
9669       board[EP_STATUS] = EP_NONE;
9670
9671   if (fromY == DROP_RANK) {
9672         /* must be first */
9673         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9674             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9675             return;
9676         }
9677         piece = board[toY][toX] = (ChessSquare) fromX;
9678   } else {
9679       ChessSquare victim;
9680       int i;
9681
9682       if( killX >= 0 && killY >= 0 ) // [HGM] lion: Lion trampled over something
9683            victim = board[killY][killX],
9684            board[killY][killX] = EmptySquare,
9685            board[EP_STATUS] = EP_CAPTURE;
9686
9687       if( board[toY][toX] != EmptySquare ) {
9688            board[EP_STATUS] = EP_CAPTURE;
9689            if( (fromX != toX || fromY != toY) && // not igui!
9690                (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
9691                 captured == BlackLion && board[fromY][fromX] != WhiteLion   ) ) { // [HGM] lion: Chu Lion-capture rules
9692                board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
9693            }
9694       }
9695
9696       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9697            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9698                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9699       } else
9700       if( board[fromY][fromX] == WhitePawn ) {
9701            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9702                board[EP_STATUS] = EP_PAWN_MOVE;
9703            if( toY-fromY==2) {
9704                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9705                         gameInfo.variant != VariantBerolina || toX < fromX)
9706                       board[EP_STATUS] = toX | berolina;
9707                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9708                         gameInfo.variant != VariantBerolina || toX > fromX)
9709                       board[EP_STATUS] = toX;
9710            }
9711       } else
9712       if( board[fromY][fromX] == BlackPawn ) {
9713            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9714                board[EP_STATUS] = EP_PAWN_MOVE;
9715            if( toY-fromY== -2) {
9716                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9717                         gameInfo.variant != VariantBerolina || toX < fromX)
9718                       board[EP_STATUS] = toX | berolina;
9719                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9720                         gameInfo.variant != VariantBerolina || toX > fromX)
9721                       board[EP_STATUS] = toX;
9722            }
9723        }
9724
9725        for(i=0; i<nrCastlingRights; i++) {
9726            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9727               board[CASTLING][i] == toX   && castlingRank[i] == toY
9728              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9729        }
9730
9731        if(gameInfo.variant == VariantSChess) { // update virginity
9732            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
9733            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
9734            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
9735            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
9736        }
9737
9738      if (fromX == toX && fromY == toY) return;
9739
9740      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9741      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9742      if(gameInfo.variant == VariantKnightmate)
9743          king += (int) WhiteUnicorn - (int) WhiteKing;
9744
9745     /* Code added by Tord: */
9746     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9747     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9748         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9749       board[fromY][fromX] = EmptySquare;
9750       board[toY][toX] = EmptySquare;
9751       if((toX > fromX) != (piece == WhiteRook)) {
9752         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9753       } else {
9754         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9755       }
9756     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9757                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9758       board[fromY][fromX] = EmptySquare;
9759       board[toY][toX] = EmptySquare;
9760       if((toX > fromX) != (piece == BlackRook)) {
9761         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9762       } else {
9763         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9764       }
9765     /* End of code added by Tord */
9766
9767     } else if (board[fromY][fromX] == king
9768         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9769         && toY == fromY && toX > fromX+1) {
9770         board[fromY][fromX] = EmptySquare;
9771         board[toY][toX] = king;
9772         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9773         board[fromY][BOARD_RGHT-1] = EmptySquare;
9774     } else if (board[fromY][fromX] == king
9775         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9776                && toY == fromY && toX < fromX-1) {
9777         board[fromY][fromX] = EmptySquare;
9778         board[toY][toX] = king;
9779         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9780         board[fromY][BOARD_LEFT] = EmptySquare;
9781     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9782                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9783                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9784                ) {
9785         /* white pawn promotion */
9786         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9787         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9788             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9789         board[fromY][fromX] = EmptySquare;
9790     } else if ((fromY >= BOARD_HEIGHT>>1)
9791                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9792                && (toX != fromX)
9793                && gameInfo.variant != VariantXiangqi
9794                && gameInfo.variant != VariantBerolina
9795                && (board[fromY][fromX] == WhitePawn)
9796                && (board[toY][toX] == EmptySquare)) {
9797         board[fromY][fromX] = EmptySquare;
9798         board[toY][toX] = WhitePawn;
9799         captured = board[toY - 1][toX];
9800         board[toY - 1][toX] = EmptySquare;
9801     } else if ((fromY == BOARD_HEIGHT-4)
9802                && (toX == fromX)
9803                && gameInfo.variant == VariantBerolina
9804                && (board[fromY][fromX] == WhitePawn)
9805                && (board[toY][toX] == EmptySquare)) {
9806         board[fromY][fromX] = EmptySquare;
9807         board[toY][toX] = WhitePawn;
9808         if(oldEP & EP_BEROLIN_A) {
9809                 captured = board[fromY][fromX-1];
9810                 board[fromY][fromX-1] = EmptySquare;
9811         }else{  captured = board[fromY][fromX+1];
9812                 board[fromY][fromX+1] = EmptySquare;
9813         }
9814     } else if (board[fromY][fromX] == king
9815         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9816                && toY == fromY && toX > fromX+1) {
9817         board[fromY][fromX] = EmptySquare;
9818         board[toY][toX] = king;
9819         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9820         board[fromY][BOARD_RGHT-1] = EmptySquare;
9821     } else if (board[fromY][fromX] == king
9822         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9823                && toY == fromY && toX < fromX-1) {
9824         board[fromY][fromX] = EmptySquare;
9825         board[toY][toX] = king;
9826         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9827         board[fromY][BOARD_LEFT] = EmptySquare;
9828     } else if (fromY == 7 && fromX == 3
9829                && board[fromY][fromX] == BlackKing
9830                && toY == 7 && toX == 5) {
9831         board[fromY][fromX] = EmptySquare;
9832         board[toY][toX] = BlackKing;
9833         board[fromY][7] = EmptySquare;
9834         board[toY][4] = BlackRook;
9835     } else if (fromY == 7 && fromX == 3
9836                && board[fromY][fromX] == BlackKing
9837                && toY == 7 && toX == 1) {
9838         board[fromY][fromX] = EmptySquare;
9839         board[toY][toX] = BlackKing;
9840         board[fromY][0] = EmptySquare;
9841         board[toY][2] = BlackRook;
9842     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9843                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9844                && toY < promoRank && promoChar
9845                ) {
9846         /* black pawn promotion */
9847         board[toY][toX] = CharToPiece(ToLower(promoChar));
9848         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9849             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9850         board[fromY][fromX] = EmptySquare;
9851     } else if ((fromY < BOARD_HEIGHT>>1)
9852                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9853                && (toX != fromX)
9854                && gameInfo.variant != VariantXiangqi
9855                && gameInfo.variant != VariantBerolina
9856                && (board[fromY][fromX] == BlackPawn)
9857                && (board[toY][toX] == EmptySquare)) {
9858         board[fromY][fromX] = EmptySquare;
9859         board[toY][toX] = BlackPawn;
9860         captured = board[toY + 1][toX];
9861         board[toY + 1][toX] = EmptySquare;
9862     } else if ((fromY == 3)
9863                && (toX == fromX)
9864                && gameInfo.variant == VariantBerolina
9865                && (board[fromY][fromX] == BlackPawn)
9866                && (board[toY][toX] == EmptySquare)) {
9867         board[fromY][fromX] = EmptySquare;
9868         board[toY][toX] = BlackPawn;
9869         if(oldEP & EP_BEROLIN_A) {
9870                 captured = board[fromY][fromX-1];
9871                 board[fromY][fromX-1] = EmptySquare;
9872         }else{  captured = board[fromY][fromX+1];
9873                 board[fromY][fromX+1] = EmptySquare;
9874         }
9875     } else {
9876         ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
9877         board[fromY][fromX] = EmptySquare;
9878         board[toY][toX] = piece;
9879     }
9880   }
9881
9882     if (gameInfo.holdingsWidth != 0) {
9883
9884       /* !!A lot more code needs to be written to support holdings  */
9885       /* [HGM] OK, so I have written it. Holdings are stored in the */
9886       /* penultimate board files, so they are automaticlly stored   */
9887       /* in the game history.                                       */
9888       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9889                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9890         /* Delete from holdings, by decreasing count */
9891         /* and erasing image if necessary            */
9892         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9893         if(p < (int) BlackPawn) { /* white drop */
9894              p -= (int)WhitePawn;
9895                  p = PieceToNumber((ChessSquare)p);
9896              if(p >= gameInfo.holdingsSize) p = 0;
9897              if(--board[p][BOARD_WIDTH-2] <= 0)
9898                   board[p][BOARD_WIDTH-1] = EmptySquare;
9899              if((int)board[p][BOARD_WIDTH-2] < 0)
9900                         board[p][BOARD_WIDTH-2] = 0;
9901         } else {                  /* black drop */
9902              p -= (int)BlackPawn;
9903                  p = PieceToNumber((ChessSquare)p);
9904              if(p >= gameInfo.holdingsSize) p = 0;
9905              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9906                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9907              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9908                         board[BOARD_HEIGHT-1-p][1] = 0;
9909         }
9910       }
9911       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9912           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9913         /* [HGM] holdings: Add to holdings, if holdings exist */
9914         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9915                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9916                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9917         }
9918         p = (int) captured;
9919         if (p >= (int) BlackPawn) {
9920           p -= (int)BlackPawn;
9921           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9922                   /* in Shogi restore piece to its original  first */
9923                   captured = (ChessSquare) (DEMOTED captured);
9924                   p = DEMOTED p;
9925           }
9926           p = PieceToNumber((ChessSquare)p);
9927           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9928           board[p][BOARD_WIDTH-2]++;
9929           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9930         } else {
9931           p -= (int)WhitePawn;
9932           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9933                   captured = (ChessSquare) (DEMOTED captured);
9934                   p = DEMOTED p;
9935           }
9936           p = PieceToNumber((ChessSquare)p);
9937           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9938           board[BOARD_HEIGHT-1-p][1]++;
9939           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9940         }
9941       }
9942     } else if (gameInfo.variant == VariantAtomic) {
9943       if (captured != EmptySquare) {
9944         int y, x;
9945         for (y = toY-1; y <= toY+1; y++) {
9946           for (x = toX-1; x <= toX+1; x++) {
9947             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9948                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9949               board[y][x] = EmptySquare;
9950             }
9951           }
9952         }
9953         board[toY][toX] = EmptySquare;
9954       }
9955     }
9956     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9957         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9958     } else
9959     if(promoChar == '+') {
9960         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
9961         board[toY][toX] = (ChessSquare) (CHUPROMOTED piece);
9962     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9963         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9964         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
9965            && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
9966         board[toY][toX] = newPiece;
9967     }
9968     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9969                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9970         // [HGM] superchess: take promotion piece out of holdings
9971         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9972         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9973             if(!--board[k][BOARD_WIDTH-2])
9974                 board[k][BOARD_WIDTH-1] = EmptySquare;
9975         } else {
9976             if(!--board[BOARD_HEIGHT-1-k][1])
9977                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9978         }
9979     }
9980 }
9981
9982 /* Updates forwardMostMove */
9983 void
9984 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
9985 {
9986     int x = toX, y = toY;
9987     char *s = parseList[forwardMostMove];
9988     ChessSquare p = boards[forwardMostMove][toY][toX];
9989 //    forwardMostMove++; // [HGM] bare: moved downstream
9990
9991     if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
9992     (void) CoordsToAlgebraic(boards[forwardMostMove],
9993                              PosFlags(forwardMostMove),
9994                              fromY, fromX, y, x, promoChar,
9995                              s);
9996     if(killX >= 0 && killY >= 0)
9997         sprintf(s + strlen(s), "%c%c%d", p == EmptySquare || toX == fromX && toY == fromY ? '-' : 'x', toX + AAA, toY + ONE - '0');
9998
9999     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
10000         int timeLeft; static int lastLoadFlag=0; int king, piece;
10001         piece = boards[forwardMostMove][fromY][fromX];
10002         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
10003         if(gameInfo.variant == VariantKnightmate)
10004             king += (int) WhiteUnicorn - (int) WhiteKing;
10005         if(forwardMostMove == 0) {
10006             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
10007                 fprintf(serverMoves, "%s;", UserName());
10008             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
10009                 fprintf(serverMoves, "%s;", second.tidy);
10010             fprintf(serverMoves, "%s;", first.tidy);
10011             if(gameMode == MachinePlaysWhite)
10012                 fprintf(serverMoves, "%s;", UserName());
10013             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
10014                 fprintf(serverMoves, "%s;", second.tidy);
10015         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
10016         lastLoadFlag = loadFlag;
10017         // print base move
10018         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
10019         // print castling suffix
10020         if( toY == fromY && piece == king ) {
10021             if(toX-fromX > 1)
10022                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
10023             if(fromX-toX >1)
10024                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10025         }
10026         // e.p. suffix
10027         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10028              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
10029              boards[forwardMostMove][toY][toX] == EmptySquare
10030              && fromX != toX && fromY != toY)
10031                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10032         // promotion suffix
10033         if(promoChar != NULLCHAR) {
10034             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10035                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10036                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10037             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10038         }
10039         if(!loadFlag) {
10040                 char buf[MOVE_LEN*2], *p; int len;
10041             fprintf(serverMoves, "/%d/%d",
10042                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10043             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10044             else                      timeLeft = blackTimeRemaining/1000;
10045             fprintf(serverMoves, "/%d", timeLeft);
10046                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10047                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10048                 if(p = strchr(buf, '=')) *p = NULLCHAR;
10049                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10050             fprintf(serverMoves, "/%s", buf);
10051         }
10052         fflush(serverMoves);
10053     }
10054
10055     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10056         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10057       return;
10058     }
10059     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10060     if (commentList[forwardMostMove+1] != NULL) {
10061         free(commentList[forwardMostMove+1]);
10062         commentList[forwardMostMove+1] = NULL;
10063     }
10064     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10065     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10066     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10067     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10068     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10069     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10070     adjustedClock = FALSE;
10071     gameInfo.result = GameUnfinished;
10072     if (gameInfo.resultDetails != NULL) {
10073         free(gameInfo.resultDetails);
10074         gameInfo.resultDetails = NULL;
10075     }
10076     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10077                               moveList[forwardMostMove - 1]);
10078     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10079       case MT_NONE:
10080       case MT_STALEMATE:
10081       default:
10082         break;
10083       case MT_CHECK:
10084         if(gameInfo.variant != VariantShogi)
10085             strcat(parseList[forwardMostMove - 1], "+");
10086         break;
10087       case MT_CHECKMATE:
10088       case MT_STAINMATE:
10089         strcat(parseList[forwardMostMove - 1], "#");
10090         break;
10091     }
10092 }
10093
10094 /* Updates currentMove if not pausing */
10095 void
10096 ShowMove (int fromX, int fromY, int toX, int toY)
10097 {
10098     int instant = (gameMode == PlayFromGameFile) ?
10099         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10100     if(appData.noGUI) return;
10101     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10102         if (!instant) {
10103             if (forwardMostMove == currentMove + 1) {
10104                 AnimateMove(boards[forwardMostMove - 1],
10105                             fromX, fromY, toX, toY);
10106             }
10107         }
10108         currentMove = forwardMostMove;
10109     }
10110
10111     killX = killY = -1; // [HGM] lion: used up
10112
10113     if (instant) return;
10114
10115     DisplayMove(currentMove - 1);
10116     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10117             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10118                 SetHighlights(fromX, fromY, toX, toY);
10119             }
10120     }
10121     DrawPosition(FALSE, boards[currentMove]);
10122     DisplayBothClocks();
10123     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10124 }
10125
10126 void
10127 SendEgtPath (ChessProgramState *cps)
10128 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10129         char buf[MSG_SIZ], name[MSG_SIZ], *p;
10130
10131         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10132
10133         while(*p) {
10134             char c, *q = name+1, *r, *s;
10135
10136             name[0] = ','; // extract next format name from feature and copy with prefixed ','
10137             while(*p && *p != ',') *q++ = *p++;
10138             *q++ = ':'; *q = 0;
10139             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10140                 strcmp(name, ",nalimov:") == 0 ) {
10141                 // take nalimov path from the menu-changeable option first, if it is defined
10142               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10143                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
10144             } else
10145             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10146                 (s = StrStr(appData.egtFormats, name)) != NULL) {
10147                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10148                 s = r = StrStr(s, ":") + 1; // beginning of path info
10149                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10150                 c = *r; *r = 0;             // temporarily null-terminate path info
10151                     *--q = 0;               // strip of trailig ':' from name
10152                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10153                 *r = c;
10154                 SendToProgram(buf,cps);     // send egtbpath command for this format
10155             }
10156             if(*p == ',') p++; // read away comma to position for next format name
10157         }
10158 }
10159
10160 static int
10161 NonStandardBoardSize (VariantClass v, int boardWidth, int boardHeight, int holdingsSize)
10162 {
10163       int width = 8, height = 8, holdings = 0;             // most common sizes
10164       if( v == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10165       // correct the deviations default for each variant
10166       if( v == VariantXiangqi ) width = 9,  height = 10;
10167       if( v == VariantShogi )   width = 9,  height = 9,  holdings = 7;
10168       if( v == VariantBughouse || v == VariantCrazyhouse) holdings = 5;
10169       if( v == VariantCapablanca || v == VariantCapaRandom ||
10170           v == VariantGothic || v == VariantFalcon || v == VariantJanus )
10171                                 width = 10;
10172       if( v == VariantCourier ) width = 12;
10173       if( v == VariantSuper )                            holdings = 8;
10174       if( v == VariantGreat )   width = 10,              holdings = 8;
10175       if( v == VariantSChess )                           holdings = 7;
10176       if( v == VariantGrand )   width = 10, height = 10, holdings = 7;
10177       if( v == VariantChu )     width = 12, height = 12;
10178       return boardWidth >= 0   && boardWidth   != width  || // -1 is default,
10179              boardHeight >= 0  && boardHeight  != height || // and thus by definition OK
10180              holdingsSize >= 0 && holdingsSize != holdings;
10181 }
10182
10183 char variantError[MSG_SIZ];
10184
10185 char *
10186 SupportedVariant (char *list, VariantClass v, int boardWidth, int boardHeight, int holdingsSize, int proto, char *engine)
10187 {     // returns error message (recognizable by upper-case) if engine does not support the variant
10188       char *p, *variant = VariantName(v);
10189       static char b[MSG_SIZ];
10190       if(NonStandardBoardSize(v, boardWidth, boardHeight, holdingsSize)) { /* [HGM] make prefix for non-standard board size. */
10191            snprintf(b, MSG_SIZ, "%dx%d+%d_%s", boardWidth, boardHeight,
10192                                                holdingsSize, variant); // cook up sized variant name
10193            /* [HGM] varsize: try first if this deviant size variant is specifically known */
10194            if(StrStr(list, b) == NULL) {
10195                // specific sized variant not known, check if general sizing allowed
10196                if(proto != 1 && StrStr(list, "boardsize") == NULL) {
10197                    snprintf(variantError, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10198                             boardWidth, boardHeight, holdingsSize, engine);
10199                    return NULL;
10200                }
10201                /* [HGM] here we really should compare with the maximum supported board size */
10202            }
10203       } else snprintf(b, MSG_SIZ,"%s", variant);
10204       if(proto == 1) return b; // for protocol 1 we cannot check and hope for the best
10205       p = StrStr(list, b);
10206       while(p && (p != list && p[-1] != ',' || p[strlen(b)] && p[strlen(b)] != ',') ) p = StrStr(p+1, b);
10207       if(p == NULL) {
10208           // occurs not at all in list, or only as sub-string
10209           snprintf(variantError, MSG_SIZ, _("Variant %s not supported by %s"), b, engine);
10210           if(p = StrStr(list, b)) { // handle requesting parent variant when only size-overridden is supported
10211               int l = strlen(variantError);
10212               char *q;
10213               while(p != list && p[-1] != ',') p--;
10214               q = strchr(p, ',');
10215               if(q) *q = NULLCHAR;
10216               snprintf(variantError + l, MSG_SIZ - l,  _(", but %s is"), p);
10217               if(q) *q= ',';
10218           }
10219           return NULL;
10220       }
10221       return b;
10222 }
10223
10224 void
10225 InitChessProgram (ChessProgramState *cps, int setup)
10226 /* setup needed to setup FRC opening position */
10227 {
10228     char buf[MSG_SIZ], *b;
10229     if (appData.noChessProgram) return;
10230     hintRequested = FALSE;
10231     bookRequested = FALSE;
10232
10233     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10234     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10235     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10236     if(cps->memSize) { /* [HGM] memory */
10237       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10238         SendToProgram(buf, cps);
10239     }
10240     SendEgtPath(cps); /* [HGM] EGT */
10241     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10242       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10243         SendToProgram(buf, cps);
10244     }
10245
10246     SendToProgram(cps->initString, cps);
10247     if (gameInfo.variant != VariantNormal &&
10248         gameInfo.variant != VariantLoadable
10249         /* [HGM] also send variant if board size non-standard */
10250         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0) {
10251
10252       b = SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
10253                            gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, cps->tidy);
10254       if (b == NULL) {
10255         DisplayFatalError(variantError, 0, 1);
10256         return;
10257       }
10258
10259       snprintf(buf, MSG_SIZ, "variant %s\n", b);
10260       SendToProgram(buf, cps);
10261     }
10262     currentlyInitializedVariant = gameInfo.variant;
10263
10264     /* [HGM] send opening position in FRC to first engine */
10265     if(setup) {
10266           SendToProgram("force\n", cps);
10267           SendBoard(cps, 0);
10268           /* engine is now in force mode! Set flag to wake it up after first move. */
10269           setboardSpoiledMachineBlack = 1;
10270     }
10271
10272     if (cps->sendICS) {
10273       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10274       SendToProgram(buf, cps);
10275     }
10276     cps->maybeThinking = FALSE;
10277     cps->offeredDraw = 0;
10278     if (!appData.icsActive) {
10279         SendTimeControl(cps, movesPerSession, timeControl,
10280                         timeIncrement, appData.searchDepth,
10281                         searchTime);
10282     }
10283     if (appData.showThinking
10284         // [HGM] thinking: four options require thinking output to be sent
10285         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10286                                 ) {
10287         SendToProgram("post\n", cps);
10288     }
10289     SendToProgram("hard\n", cps);
10290     if (!appData.ponderNextMove) {
10291         /* Warning: "easy" is a toggle in GNU Chess, so don't send
10292            it without being sure what state we are in first.  "hard"
10293            is not a toggle, so that one is OK.
10294          */
10295         SendToProgram("easy\n", cps);
10296     }
10297     if (cps->usePing) {
10298       snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++cps->lastPing);
10299       SendToProgram(buf, cps);
10300     }
10301     cps->initDone = TRUE;
10302     ClearEngineOutputPane(cps == &second);
10303 }
10304
10305
10306 void
10307 ResendOptions (ChessProgramState *cps)
10308 { // send the stored value of the options
10309   int i;
10310   char buf[MSG_SIZ];
10311   Option *opt = cps->option;
10312   for(i=0; i<cps->nrOptions; i++, opt++) {
10313       switch(opt->type) {
10314         case Spin:
10315         case Slider:
10316         case CheckBox:
10317             snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10318           break;
10319         case ComboBox:
10320           snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10321           break;
10322         default:
10323             snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10324           break;
10325         case Button:
10326         case SaveButton:
10327           continue;
10328       }
10329       SendToProgram(buf, cps);
10330   }
10331 }
10332
10333 void
10334 StartChessProgram (ChessProgramState *cps)
10335 {
10336     char buf[MSG_SIZ];
10337     int err;
10338
10339     if (appData.noChessProgram) return;
10340     cps->initDone = FALSE;
10341
10342     if (strcmp(cps->host, "localhost") == 0) {
10343         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10344     } else if (*appData.remoteShell == NULLCHAR) {
10345         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10346     } else {
10347         if (*appData.remoteUser == NULLCHAR) {
10348           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10349                     cps->program);
10350         } else {
10351           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10352                     cps->host, appData.remoteUser, cps->program);
10353         }
10354         err = StartChildProcess(buf, "", &cps->pr);
10355     }
10356
10357     if (err != 0) {
10358       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10359         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10360         if(cps != &first) return;
10361         appData.noChessProgram = TRUE;
10362         ThawUI();
10363         SetNCPMode();
10364 //      DisplayFatalError(buf, err, 1);
10365 //      cps->pr = NoProc;
10366 //      cps->isr = NULL;
10367         return;
10368     }
10369
10370     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10371     if (cps->protocolVersion > 1) {
10372       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10373       if(!cps->reload) { // do not clear options when reloading because of -xreuse
10374         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10375         cps->comboCnt = 0;  //                and values of combo boxes
10376       }
10377       SendToProgram(buf, cps);
10378       if(cps->reload) ResendOptions(cps);
10379     } else {
10380       SendToProgram("xboard\n", cps);
10381     }
10382 }
10383
10384 void
10385 TwoMachinesEventIfReady P((void))
10386 {
10387   static int curMess = 0;
10388   if (first.lastPing != first.lastPong) {
10389     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10390     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10391     return;
10392   }
10393   if (second.lastPing != second.lastPong) {
10394     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10395     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10396     return;
10397   }
10398   DisplayMessage("", ""); curMess = 0;
10399   TwoMachinesEvent();
10400 }
10401
10402 char *
10403 MakeName (char *template)
10404 {
10405     time_t clock;
10406     struct tm *tm;
10407     static char buf[MSG_SIZ];
10408     char *p = buf;
10409     int i;
10410
10411     clock = time((time_t *)NULL);
10412     tm = localtime(&clock);
10413
10414     while(*p++ = *template++) if(p[-1] == '%') {
10415         switch(*template++) {
10416           case 0:   *p = 0; return buf;
10417           case 'Y': i = tm->tm_year+1900; break;
10418           case 'y': i = tm->tm_year-100; break;
10419           case 'M': i = tm->tm_mon+1; break;
10420           case 'd': i = tm->tm_mday; break;
10421           case 'h': i = tm->tm_hour; break;
10422           case 'm': i = tm->tm_min; break;
10423           case 's': i = tm->tm_sec; break;
10424           default:  i = 0;
10425         }
10426         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10427     }
10428     return buf;
10429 }
10430
10431 int
10432 CountPlayers (char *p)
10433 {
10434     int n = 0;
10435     while(p = strchr(p, '\n')) p++, n++; // count participants
10436     return n;
10437 }
10438
10439 FILE *
10440 WriteTourneyFile (char *results, FILE *f)
10441 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10442     if(f == NULL) f = fopen(appData.tourneyFile, "w");
10443     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10444         // create a file with tournament description
10445         fprintf(f, "-participants {%s}\n", appData.participants);
10446         fprintf(f, "-seedBase %d\n", appData.seedBase);
10447         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10448         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10449         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10450         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10451         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10452         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10453         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10454         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10455         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10456         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10457         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10458         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10459         fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
10460         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10461         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10462         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10463         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10464         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10465         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10466         fprintf(f, "-smpCores %d\n", appData.smpCores);
10467         if(searchTime > 0)
10468                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10469         else {
10470                 fprintf(f, "-mps %d\n", appData.movesPerSession);
10471                 fprintf(f, "-tc %s\n", appData.timeControl);
10472                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10473         }
10474         fprintf(f, "-results \"%s\"\n", results);
10475     }
10476     return f;
10477 }
10478
10479 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10480
10481 void
10482 Substitute (char *participants, int expunge)
10483 {
10484     int i, changed, changes=0, nPlayers=0;
10485     char *p, *q, *r, buf[MSG_SIZ];
10486     if(participants == NULL) return;
10487     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10488     r = p = participants; q = appData.participants;
10489     while(*p && *p == *q) {
10490         if(*p == '\n') r = p+1, nPlayers++;
10491         p++; q++;
10492     }
10493     if(*p) { // difference
10494         while(*p && *p++ != '\n');
10495         while(*q && *q++ != '\n');
10496       changed = nPlayers;
10497         changes = 1 + (strcmp(p, q) != 0);
10498     }
10499     if(changes == 1) { // a single engine mnemonic was changed
10500         q = r; while(*q) nPlayers += (*q++ == '\n');
10501         p = buf; while(*r && (*p = *r++) != '\n') p++;
10502         *p = NULLCHAR;
10503         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10504         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10505         if(mnemonic[i]) { // The substitute is valid
10506             FILE *f;
10507             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10508                 flock(fileno(f), LOCK_EX);
10509                 ParseArgsFromFile(f);
10510                 fseek(f, 0, SEEK_SET);
10511                 FREE(appData.participants); appData.participants = participants;
10512                 if(expunge) { // erase results of replaced engine
10513                     int len = strlen(appData.results), w, b, dummy;
10514                     for(i=0; i<len; i++) {
10515                         Pairing(i, nPlayers, &w, &b, &dummy);
10516                         if((w == changed || b == changed) && appData.results[i] == '*') {
10517                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10518                             fclose(f);
10519                             return;
10520                         }
10521                     }
10522                     for(i=0; i<len; i++) {
10523                         Pairing(i, nPlayers, &w, &b, &dummy);
10524                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10525                     }
10526                 }
10527                 WriteTourneyFile(appData.results, f);
10528                 fclose(f); // release lock
10529                 return;
10530             }
10531         } else DisplayError(_("No engine with the name you gave is installed"), 0);
10532     }
10533     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10534     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
10535     free(participants);
10536     return;
10537 }
10538
10539 int
10540 CheckPlayers (char *participants)
10541 {
10542         int i;
10543         char buf[MSG_SIZ], *p;
10544         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10545         while(p = strchr(participants, '\n')) {
10546             *p = NULLCHAR;
10547             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10548             if(!mnemonic[i]) {
10549                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10550                 *p = '\n';
10551                 DisplayError(buf, 0);
10552                 return 1;
10553             }
10554             *p = '\n';
10555             participants = p + 1;
10556         }
10557         return 0;
10558 }
10559
10560 int
10561 CreateTourney (char *name)
10562 {
10563         FILE *f;
10564         if(matchMode && strcmp(name, appData.tourneyFile)) {
10565              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10566         }
10567         if(name[0] == NULLCHAR) {
10568             if(appData.participants[0])
10569                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10570             return 0;
10571         }
10572         f = fopen(name, "r");
10573         if(f) { // file exists
10574             ASSIGN(appData.tourneyFile, name);
10575             ParseArgsFromFile(f); // parse it
10576         } else {
10577             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10578             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10579                 DisplayError(_("Not enough participants"), 0);
10580                 return 0;
10581             }
10582             if(CheckPlayers(appData.participants)) return 0;
10583             ASSIGN(appData.tourneyFile, name);
10584             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10585             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10586         }
10587         fclose(f);
10588         appData.noChessProgram = FALSE;
10589         appData.clockMode = TRUE;
10590         SetGNUMode();
10591         return 1;
10592 }
10593
10594 int
10595 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10596 {
10597     char buf[MSG_SIZ], *p, *q;
10598     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10599     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10600     skip = !all && group[0]; // if group requested, we start in skip mode
10601     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10602         p = names; q = buf; header = 0;
10603         while(*p && *p != '\n') *q++ = *p++;
10604         *q = 0;
10605         if(*p == '\n') p++;
10606         if(buf[0] == '#') {
10607             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10608             depth++; // we must be entering a new group
10609             if(all) continue; // suppress printing group headers when complete list requested
10610             header = 1;
10611             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10612         }
10613         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10614         if(engineList[i]) free(engineList[i]);
10615         engineList[i] = strdup(buf);
10616         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10617         if(engineMnemonic[i]) free(engineMnemonic[i]);
10618         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10619             strcat(buf, " (");
10620             sscanf(q + 8, "%s", buf + strlen(buf));
10621             strcat(buf, ")");
10622         }
10623         engineMnemonic[i] = strdup(buf);
10624         i++;
10625     }
10626     engineList[i] = engineMnemonic[i] = NULL;
10627     return i;
10628 }
10629
10630 // following implemented as macro to avoid type limitations
10631 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10632
10633 void
10634 SwapEngines (int n)
10635 {   // swap settings for first engine and other engine (so far only some selected options)
10636     int h;
10637     char *p;
10638     if(n == 0) return;
10639     SWAP(directory, p)
10640     SWAP(chessProgram, p)
10641     SWAP(isUCI, h)
10642     SWAP(hasOwnBookUCI, h)
10643     SWAP(protocolVersion, h)
10644     SWAP(reuse, h)
10645     SWAP(scoreIsAbsolute, h)
10646     SWAP(timeOdds, h)
10647     SWAP(logo, p)
10648     SWAP(pgnName, p)
10649     SWAP(pvSAN, h)
10650     SWAP(engOptions, p)
10651     SWAP(engInitString, p)
10652     SWAP(computerString, p)
10653     SWAP(features, p)
10654     SWAP(fenOverride, p)
10655     SWAP(NPS, h)
10656     SWAP(accumulateTC, h)
10657     SWAP(host, p)
10658 }
10659
10660 int
10661 GetEngineLine (char *s, int n)
10662 {
10663     int i;
10664     char buf[MSG_SIZ];
10665     extern char *icsNames;
10666     if(!s || !*s) return 0;
10667     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
10668     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
10669     if(!mnemonic[i]) return 0;
10670     if(n == 11) return 1; // just testing if there was a match
10671     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
10672     if(n == 1) SwapEngines(n);
10673     ParseArgsFromString(buf);
10674     if(n == 1) SwapEngines(n);
10675     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
10676         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
10677         ParseArgsFromString(buf);
10678     }
10679     return 1;
10680 }
10681
10682 int
10683 SetPlayer (int player, char *p)
10684 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
10685     int i;
10686     char buf[MSG_SIZ], *engineName;
10687     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10688     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10689     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10690     if(mnemonic[i]) {
10691         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10692         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10693         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10694         ParseArgsFromString(buf);
10695     } else { // no engine with this nickname is installed!
10696         snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
10697         ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
10698         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10699         ModeHighlight();
10700         DisplayError(buf, 0);
10701         return 0;
10702     }
10703     free(engineName);
10704     return i;
10705 }
10706
10707 char *recentEngines;
10708
10709 void
10710 RecentEngineEvent (int nr)
10711 {
10712     int n;
10713 //    SwapEngines(1); // bump first to second
10714 //    ReplaceEngine(&second, 1); // and load it there
10715     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10716     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10717     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10718         ReplaceEngine(&first, 0);
10719         FloatToFront(&appData.recentEngineList, command[n]);
10720     }
10721 }
10722
10723 int
10724 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10725 {   // determine players from game number
10726     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10727
10728     if(appData.tourneyType == 0) {
10729         roundsPerCycle = (nPlayers - 1) | 1;
10730         pairingsPerRound = nPlayers / 2;
10731     } else if(appData.tourneyType > 0) {
10732         roundsPerCycle = nPlayers - appData.tourneyType;
10733         pairingsPerRound = appData.tourneyType;
10734     }
10735     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10736     gamesPerCycle = gamesPerRound * roundsPerCycle;
10737     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10738     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10739     curRound = nr / gamesPerRound; nr %= gamesPerRound;
10740     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10741     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10742     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10743
10744     if(appData.cycleSync) *syncInterval = gamesPerCycle;
10745     if(appData.roundSync) *syncInterval = gamesPerRound;
10746
10747     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10748
10749     if(appData.tourneyType == 0) {
10750         if(curPairing == (nPlayers-1)/2 ) {
10751             *whitePlayer = curRound;
10752             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10753         } else {
10754             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10755             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10756             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10757             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10758         }
10759     } else if(appData.tourneyType > 1) {
10760         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10761         *whitePlayer = curRound + appData.tourneyType;
10762     } else if(appData.tourneyType > 0) {
10763         *whitePlayer = curPairing;
10764         *blackPlayer = curRound + appData.tourneyType;
10765     }
10766
10767     // take care of white/black alternation per round.
10768     // For cycles and games this is already taken care of by default, derived from matchGame!
10769     return curRound & 1;
10770 }
10771
10772 int
10773 NextTourneyGame (int nr, int *swapColors)
10774 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10775     char *p, *q;
10776     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
10777     FILE *tf;
10778     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10779     tf = fopen(appData.tourneyFile, "r");
10780     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10781     ParseArgsFromFile(tf); fclose(tf);
10782     InitTimeControls(); // TC might be altered from tourney file
10783
10784     nPlayers = CountPlayers(appData.participants); // count participants
10785     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10786     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10787
10788     if(syncInterval) {
10789         p = q = appData.results;
10790         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10791         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10792             DisplayMessage(_("Waiting for other game(s)"),"");
10793             waitingForGame = TRUE;
10794             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10795             return 0;
10796         }
10797         waitingForGame = FALSE;
10798     }
10799
10800     if(appData.tourneyType < 0) {
10801         if(nr>=0 && !pairingReceived) {
10802             char buf[1<<16];
10803             if(pairing.pr == NoProc) {
10804                 if(!appData.pairingEngine[0]) {
10805                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10806                     return 0;
10807                 }
10808                 StartChessProgram(&pairing); // starts the pairing engine
10809             }
10810             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10811             SendToProgram(buf, &pairing);
10812             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10813             SendToProgram(buf, &pairing);
10814             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10815         }
10816         pairingReceived = 0;                              // ... so we continue here
10817         *swapColors = 0;
10818         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10819         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10820         matchGame = 1; roundNr = nr / syncInterval + 1;
10821     }
10822
10823     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10824
10825     // redefine engines, engine dir, etc.
10826     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10827     if(first.pr == NoProc) {
10828       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
10829       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10830     }
10831     if(second.pr == NoProc) {
10832       SwapEngines(1);
10833       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
10834       SwapEngines(1);         // and make that valid for second engine by swapping
10835       InitEngine(&second, 1);
10836     }
10837     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10838     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10839     return OK;
10840 }
10841
10842 void
10843 NextMatchGame ()
10844 {   // performs game initialization that does not invoke engines, and then tries to start the game
10845     int res, firstWhite, swapColors = 0;
10846     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10847     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
10848         char buf[MSG_SIZ];
10849         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
10850         if(strcmp(buf, currentDebugFile)) { // name has changed
10851             FILE *f = fopen(buf, "w");
10852             if(f) { // if opening the new file failed, just keep using the old one
10853                 ASSIGN(currentDebugFile, buf);
10854                 fclose(debugFP);
10855                 debugFP = f;
10856             }
10857             if(appData.serverFileName) {
10858                 if(serverFP) fclose(serverFP);
10859                 serverFP = fopen(appData.serverFileName, "w");
10860                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
10861                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
10862             }
10863         }
10864     }
10865     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10866     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10867     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
10868     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10869     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10870     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10871     Reset(FALSE, first.pr != NoProc);
10872     res = LoadGameOrPosition(matchGame); // setup game
10873     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10874     if(!res) return; // abort when bad game/pos file
10875     TwoMachinesEvent();
10876 }
10877
10878 void
10879 UserAdjudicationEvent (int result)
10880 {
10881     ChessMove gameResult = GameIsDrawn;
10882
10883     if( result > 0 ) {
10884         gameResult = WhiteWins;
10885     }
10886     else if( result < 0 ) {
10887         gameResult = BlackWins;
10888     }
10889
10890     if( gameMode == TwoMachinesPlay ) {
10891         GameEnds( gameResult, "User adjudication", GE_XBOARD );
10892     }
10893 }
10894
10895
10896 // [HGM] save: calculate checksum of game to make games easily identifiable
10897 int
10898 StringCheckSum (char *s)
10899 {
10900         int i = 0;
10901         if(s==NULL) return 0;
10902         while(*s) i = i*259 + *s++;
10903         return i;
10904 }
10905
10906 int
10907 GameCheckSum ()
10908 {
10909         int i, sum=0;
10910         for(i=backwardMostMove; i<forwardMostMove; i++) {
10911                 sum += pvInfoList[i].depth;
10912                 sum += StringCheckSum(parseList[i]);
10913                 sum += StringCheckSum(commentList[i]);
10914                 sum *= 261;
10915         }
10916         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10917         return sum + StringCheckSum(commentList[i]);
10918 } // end of save patch
10919
10920 void
10921 GameEnds (ChessMove result, char *resultDetails, int whosays)
10922 {
10923     GameMode nextGameMode;
10924     int isIcsGame;
10925     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10926
10927     if(endingGame) return; /* [HGM] crash: forbid recursion */
10928     endingGame = 1;
10929     if(twoBoards) { // [HGM] dual: switch back to one board
10930         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10931         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10932     }
10933     if (appData.debugMode) {
10934       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10935               result, resultDetails ? resultDetails : "(null)", whosays);
10936     }
10937
10938     fromX = fromY = killX = killY = -1; // [HGM] abort any move the user is entering. // [HGM] lion
10939
10940     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
10941
10942     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10943         /* If we are playing on ICS, the server decides when the
10944            game is over, but the engine can offer to draw, claim
10945            a draw, or resign.
10946          */
10947 #if ZIPPY
10948         if (appData.zippyPlay && first.initDone) {
10949             if (result == GameIsDrawn) {
10950                 /* In case draw still needs to be claimed */
10951                 SendToICS(ics_prefix);
10952                 SendToICS("draw\n");
10953             } else if (StrCaseStr(resultDetails, "resign")) {
10954                 SendToICS(ics_prefix);
10955                 SendToICS("resign\n");
10956             }
10957         }
10958 #endif
10959         endingGame = 0; /* [HGM] crash */
10960         return;
10961     }
10962
10963     /* If we're loading the game from a file, stop */
10964     if (whosays == GE_FILE) {
10965       (void) StopLoadGameTimer();
10966       gameFileFP = NULL;
10967     }
10968
10969     /* Cancel draw offers */
10970     first.offeredDraw = second.offeredDraw = 0;
10971
10972     /* If this is an ICS game, only ICS can really say it's done;
10973        if not, anyone can. */
10974     isIcsGame = (gameMode == IcsPlayingWhite ||
10975                  gameMode == IcsPlayingBlack ||
10976                  gameMode == IcsObserving    ||
10977                  gameMode == IcsExamining);
10978
10979     if (!isIcsGame || whosays == GE_ICS) {
10980         /* OK -- not an ICS game, or ICS said it was done */
10981         StopClocks();
10982         if (!isIcsGame && !appData.noChessProgram)
10983           SetUserThinkingEnables();
10984
10985         /* [HGM] if a machine claims the game end we verify this claim */
10986         if(gameMode == TwoMachinesPlay && appData.testClaims) {
10987             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10988                 char claimer;
10989                 ChessMove trueResult = (ChessMove) -1;
10990
10991                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
10992                                             first.twoMachinesColor[0] :
10993                                             second.twoMachinesColor[0] ;
10994
10995                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10996                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10997                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10998                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10999                 } else
11000                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
11001                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11002                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
11003                 } else
11004                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
11005                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
11006                 }
11007
11008                 // now verify win claims, but not in drop games, as we don't understand those yet
11009                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11010                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
11011                     (result == WhiteWins && claimer == 'w' ||
11012                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
11013                       if (appData.debugMode) {
11014                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
11015                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
11016                       }
11017                       if(result != trueResult) {
11018                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
11019                               result = claimer == 'w' ? BlackWins : WhiteWins;
11020                               resultDetails = buf;
11021                       }
11022                 } else
11023                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
11024                     && (forwardMostMove <= backwardMostMove ||
11025                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
11026                         (claimer=='b')==(forwardMostMove&1))
11027                                                                                   ) {
11028                       /* [HGM] verify: draws that were not flagged are false claims */
11029                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
11030                       result = claimer == 'w' ? BlackWins : WhiteWins;
11031                       resultDetails = buf;
11032                 }
11033                 /* (Claiming a loss is accepted no questions asked!) */
11034             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
11035                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
11036                 result = GameUnfinished;
11037                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
11038             }
11039             /* [HGM] bare: don't allow bare King to win */
11040             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11041                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11042                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11043                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11044                && result != GameIsDrawn)
11045             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11046                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11047                         int p = (signed char)boards[forwardMostMove][i][j] - color;
11048                         if(p >= 0 && p <= (int)WhiteKing) k++;
11049                 }
11050                 if (appData.debugMode) {
11051                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11052                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11053                 }
11054                 if(k <= 1) {
11055                         result = GameIsDrawn;
11056                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11057                         resultDetails = buf;
11058                 }
11059             }
11060         }
11061
11062
11063         if(serverMoves != NULL && !loadFlag) { char c = '=';
11064             if(result==WhiteWins) c = '+';
11065             if(result==BlackWins) c = '-';
11066             if(resultDetails != NULL)
11067                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11068         }
11069         if (resultDetails != NULL) {
11070             gameInfo.result = result;
11071             gameInfo.resultDetails = StrSave(resultDetails);
11072
11073             /* display last move only if game was not loaded from file */
11074             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11075                 DisplayMove(currentMove - 1);
11076
11077             if (forwardMostMove != 0) {
11078                 if (gameMode != PlayFromGameFile && gameMode != EditGame
11079                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11080                                                                 ) {
11081                     if (*appData.saveGameFile != NULLCHAR) {
11082                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11083                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11084                         else
11085                         SaveGameToFile(appData.saveGameFile, TRUE);
11086                     } else if (appData.autoSaveGames) {
11087                         if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11088                     }
11089                     if (*appData.savePositionFile != NULLCHAR) {
11090                         SavePositionToFile(appData.savePositionFile);
11091                     }
11092                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11093                 }
11094             }
11095
11096             /* Tell program how game ended in case it is learning */
11097             /* [HGM] Moved this to after saving the PGN, just in case */
11098             /* engine died and we got here through time loss. In that */
11099             /* case we will get a fatal error writing the pipe, which */
11100             /* would otherwise lose us the PGN.                       */
11101             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
11102             /* output during GameEnds should never be fatal anymore   */
11103             if (gameMode == MachinePlaysWhite ||
11104                 gameMode == MachinePlaysBlack ||
11105                 gameMode == TwoMachinesPlay ||
11106                 gameMode == IcsPlayingWhite ||
11107                 gameMode == IcsPlayingBlack ||
11108                 gameMode == BeginningOfGame) {
11109                 char buf[MSG_SIZ];
11110                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11111                         resultDetails);
11112                 if (first.pr != NoProc) {
11113                     SendToProgram(buf, &first);
11114                 }
11115                 if (second.pr != NoProc &&
11116                     gameMode == TwoMachinesPlay) {
11117                     SendToProgram(buf, &second);
11118                 }
11119             }
11120         }
11121
11122         if (appData.icsActive) {
11123             if (appData.quietPlay &&
11124                 (gameMode == IcsPlayingWhite ||
11125                  gameMode == IcsPlayingBlack)) {
11126                 SendToICS(ics_prefix);
11127                 SendToICS("set shout 1\n");
11128             }
11129             nextGameMode = IcsIdle;
11130             ics_user_moved = FALSE;
11131             /* clean up premove.  It's ugly when the game has ended and the
11132              * premove highlights are still on the board.
11133              */
11134             if (gotPremove) {
11135               gotPremove = FALSE;
11136               ClearPremoveHighlights();
11137               DrawPosition(FALSE, boards[currentMove]);
11138             }
11139             if (whosays == GE_ICS) {
11140                 switch (result) {
11141                 case WhiteWins:
11142                     if (gameMode == IcsPlayingWhite)
11143                         PlayIcsWinSound();
11144                     else if(gameMode == IcsPlayingBlack)
11145                         PlayIcsLossSound();
11146                     break;
11147                 case BlackWins:
11148                     if (gameMode == IcsPlayingBlack)
11149                         PlayIcsWinSound();
11150                     else if(gameMode == IcsPlayingWhite)
11151                         PlayIcsLossSound();
11152                     break;
11153                 case GameIsDrawn:
11154                     PlayIcsDrawSound();
11155                     break;
11156                 default:
11157                     PlayIcsUnfinishedSound();
11158                 }
11159             }
11160             if(appData.quitNext) { ExitEvent(0); return; }
11161         } else if (gameMode == EditGame ||
11162                    gameMode == PlayFromGameFile ||
11163                    gameMode == AnalyzeMode ||
11164                    gameMode == AnalyzeFile) {
11165             nextGameMode = gameMode;
11166         } else {
11167             nextGameMode = EndOfGame;
11168         }
11169         pausing = FALSE;
11170         ModeHighlight();
11171     } else {
11172         nextGameMode = gameMode;
11173     }
11174
11175     if (appData.noChessProgram) {
11176         gameMode = nextGameMode;
11177         ModeHighlight();
11178         endingGame = 0; /* [HGM] crash */
11179         return;
11180     }
11181
11182     if (first.reuse) {
11183         /* Put first chess program into idle state */
11184         if (first.pr != NoProc &&
11185             (gameMode == MachinePlaysWhite ||
11186              gameMode == MachinePlaysBlack ||
11187              gameMode == TwoMachinesPlay ||
11188              gameMode == IcsPlayingWhite ||
11189              gameMode == IcsPlayingBlack ||
11190              gameMode == BeginningOfGame)) {
11191             SendToProgram("force\n", &first);
11192             if (first.usePing) {
11193               char buf[MSG_SIZ];
11194               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11195               SendToProgram(buf, &first);
11196             }
11197         }
11198     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11199         /* Kill off first chess program */
11200         if (first.isr != NULL)
11201           RemoveInputSource(first.isr);
11202         first.isr = NULL;
11203
11204         if (first.pr != NoProc) {
11205             ExitAnalyzeMode();
11206             DoSleep( appData.delayBeforeQuit );
11207             SendToProgram("quit\n", &first);
11208             DoSleep( appData.delayAfterQuit );
11209             DestroyChildProcess(first.pr, first.useSigterm);
11210             first.reload = TRUE;
11211         }
11212         first.pr = NoProc;
11213     }
11214     if (second.reuse) {
11215         /* Put second chess program into idle state */
11216         if (second.pr != NoProc &&
11217             gameMode == TwoMachinesPlay) {
11218             SendToProgram("force\n", &second);
11219             if (second.usePing) {
11220               char buf[MSG_SIZ];
11221               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11222               SendToProgram(buf, &second);
11223             }
11224         }
11225     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11226         /* Kill off second chess program */
11227         if (second.isr != NULL)
11228           RemoveInputSource(second.isr);
11229         second.isr = NULL;
11230
11231         if (second.pr != NoProc) {
11232             DoSleep( appData.delayBeforeQuit );
11233             SendToProgram("quit\n", &second);
11234             DoSleep( appData.delayAfterQuit );
11235             DestroyChildProcess(second.pr, second.useSigterm);
11236             second.reload = TRUE;
11237         }
11238         second.pr = NoProc;
11239     }
11240
11241     if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11242         char resChar = '=';
11243         switch (result) {
11244         case WhiteWins:
11245           resChar = '+';
11246           if (first.twoMachinesColor[0] == 'w') {
11247             first.matchWins++;
11248           } else {
11249             second.matchWins++;
11250           }
11251           break;
11252         case BlackWins:
11253           resChar = '-';
11254           if (first.twoMachinesColor[0] == 'b') {
11255             first.matchWins++;
11256           } else {
11257             second.matchWins++;
11258           }
11259           break;
11260         case GameUnfinished:
11261           resChar = ' ';
11262         default:
11263           break;
11264         }
11265
11266         if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11267         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11268             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11269             ReserveGame(nextGame, resChar); // sets nextGame
11270             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11271             else ranking = strdup("busy"); //suppress popup when aborted but not finished
11272         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11273
11274         if (nextGame <= appData.matchGames && !abortMatch) {
11275             gameMode = nextGameMode;
11276             matchGame = nextGame; // this will be overruled in tourney mode!
11277             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11278             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11279             endingGame = 0; /* [HGM] crash */
11280             return;
11281         } else {
11282             gameMode = nextGameMode;
11283             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11284                      first.tidy, second.tidy,
11285                      first.matchWins, second.matchWins,
11286                      appData.matchGames - (first.matchWins + second.matchWins));
11287             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11288             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11289             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11290             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11291                 first.twoMachinesColor = "black\n";
11292                 second.twoMachinesColor = "white\n";
11293             } else {
11294                 first.twoMachinesColor = "white\n";
11295                 second.twoMachinesColor = "black\n";
11296             }
11297         }
11298     }
11299     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11300         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11301       ExitAnalyzeMode();
11302     gameMode = nextGameMode;
11303     ModeHighlight();
11304     endingGame = 0;  /* [HGM] crash */
11305     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11306         if(matchMode == TRUE) { // match through command line: exit with or without popup
11307             if(ranking) {
11308                 ToNrEvent(forwardMostMove);
11309                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11310                 else ExitEvent(0);
11311             } else DisplayFatalError(buf, 0, 0);
11312         } else { // match through menu; just stop, with or without popup
11313             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11314             ModeHighlight();
11315             if(ranking){
11316                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11317             } else DisplayNote(buf);
11318       }
11319       if(ranking) free(ranking);
11320     }
11321 }
11322
11323 /* Assumes program was just initialized (initString sent).
11324    Leaves program in force mode. */
11325 void
11326 FeedMovesToProgram (ChessProgramState *cps, int upto)
11327 {
11328     int i;
11329
11330     if (appData.debugMode)
11331       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11332               startedFromSetupPosition ? "position and " : "",
11333               backwardMostMove, upto, cps->which);
11334     if(currentlyInitializedVariant != gameInfo.variant) {
11335       char buf[MSG_SIZ];
11336         // [HGM] variantswitch: make engine aware of new variant
11337         if(!SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
11338                              gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, ""))
11339                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11340         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11341         SendToProgram(buf, cps);
11342         currentlyInitializedVariant = gameInfo.variant;
11343     }
11344     SendToProgram("force\n", cps);
11345     if (startedFromSetupPosition) {
11346         SendBoard(cps, backwardMostMove);
11347     if (appData.debugMode) {
11348         fprintf(debugFP, "feedMoves\n");
11349     }
11350     }
11351     for (i = backwardMostMove; i < upto; i++) {
11352         SendMoveToProgram(i, cps);
11353     }
11354 }
11355
11356
11357 int
11358 ResurrectChessProgram ()
11359 {
11360      /* The chess program may have exited.
11361         If so, restart it and feed it all the moves made so far. */
11362     static int doInit = 0;
11363
11364     if (appData.noChessProgram) return 1;
11365
11366     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11367         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11368         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11369         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11370     } else {
11371         if (first.pr != NoProc) return 1;
11372         StartChessProgram(&first);
11373     }
11374     InitChessProgram(&first, FALSE);
11375     FeedMovesToProgram(&first, currentMove);
11376
11377     if (!first.sendTime) {
11378         /* can't tell gnuchess what its clock should read,
11379            so we bow to its notion. */
11380         ResetClocks();
11381         timeRemaining[0][currentMove] = whiteTimeRemaining;
11382         timeRemaining[1][currentMove] = blackTimeRemaining;
11383     }
11384
11385     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11386                 appData.icsEngineAnalyze) && first.analysisSupport) {
11387       SendToProgram("analyze\n", &first);
11388       first.analyzing = TRUE;
11389     }
11390     return 1;
11391 }
11392
11393 /*
11394  * Button procedures
11395  */
11396 void
11397 Reset (int redraw, int init)
11398 {
11399     int i;
11400
11401     if (appData.debugMode) {
11402         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11403                 redraw, init, gameMode);
11404     }
11405     CleanupTail(); // [HGM] vari: delete any stored variations
11406     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11407     pausing = pauseExamInvalid = FALSE;
11408     startedFromSetupPosition = blackPlaysFirst = FALSE;
11409     firstMove = TRUE;
11410     whiteFlag = blackFlag = FALSE;
11411     userOfferedDraw = FALSE;
11412     hintRequested = bookRequested = FALSE;
11413     first.maybeThinking = FALSE;
11414     second.maybeThinking = FALSE;
11415     first.bookSuspend = FALSE; // [HGM] book
11416     second.bookSuspend = FALSE;
11417     thinkOutput[0] = NULLCHAR;
11418     lastHint[0] = NULLCHAR;
11419     ClearGameInfo(&gameInfo);
11420     gameInfo.variant = StringToVariant(appData.variant);
11421     if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) gameInfo.variant = VariantUnknown;
11422     ics_user_moved = ics_clock_paused = FALSE;
11423     ics_getting_history = H_FALSE;
11424     ics_gamenum = -1;
11425     white_holding[0] = black_holding[0] = NULLCHAR;
11426     ClearProgramStats();
11427     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11428
11429     ResetFrontEnd();
11430     ClearHighlights();
11431     flipView = appData.flipView;
11432     ClearPremoveHighlights();
11433     gotPremove = FALSE;
11434     alarmSounded = FALSE;
11435     killX = killY = -1; // [HGM] lion
11436
11437     GameEnds(EndOfFile, NULL, GE_PLAYER);
11438     if(appData.serverMovesName != NULL) {
11439         /* [HGM] prepare to make moves file for broadcasting */
11440         clock_t t = clock();
11441         if(serverMoves != NULL) fclose(serverMoves);
11442         serverMoves = fopen(appData.serverMovesName, "r");
11443         if(serverMoves != NULL) {
11444             fclose(serverMoves);
11445             /* delay 15 sec before overwriting, so all clients can see end */
11446             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11447         }
11448         serverMoves = fopen(appData.serverMovesName, "w");
11449     }
11450
11451     ExitAnalyzeMode();
11452     gameMode = BeginningOfGame;
11453     ModeHighlight();
11454     if(appData.icsActive) gameInfo.variant = VariantNormal;
11455     currentMove = forwardMostMove = backwardMostMove = 0;
11456     MarkTargetSquares(1);
11457     InitPosition(redraw);
11458     for (i = 0; i < MAX_MOVES; i++) {
11459         if (commentList[i] != NULL) {
11460             free(commentList[i]);
11461             commentList[i] = NULL;
11462         }
11463     }
11464     ResetClocks();
11465     timeRemaining[0][0] = whiteTimeRemaining;
11466     timeRemaining[1][0] = blackTimeRemaining;
11467
11468     if (first.pr == NoProc) {
11469         StartChessProgram(&first);
11470     }
11471     if (init) {
11472             InitChessProgram(&first, startedFromSetupPosition);
11473     }
11474     DisplayTitle("");
11475     DisplayMessage("", "");
11476     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11477     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11478     ClearMap();        // [HGM] exclude: invalidate map
11479 }
11480
11481 void
11482 AutoPlayGameLoop ()
11483 {
11484     for (;;) {
11485         if (!AutoPlayOneMove())
11486           return;
11487         if (matchMode || appData.timeDelay == 0)
11488           continue;
11489         if (appData.timeDelay < 0)
11490           return;
11491         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11492         break;
11493     }
11494 }
11495
11496 void
11497 AnalyzeNextGame()
11498 {
11499     ReloadGame(1); // next game
11500 }
11501
11502 int
11503 AutoPlayOneMove ()
11504 {
11505     int fromX, fromY, toX, toY;
11506
11507     if (appData.debugMode) {
11508       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11509     }
11510
11511     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11512       return FALSE;
11513
11514     if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
11515       pvInfoList[currentMove].depth = programStats.depth;
11516       pvInfoList[currentMove].score = programStats.score;
11517       pvInfoList[currentMove].time  = 0;
11518       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11519       else { // append analysis of final position as comment
11520         char buf[MSG_SIZ];
11521         snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
11522         AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
11523       }
11524       programStats.depth = 0;
11525     }
11526
11527     if (currentMove >= forwardMostMove) {
11528       if(gameMode == AnalyzeFile) {
11529           if(appData.loadGameIndex == -1) {
11530             GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
11531           ScheduleDelayedEvent(AnalyzeNextGame, 10);
11532           } else {
11533           ExitAnalyzeMode(); SendToProgram("force\n", &first);
11534         }
11535       }
11536 //      gameMode = EndOfGame;
11537 //      ModeHighlight();
11538
11539       /* [AS] Clear current move marker at the end of a game */
11540       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11541
11542       return FALSE;
11543     }
11544
11545     toX = moveList[currentMove][2] - AAA;
11546     toY = moveList[currentMove][3] - ONE;
11547
11548     if (moveList[currentMove][1] == '@') {
11549         if (appData.highlightLastMove) {
11550             SetHighlights(-1, -1, toX, toY);
11551         }
11552     } else {
11553         fromX = moveList[currentMove][0] - AAA;
11554         fromY = moveList[currentMove][1] - ONE;
11555
11556         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11557
11558         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11559
11560         if (appData.highlightLastMove) {
11561             SetHighlights(fromX, fromY, toX, toY);
11562         }
11563     }
11564     DisplayMove(currentMove);
11565     SendMoveToProgram(currentMove++, &first);
11566     DisplayBothClocks();
11567     DrawPosition(FALSE, boards[currentMove]);
11568     // [HGM] PV info: always display, routine tests if empty
11569     DisplayComment(currentMove - 1, commentList[currentMove]);
11570     return TRUE;
11571 }
11572
11573
11574 int
11575 LoadGameOneMove (ChessMove readAhead)
11576 {
11577     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11578     char promoChar = NULLCHAR;
11579     ChessMove moveType;
11580     char move[MSG_SIZ];
11581     char *p, *q;
11582
11583     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11584         gameMode != AnalyzeMode && gameMode != Training) {
11585         gameFileFP = NULL;
11586         return FALSE;
11587     }
11588
11589     yyboardindex = forwardMostMove;
11590     if (readAhead != EndOfFile) {
11591       moveType = readAhead;
11592     } else {
11593       if (gameFileFP == NULL)
11594           return FALSE;
11595       moveType = (ChessMove) Myylex();
11596     }
11597
11598     done = FALSE;
11599     switch (moveType) {
11600       case Comment:
11601         if (appData.debugMode)
11602           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11603         p = yy_text;
11604
11605         /* append the comment but don't display it */
11606         AppendComment(currentMove, p, FALSE);
11607         return TRUE;
11608
11609       case WhiteCapturesEnPassant:
11610       case BlackCapturesEnPassant:
11611       case WhitePromotion:
11612       case BlackPromotion:
11613       case WhiteNonPromotion:
11614       case BlackNonPromotion:
11615       case NormalMove:
11616       case FirstLeg:
11617       case WhiteKingSideCastle:
11618       case WhiteQueenSideCastle:
11619       case BlackKingSideCastle:
11620       case BlackQueenSideCastle:
11621       case WhiteKingSideCastleWild:
11622       case WhiteQueenSideCastleWild:
11623       case BlackKingSideCastleWild:
11624       case BlackQueenSideCastleWild:
11625       /* PUSH Fabien */
11626       case WhiteHSideCastleFR:
11627       case WhiteASideCastleFR:
11628       case BlackHSideCastleFR:
11629       case BlackASideCastleFR:
11630       /* POP Fabien */
11631         if (appData.debugMode)
11632           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11633         fromX = currentMoveString[0] - AAA;
11634         fromY = currentMoveString[1] - ONE;
11635         toX = currentMoveString[2] - AAA;
11636         toY = currentMoveString[3] - ONE;
11637         promoChar = currentMoveString[4];
11638         if(promoChar == ';') promoChar = NULLCHAR;
11639         break;
11640
11641       case WhiteDrop:
11642       case BlackDrop:
11643         if (appData.debugMode)
11644           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11645         fromX = moveType == WhiteDrop ?
11646           (int) CharToPiece(ToUpper(currentMoveString[0])) :
11647         (int) CharToPiece(ToLower(currentMoveString[0]));
11648         fromY = DROP_RANK;
11649         toX = currentMoveString[2] - AAA;
11650         toY = currentMoveString[3] - ONE;
11651         break;
11652
11653       case WhiteWins:
11654       case BlackWins:
11655       case GameIsDrawn:
11656       case GameUnfinished:
11657         if (appData.debugMode)
11658           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11659         p = strchr(yy_text, '{');
11660         if (p == NULL) p = strchr(yy_text, '(');
11661         if (p == NULL) {
11662             p = yy_text;
11663             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11664         } else {
11665             q = strchr(p, *p == '{' ? '}' : ')');
11666             if (q != NULL) *q = NULLCHAR;
11667             p++;
11668         }
11669         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11670         GameEnds(moveType, p, GE_FILE);
11671         done = TRUE;
11672         if (cmailMsgLoaded) {
11673             ClearHighlights();
11674             flipView = WhiteOnMove(currentMove);
11675             if (moveType == GameUnfinished) flipView = !flipView;
11676             if (appData.debugMode)
11677               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11678         }
11679         break;
11680
11681       case EndOfFile:
11682         if (appData.debugMode)
11683           fprintf(debugFP, "Parser hit end of file\n");
11684         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11685           case MT_NONE:
11686           case MT_CHECK:
11687             break;
11688           case MT_CHECKMATE:
11689           case MT_STAINMATE:
11690             if (WhiteOnMove(currentMove)) {
11691                 GameEnds(BlackWins, "Black mates", GE_FILE);
11692             } else {
11693                 GameEnds(WhiteWins, "White mates", GE_FILE);
11694             }
11695             break;
11696           case MT_STALEMATE:
11697             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11698             break;
11699         }
11700         done = TRUE;
11701         break;
11702
11703       case MoveNumberOne:
11704         if (lastLoadGameStart == GNUChessGame) {
11705             /* GNUChessGames have numbers, but they aren't move numbers */
11706             if (appData.debugMode)
11707               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11708                       yy_text, (int) moveType);
11709             return LoadGameOneMove(EndOfFile); /* tail recursion */
11710         }
11711         /* else fall thru */
11712
11713       case XBoardGame:
11714       case GNUChessGame:
11715       case PGNTag:
11716         /* Reached start of next game in file */
11717         if (appData.debugMode)
11718           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11719         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11720           case MT_NONE:
11721           case MT_CHECK:
11722             break;
11723           case MT_CHECKMATE:
11724           case MT_STAINMATE:
11725             if (WhiteOnMove(currentMove)) {
11726                 GameEnds(BlackWins, "Black mates", GE_FILE);
11727             } else {
11728                 GameEnds(WhiteWins, "White mates", GE_FILE);
11729             }
11730             break;
11731           case MT_STALEMATE:
11732             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11733             break;
11734         }
11735         done = TRUE;
11736         break;
11737
11738       case PositionDiagram:     /* should not happen; ignore */
11739       case ElapsedTime:         /* ignore */
11740       case NAG:                 /* ignore */
11741         if (appData.debugMode)
11742           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11743                   yy_text, (int) moveType);
11744         return LoadGameOneMove(EndOfFile); /* tail recursion */
11745
11746       case IllegalMove:
11747         if (appData.testLegality) {
11748             if (appData.debugMode)
11749               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11750             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11751                     (forwardMostMove / 2) + 1,
11752                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11753             DisplayError(move, 0);
11754             done = TRUE;
11755         } else {
11756             if (appData.debugMode)
11757               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11758                       yy_text, currentMoveString);
11759             fromX = currentMoveString[0] - AAA;
11760             fromY = currentMoveString[1] - ONE;
11761             toX = currentMoveString[2] - AAA;
11762             toY = currentMoveString[3] - ONE;
11763             promoChar = currentMoveString[4];
11764         }
11765         break;
11766
11767       case AmbiguousMove:
11768         if (appData.debugMode)
11769           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11770         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11771                 (forwardMostMove / 2) + 1,
11772                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11773         DisplayError(move, 0);
11774         done = TRUE;
11775         break;
11776
11777       default:
11778       case ImpossibleMove:
11779         if (appData.debugMode)
11780           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11781         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11782                 (forwardMostMove / 2) + 1,
11783                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11784         DisplayError(move, 0);
11785         done = TRUE;
11786         break;
11787     }
11788
11789     if (done) {
11790         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11791             DrawPosition(FALSE, boards[currentMove]);
11792             DisplayBothClocks();
11793             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11794               DisplayComment(currentMove - 1, commentList[currentMove]);
11795         }
11796         (void) StopLoadGameTimer();
11797         gameFileFP = NULL;
11798         cmailOldMove = forwardMostMove;
11799         return FALSE;
11800     } else {
11801         /* currentMoveString is set as a side-effect of yylex */
11802
11803         thinkOutput[0] = NULLCHAR;
11804         MakeMove(fromX, fromY, toX, toY, promoChar);
11805         killX = killY = -1; // [HGM] lion: used up
11806         currentMove = forwardMostMove;
11807         return TRUE;
11808     }
11809 }
11810
11811 /* Load the nth game from the given file */
11812 int
11813 LoadGameFromFile (char *filename, int n, char *title, int useList)
11814 {
11815     FILE *f;
11816     char buf[MSG_SIZ];
11817
11818     if (strcmp(filename, "-") == 0) {
11819         f = stdin;
11820         title = "stdin";
11821     } else {
11822         f = fopen(filename, "rb");
11823         if (f == NULL) {
11824           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
11825             DisplayError(buf, errno);
11826             return FALSE;
11827         }
11828     }
11829     if (fseek(f, 0, 0) == -1) {
11830         /* f is not seekable; probably a pipe */
11831         useList = FALSE;
11832     }
11833     if (useList && n == 0) {
11834         int error = GameListBuild(f);
11835         if (error) {
11836             DisplayError(_("Cannot build game list"), error);
11837         } else if (!ListEmpty(&gameList) &&
11838                    ((ListGame *) gameList.tailPred)->number > 1) {
11839             GameListPopUp(f, title);
11840             return TRUE;
11841         }
11842         GameListDestroy();
11843         n = 1;
11844     }
11845     if (n == 0) n = 1;
11846     return LoadGame(f, n, title, FALSE);
11847 }
11848
11849
11850 void
11851 MakeRegisteredMove ()
11852 {
11853     int fromX, fromY, toX, toY;
11854     char promoChar;
11855     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11856         switch (cmailMoveType[lastLoadGameNumber - 1]) {
11857           case CMAIL_MOVE:
11858           case CMAIL_DRAW:
11859             if (appData.debugMode)
11860               fprintf(debugFP, "Restoring %s for game %d\n",
11861                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11862
11863             thinkOutput[0] = NULLCHAR;
11864             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11865             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11866             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11867             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11868             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11869             promoChar = cmailMove[lastLoadGameNumber - 1][4];
11870             MakeMove(fromX, fromY, toX, toY, promoChar);
11871             ShowMove(fromX, fromY, toX, toY);
11872
11873             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11874               case MT_NONE:
11875               case MT_CHECK:
11876                 break;
11877
11878               case MT_CHECKMATE:
11879               case MT_STAINMATE:
11880                 if (WhiteOnMove(currentMove)) {
11881                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
11882                 } else {
11883                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
11884                 }
11885                 break;
11886
11887               case MT_STALEMATE:
11888                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11889                 break;
11890             }
11891
11892             break;
11893
11894           case CMAIL_RESIGN:
11895             if (WhiteOnMove(currentMove)) {
11896                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11897             } else {
11898                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11899             }
11900             break;
11901
11902           case CMAIL_ACCEPT:
11903             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11904             break;
11905
11906           default:
11907             break;
11908         }
11909     }
11910
11911     return;
11912 }
11913
11914 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11915 int
11916 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11917 {
11918     int retVal;
11919
11920     if (gameNumber > nCmailGames) {
11921         DisplayError(_("No more games in this message"), 0);
11922         return FALSE;
11923     }
11924     if (f == lastLoadGameFP) {
11925         int offset = gameNumber - lastLoadGameNumber;
11926         if (offset == 0) {
11927             cmailMsg[0] = NULLCHAR;
11928             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11929                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11930                 nCmailMovesRegistered--;
11931             }
11932             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11933             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11934                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11935             }
11936         } else {
11937             if (! RegisterMove()) return FALSE;
11938         }
11939     }
11940
11941     retVal = LoadGame(f, gameNumber, title, useList);
11942
11943     /* Make move registered during previous look at this game, if any */
11944     MakeRegisteredMove();
11945
11946     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11947         commentList[currentMove]
11948           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11949         DisplayComment(currentMove - 1, commentList[currentMove]);
11950     }
11951
11952     return retVal;
11953 }
11954
11955 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11956 int
11957 ReloadGame (int offset)
11958 {
11959     int gameNumber = lastLoadGameNumber + offset;
11960     if (lastLoadGameFP == NULL) {
11961         DisplayError(_("No game has been loaded yet"), 0);
11962         return FALSE;
11963     }
11964     if (gameNumber <= 0) {
11965         DisplayError(_("Can't back up any further"), 0);
11966         return FALSE;
11967     }
11968     if (cmailMsgLoaded) {
11969         return CmailLoadGame(lastLoadGameFP, gameNumber,
11970                              lastLoadGameTitle, lastLoadGameUseList);
11971     } else {
11972         return LoadGame(lastLoadGameFP, gameNumber,
11973                         lastLoadGameTitle, lastLoadGameUseList);
11974     }
11975 }
11976
11977 int keys[EmptySquare+1];
11978
11979 int
11980 PositionMatches (Board b1, Board b2)
11981 {
11982     int r, f, sum=0;
11983     switch(appData.searchMode) {
11984         case 1: return CompareWithRights(b1, b2);
11985         case 2:
11986             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11987                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11988             }
11989             return TRUE;
11990         case 3:
11991             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11992               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11993                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11994             }
11995             return sum==0;
11996         case 4:
11997             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11998                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11999             }
12000             return sum==0;
12001     }
12002     return TRUE;
12003 }
12004
12005 #define Q_PROMO  4
12006 #define Q_EP     3
12007 #define Q_BCASTL 2
12008 #define Q_WCASTL 1
12009
12010 int pieceList[256], quickBoard[256];
12011 ChessSquare pieceType[256] = { EmptySquare };
12012 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
12013 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
12014 int soughtTotal, turn;
12015 Boolean epOK, flipSearch;
12016
12017 typedef struct {
12018     unsigned char piece, to;
12019 } Move;
12020
12021 #define DSIZE (250000)
12022
12023 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
12024 Move *moveDatabase = initialSpace;
12025 unsigned int movePtr, dataSize = DSIZE;
12026
12027 int
12028 MakePieceList (Board board, int *counts)
12029 {
12030     int r, f, n=Q_PROMO, total=0;
12031     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
12032     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12033         int sq = f + (r<<4);
12034         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
12035             quickBoard[sq] = ++n;
12036             pieceList[n] = sq;
12037             pieceType[n] = board[r][f];
12038             counts[board[r][f]]++;
12039             if(board[r][f] == WhiteKing) pieceList[1] = n; else
12040             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
12041             total++;
12042         }
12043     }
12044     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12045     return total;
12046 }
12047
12048 void
12049 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12050 {
12051     int sq = fromX + (fromY<<4);
12052     int piece = quickBoard[sq];
12053     quickBoard[sq] = 0;
12054     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12055     if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12056         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
12057         moveDatabase[movePtr++].piece = Q_WCASTL;
12058         quickBoard[sq] = piece;
12059         piece = quickBoard[from]; quickBoard[from] = 0;
12060         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12061     } else
12062     if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12063         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12064         moveDatabase[movePtr++].piece = Q_BCASTL;
12065         quickBoard[sq] = piece;
12066         piece = quickBoard[from]; quickBoard[from] = 0;
12067         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12068     } else
12069     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12070         quickBoard[(fromY<<4)+toX] = 0;
12071         moveDatabase[movePtr].piece = Q_EP;
12072         moveDatabase[movePtr++].to = (fromY<<4)+toX;
12073         moveDatabase[movePtr].to = sq;
12074     } else
12075     if(promoPiece != pieceType[piece]) {
12076         moveDatabase[movePtr++].piece = Q_PROMO;
12077         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12078     }
12079     moveDatabase[movePtr].piece = piece;
12080     quickBoard[sq] = piece;
12081     movePtr++;
12082 }
12083
12084 int
12085 PackGame (Board board)
12086 {
12087     Move *newSpace = NULL;
12088     moveDatabase[movePtr].piece = 0; // terminate previous game
12089     if(movePtr > dataSize) {
12090         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12091         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12092         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12093         if(newSpace) {
12094             int i;
12095             Move *p = moveDatabase, *q = newSpace;
12096             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
12097             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12098             moveDatabase = newSpace;
12099         } else { // calloc failed, we must be out of memory. Too bad...
12100             dataSize = 0; // prevent calloc events for all subsequent games
12101             return 0;     // and signal this one isn't cached
12102         }
12103     }
12104     movePtr++;
12105     MakePieceList(board, counts);
12106     return movePtr;
12107 }
12108
12109 int
12110 QuickCompare (Board board, int *minCounts, int *maxCounts)
12111 {   // compare according to search mode
12112     int r, f;
12113     switch(appData.searchMode)
12114     {
12115       case 1: // exact position match
12116         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12117         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12118             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12119         }
12120         break;
12121       case 2: // can have extra material on empty squares
12122         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12123             if(board[r][f] == EmptySquare) continue;
12124             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12125         }
12126         break;
12127       case 3: // material with exact Pawn structure
12128         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12129             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12130             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12131         } // fall through to material comparison
12132       case 4: // exact material
12133         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12134         break;
12135       case 6: // material range with given imbalance
12136         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12137         // fall through to range comparison
12138       case 5: // material range
12139         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12140     }
12141     return TRUE;
12142 }
12143
12144 int
12145 QuickScan (Board board, Move *move)
12146 {   // reconstruct game,and compare all positions in it
12147     int cnt=0, stretch=0, total = MakePieceList(board, counts);
12148     do {
12149         int piece = move->piece;
12150         int to = move->to, from = pieceList[piece];
12151         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
12152           if(!piece) return -1;
12153           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
12154             piece = (++move)->piece;
12155             from = pieceList[piece];
12156             counts[pieceType[piece]]--;
12157             pieceType[piece] = (ChessSquare) move->to;
12158             counts[move->to]++;
12159           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
12160             counts[pieceType[quickBoard[to]]]--;
12161             quickBoard[to] = 0; total--;
12162             move++;
12163             continue;
12164           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
12165             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
12166             from  = pieceList[piece]; // so this must be King
12167             quickBoard[from] = 0;
12168             pieceList[piece] = to;
12169             from = pieceList[(++move)->piece]; // for FRC this has to be done here
12170             quickBoard[from] = 0; // rook
12171             quickBoard[to] = piece;
12172             to = move->to; piece = move->piece;
12173             goto aftercastle;
12174           }
12175         }
12176         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12177         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
12178         quickBoard[from] = 0;
12179       aftercastle:
12180         quickBoard[to] = piece;
12181         pieceList[piece] = to;
12182         cnt++; turn ^= 3;
12183         if(QuickCompare(soughtBoard, minSought, maxSought) ||
12184            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12185            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12186                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12187           ) {
12188             static int lastCounts[EmptySquare+1];
12189             int i;
12190             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12191             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12192         } else stretch = 0;
12193         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
12194         move++;
12195     } while(1);
12196 }
12197
12198 void
12199 InitSearch ()
12200 {
12201     int r, f;
12202     flipSearch = FALSE;
12203     CopyBoard(soughtBoard, boards[currentMove]);
12204     soughtTotal = MakePieceList(soughtBoard, maxSought);
12205     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12206     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12207     CopyBoard(reverseBoard, boards[currentMove]);
12208     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12209         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12210         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12211         reverseBoard[r][f] = piece;
12212     }
12213     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12214     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12215     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12216                  || (boards[currentMove][CASTLING][2] == NoRights ||
12217                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12218                  && (boards[currentMove][CASTLING][5] == NoRights ||
12219                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12220       ) {
12221         flipSearch = TRUE;
12222         CopyBoard(flipBoard, soughtBoard);
12223         CopyBoard(rotateBoard, reverseBoard);
12224         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12225             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
12226             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12227         }
12228     }
12229     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12230     if(appData.searchMode >= 5) {
12231         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12232         MakePieceList(soughtBoard, minSought);
12233         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12234     }
12235     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12236         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12237 }
12238
12239 GameInfo dummyInfo;
12240 static int creatingBook;
12241
12242 int
12243 GameContainsPosition (FILE *f, ListGame *lg)
12244 {
12245     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12246     int fromX, fromY, toX, toY;
12247     char promoChar;
12248     static int initDone=FALSE;
12249
12250     // weed out games based on numerical tag comparison
12251     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12252     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12253     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12254     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12255     if(!initDone) {
12256         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12257         initDone = TRUE;
12258     }
12259     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
12260     else CopyBoard(boards[scratch], initialPosition); // default start position
12261     if(lg->moves) {
12262         turn = btm + 1;
12263         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12264         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12265     }
12266     if(btm) plyNr++;
12267     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12268     fseek(f, lg->offset, 0);
12269     yynewfile(f);
12270     while(1) {
12271         yyboardindex = scratch;
12272         quickFlag = plyNr+1;
12273         next = Myylex();
12274         quickFlag = 0;
12275         switch(next) {
12276             case PGNTag:
12277                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12278             default:
12279                 continue;
12280
12281             case XBoardGame:
12282             case GNUChessGame:
12283                 if(plyNr) return -1; // after we have seen moves, this is for new game
12284               continue;
12285
12286             case AmbiguousMove: // we cannot reconstruct the game beyond these two
12287             case ImpossibleMove:
12288             case WhiteWins: // game ends here with these four
12289             case BlackWins:
12290             case GameIsDrawn:
12291             case GameUnfinished:
12292                 return -1;
12293
12294             case IllegalMove:
12295                 if(appData.testLegality) return -1;
12296             case WhiteCapturesEnPassant:
12297             case BlackCapturesEnPassant:
12298             case WhitePromotion:
12299             case BlackPromotion:
12300             case WhiteNonPromotion:
12301             case BlackNonPromotion:
12302             case NormalMove:
12303             case FirstLeg:
12304             case WhiteKingSideCastle:
12305             case WhiteQueenSideCastle:
12306             case BlackKingSideCastle:
12307             case BlackQueenSideCastle:
12308             case WhiteKingSideCastleWild:
12309             case WhiteQueenSideCastleWild:
12310             case BlackKingSideCastleWild:
12311             case BlackQueenSideCastleWild:
12312             case WhiteHSideCastleFR:
12313             case WhiteASideCastleFR:
12314             case BlackHSideCastleFR:
12315             case BlackASideCastleFR:
12316                 fromX = currentMoveString[0] - AAA;
12317                 fromY = currentMoveString[1] - ONE;
12318                 toX = currentMoveString[2] - AAA;
12319                 toY = currentMoveString[3] - ONE;
12320                 promoChar = currentMoveString[4];
12321                 break;
12322             case WhiteDrop:
12323             case BlackDrop:
12324                 fromX = next == WhiteDrop ?
12325                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
12326                   (int) CharToPiece(ToLower(currentMoveString[0]));
12327                 fromY = DROP_RANK;
12328                 toX = currentMoveString[2] - AAA;
12329                 toY = currentMoveString[3] - ONE;
12330                 promoChar = 0;
12331                 break;
12332         }
12333         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12334         plyNr++;
12335         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12336         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12337         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12338         if(appData.findMirror) {
12339             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12340             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12341         }
12342     }
12343 }
12344
12345 /* Load the nth game from open file f */
12346 int
12347 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12348 {
12349     ChessMove cm;
12350     char buf[MSG_SIZ];
12351     int gn = gameNumber;
12352     ListGame *lg = NULL;
12353     int numPGNTags = 0;
12354     int err, pos = -1;
12355     GameMode oldGameMode;
12356     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12357
12358     if (appData.debugMode)
12359         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12360
12361     if (gameMode == Training )
12362         SetTrainingModeOff();
12363
12364     oldGameMode = gameMode;
12365     if (gameMode != BeginningOfGame) {
12366       Reset(FALSE, TRUE);
12367     }
12368     killX = killY = -1; // [HGM] lion: in case we did not Reset
12369
12370     gameFileFP = f;
12371     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12372         fclose(lastLoadGameFP);
12373     }
12374
12375     if (useList) {
12376         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12377
12378         if (lg) {
12379             fseek(f, lg->offset, 0);
12380             GameListHighlight(gameNumber);
12381             pos = lg->position;
12382             gn = 1;
12383         }
12384         else {
12385             if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
12386               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12387             else
12388             DisplayError(_("Game number out of range"), 0);
12389             return FALSE;
12390         }
12391     } else {
12392         GameListDestroy();
12393         if (fseek(f, 0, 0) == -1) {
12394             if (f == lastLoadGameFP ?
12395                 gameNumber == lastLoadGameNumber + 1 :
12396                 gameNumber == 1) {
12397                 gn = 1;
12398             } else {
12399                 DisplayError(_("Can't seek on game file"), 0);
12400                 return FALSE;
12401             }
12402         }
12403     }
12404     lastLoadGameFP = f;
12405     lastLoadGameNumber = gameNumber;
12406     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12407     lastLoadGameUseList = useList;
12408
12409     yynewfile(f);
12410
12411     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12412       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12413                 lg->gameInfo.black);
12414             DisplayTitle(buf);
12415     } else if (*title != NULLCHAR) {
12416         if (gameNumber > 1) {
12417           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12418             DisplayTitle(buf);
12419         } else {
12420             DisplayTitle(title);
12421         }
12422     }
12423
12424     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12425         gameMode = PlayFromGameFile;
12426         ModeHighlight();
12427     }
12428
12429     currentMove = forwardMostMove = backwardMostMove = 0;
12430     CopyBoard(boards[0], initialPosition);
12431     StopClocks();
12432
12433     /*
12434      * Skip the first gn-1 games in the file.
12435      * Also skip over anything that precedes an identifiable
12436      * start of game marker, to avoid being confused by
12437      * garbage at the start of the file.  Currently
12438      * recognized start of game markers are the move number "1",
12439      * the pattern "gnuchess .* game", the pattern
12440      * "^[#;%] [^ ]* game file", and a PGN tag block.
12441      * A game that starts with one of the latter two patterns
12442      * will also have a move number 1, possibly
12443      * following a position diagram.
12444      * 5-4-02: Let's try being more lenient and allowing a game to
12445      * start with an unnumbered move.  Does that break anything?
12446      */
12447     cm = lastLoadGameStart = EndOfFile;
12448     while (gn > 0) {
12449         yyboardindex = forwardMostMove;
12450         cm = (ChessMove) Myylex();
12451         switch (cm) {
12452           case EndOfFile:
12453             if (cmailMsgLoaded) {
12454                 nCmailGames = CMAIL_MAX_GAMES - gn;
12455             } else {
12456                 Reset(TRUE, TRUE);
12457                 DisplayError(_("Game not found in file"), 0);
12458             }
12459             return FALSE;
12460
12461           case GNUChessGame:
12462           case XBoardGame:
12463             gn--;
12464             lastLoadGameStart = cm;
12465             break;
12466
12467           case MoveNumberOne:
12468             switch (lastLoadGameStart) {
12469               case GNUChessGame:
12470               case XBoardGame:
12471               case PGNTag:
12472                 break;
12473               case MoveNumberOne:
12474               case EndOfFile:
12475                 gn--;           /* count this game */
12476                 lastLoadGameStart = cm;
12477                 break;
12478               default:
12479                 /* impossible */
12480                 break;
12481             }
12482             break;
12483
12484           case PGNTag:
12485             switch (lastLoadGameStart) {
12486               case GNUChessGame:
12487               case PGNTag:
12488               case MoveNumberOne:
12489               case EndOfFile:
12490                 gn--;           /* count this game */
12491                 lastLoadGameStart = cm;
12492                 break;
12493               case XBoardGame:
12494                 lastLoadGameStart = cm; /* game counted already */
12495                 break;
12496               default:
12497                 /* impossible */
12498                 break;
12499             }
12500             if (gn > 0) {
12501                 do {
12502                     yyboardindex = forwardMostMove;
12503                     cm = (ChessMove) Myylex();
12504                 } while (cm == PGNTag || cm == Comment);
12505             }
12506             break;
12507
12508           case WhiteWins:
12509           case BlackWins:
12510           case GameIsDrawn:
12511             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
12512                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
12513                     != CMAIL_OLD_RESULT) {
12514                     nCmailResults ++ ;
12515                     cmailResult[  CMAIL_MAX_GAMES
12516                                 - gn - 1] = CMAIL_OLD_RESULT;
12517                 }
12518             }
12519             break;
12520
12521           case NormalMove:
12522           case FirstLeg:
12523             /* Only a NormalMove can be at the start of a game
12524              * without a position diagram. */
12525             if (lastLoadGameStart == EndOfFile ) {
12526               gn--;
12527               lastLoadGameStart = MoveNumberOne;
12528             }
12529             break;
12530
12531           default:
12532             break;
12533         }
12534     }
12535
12536     if (appData.debugMode)
12537       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
12538
12539     if (cm == XBoardGame) {
12540         /* Skip any header junk before position diagram and/or move 1 */
12541         for (;;) {
12542             yyboardindex = forwardMostMove;
12543             cm = (ChessMove) Myylex();
12544
12545             if (cm == EndOfFile ||
12546                 cm == GNUChessGame || cm == XBoardGame) {
12547                 /* Empty game; pretend end-of-file and handle later */
12548                 cm = EndOfFile;
12549                 break;
12550             }
12551
12552             if (cm == MoveNumberOne || cm == PositionDiagram ||
12553                 cm == PGNTag || cm == Comment)
12554               break;
12555         }
12556     } else if (cm == GNUChessGame) {
12557         if (gameInfo.event != NULL) {
12558             free(gameInfo.event);
12559         }
12560         gameInfo.event = StrSave(yy_text);
12561     }
12562
12563     startedFromSetupPosition = FALSE;
12564     while (cm == PGNTag) {
12565         if (appData.debugMode)
12566           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
12567         err = ParsePGNTag(yy_text, &gameInfo);
12568         if (!err) numPGNTags++;
12569
12570         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
12571         if(gameInfo.variant != oldVariant) {
12572             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
12573             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
12574             InitPosition(TRUE);
12575             oldVariant = gameInfo.variant;
12576             if (appData.debugMode)
12577               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
12578         }
12579
12580
12581         if (gameInfo.fen != NULL) {
12582           Board initial_position;
12583           startedFromSetupPosition = TRUE;
12584           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
12585             Reset(TRUE, TRUE);
12586             DisplayError(_("Bad FEN position in file"), 0);
12587             return FALSE;
12588           }
12589           CopyBoard(boards[0], initial_position);
12590           if (blackPlaysFirst) {
12591             currentMove = forwardMostMove = backwardMostMove = 1;
12592             CopyBoard(boards[1], initial_position);
12593             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12594             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12595             timeRemaining[0][1] = whiteTimeRemaining;
12596             timeRemaining[1][1] = blackTimeRemaining;
12597             if (commentList[0] != NULL) {
12598               commentList[1] = commentList[0];
12599               commentList[0] = NULL;
12600             }
12601           } else {
12602             currentMove = forwardMostMove = backwardMostMove = 0;
12603           }
12604           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12605           {   int i;
12606               initialRulePlies = FENrulePlies;
12607               for( i=0; i< nrCastlingRights; i++ )
12608                   initialRights[i] = initial_position[CASTLING][i];
12609           }
12610           yyboardindex = forwardMostMove;
12611           free(gameInfo.fen);
12612           gameInfo.fen = NULL;
12613         }
12614
12615         yyboardindex = forwardMostMove;
12616         cm = (ChessMove) Myylex();
12617
12618         /* Handle comments interspersed among the tags */
12619         while (cm == Comment) {
12620             char *p;
12621             if (appData.debugMode)
12622               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12623             p = yy_text;
12624             AppendComment(currentMove, p, FALSE);
12625             yyboardindex = forwardMostMove;
12626             cm = (ChessMove) Myylex();
12627         }
12628     }
12629
12630     /* don't rely on existence of Event tag since if game was
12631      * pasted from clipboard the Event tag may not exist
12632      */
12633     if (numPGNTags > 0){
12634         char *tags;
12635         if (gameInfo.variant == VariantNormal) {
12636           VariantClass v = StringToVariant(gameInfo.event);
12637           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12638           if(v < VariantShogi) gameInfo.variant = v;
12639         }
12640         if (!matchMode) {
12641           if( appData.autoDisplayTags ) {
12642             tags = PGNTags(&gameInfo);
12643             TagsPopUp(tags, CmailMsg());
12644             free(tags);
12645           }
12646         }
12647     } else {
12648         /* Make something up, but don't display it now */
12649         SetGameInfo();
12650         TagsPopDown();
12651     }
12652
12653     if (cm == PositionDiagram) {
12654         int i, j;
12655         char *p;
12656         Board initial_position;
12657
12658         if (appData.debugMode)
12659           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12660
12661         if (!startedFromSetupPosition) {
12662             p = yy_text;
12663             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12664               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12665                 switch (*p) {
12666                   case '{':
12667                   case '[':
12668                   case '-':
12669                   case ' ':
12670                   case '\t':
12671                   case '\n':
12672                   case '\r':
12673                     break;
12674                   default:
12675                     initial_position[i][j++] = CharToPiece(*p);
12676                     break;
12677                 }
12678             while (*p == ' ' || *p == '\t' ||
12679                    *p == '\n' || *p == '\r') p++;
12680
12681             if (strncmp(p, "black", strlen("black"))==0)
12682               blackPlaysFirst = TRUE;
12683             else
12684               blackPlaysFirst = FALSE;
12685             startedFromSetupPosition = TRUE;
12686
12687             CopyBoard(boards[0], initial_position);
12688             if (blackPlaysFirst) {
12689                 currentMove = forwardMostMove = backwardMostMove = 1;
12690                 CopyBoard(boards[1], initial_position);
12691                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12692                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12693                 timeRemaining[0][1] = whiteTimeRemaining;
12694                 timeRemaining[1][1] = blackTimeRemaining;
12695                 if (commentList[0] != NULL) {
12696                     commentList[1] = commentList[0];
12697                     commentList[0] = NULL;
12698                 }
12699             } else {
12700                 currentMove = forwardMostMove = backwardMostMove = 0;
12701             }
12702         }
12703         yyboardindex = forwardMostMove;
12704         cm = (ChessMove) Myylex();
12705     }
12706
12707   if(!creatingBook) {
12708     if (first.pr == NoProc) {
12709         StartChessProgram(&first);
12710     }
12711     InitChessProgram(&first, FALSE);
12712     SendToProgram("force\n", &first);
12713     if (startedFromSetupPosition) {
12714         SendBoard(&first, forwardMostMove);
12715     if (appData.debugMode) {
12716         fprintf(debugFP, "Load Game\n");
12717     }
12718         DisplayBothClocks();
12719     }
12720   }
12721
12722     /* [HGM] server: flag to write setup moves in broadcast file as one */
12723     loadFlag = appData.suppressLoadMoves;
12724
12725     while (cm == Comment) {
12726         char *p;
12727         if (appData.debugMode)
12728           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12729         p = yy_text;
12730         AppendComment(currentMove, p, FALSE);
12731         yyboardindex = forwardMostMove;
12732         cm = (ChessMove) Myylex();
12733     }
12734
12735     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12736         cm == WhiteWins || cm == BlackWins ||
12737         cm == GameIsDrawn || cm == GameUnfinished) {
12738         DisplayMessage("", _("No moves in game"));
12739         if (cmailMsgLoaded) {
12740             if (appData.debugMode)
12741               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12742             ClearHighlights();
12743             flipView = FALSE;
12744         }
12745         DrawPosition(FALSE, boards[currentMove]);
12746         DisplayBothClocks();
12747         gameMode = EditGame;
12748         ModeHighlight();
12749         gameFileFP = NULL;
12750         cmailOldMove = 0;
12751         return TRUE;
12752     }
12753
12754     // [HGM] PV info: routine tests if comment empty
12755     if (!matchMode && (pausing || appData.timeDelay != 0)) {
12756         DisplayComment(currentMove - 1, commentList[currentMove]);
12757     }
12758     if (!matchMode && appData.timeDelay != 0)
12759       DrawPosition(FALSE, boards[currentMove]);
12760
12761     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12762       programStats.ok_to_send = 1;
12763     }
12764
12765     /* if the first token after the PGN tags is a move
12766      * and not move number 1, retrieve it from the parser
12767      */
12768     if (cm != MoveNumberOne)
12769         LoadGameOneMove(cm);
12770
12771     /* load the remaining moves from the file */
12772     while (LoadGameOneMove(EndOfFile)) {
12773       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12774       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12775     }
12776
12777     /* rewind to the start of the game */
12778     currentMove = backwardMostMove;
12779
12780     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12781
12782     if (oldGameMode == AnalyzeFile) {
12783       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
12784       AnalyzeFileEvent();
12785     } else
12786     if (oldGameMode == AnalyzeMode) {
12787       AnalyzeFileEvent();
12788     }
12789
12790     if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
12791         long int w, b; // [HGM] adjourn: restore saved clock times
12792         char *p = strstr(gameInfo.resultDetails, "(Clocks:");
12793         if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
12794             timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
12795             timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
12796         }
12797     }
12798
12799     if(creatingBook) return TRUE;
12800     if (!matchMode && pos > 0) {
12801         ToNrEvent(pos); // [HGM] no autoplay if selected on position
12802     } else
12803     if (matchMode || appData.timeDelay == 0) {
12804       ToEndEvent();
12805     } else if (appData.timeDelay > 0) {
12806       AutoPlayGameLoop();
12807     }
12808
12809     if (appData.debugMode)
12810         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
12811
12812     loadFlag = 0; /* [HGM] true game starts */
12813     return TRUE;
12814 }
12815
12816 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
12817 int
12818 ReloadPosition (int offset)
12819 {
12820     int positionNumber = lastLoadPositionNumber + offset;
12821     if (lastLoadPositionFP == NULL) {
12822         DisplayError(_("No position has been loaded yet"), 0);
12823         return FALSE;
12824     }
12825     if (positionNumber <= 0) {
12826         DisplayError(_("Can't back up any further"), 0);
12827         return FALSE;
12828     }
12829     return LoadPosition(lastLoadPositionFP, positionNumber,
12830                         lastLoadPositionTitle);
12831 }
12832
12833 /* Load the nth position from the given file */
12834 int
12835 LoadPositionFromFile (char *filename, int n, char *title)
12836 {
12837     FILE *f;
12838     char buf[MSG_SIZ];
12839
12840     if (strcmp(filename, "-") == 0) {
12841         return LoadPosition(stdin, n, "stdin");
12842     } else {
12843         f = fopen(filename, "rb");
12844         if (f == NULL) {
12845             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12846             DisplayError(buf, errno);
12847             return FALSE;
12848         } else {
12849             return LoadPosition(f, n, title);
12850         }
12851     }
12852 }
12853
12854 /* Load the nth position from the given open file, and close it */
12855 int
12856 LoadPosition (FILE *f, int positionNumber, char *title)
12857 {
12858     char *p, line[MSG_SIZ];
12859     Board initial_position;
12860     int i, j, fenMode, pn;
12861
12862     if (gameMode == Training )
12863         SetTrainingModeOff();
12864
12865     if (gameMode != BeginningOfGame) {
12866         Reset(FALSE, TRUE);
12867     }
12868     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12869         fclose(lastLoadPositionFP);
12870     }
12871     if (positionNumber == 0) positionNumber = 1;
12872     lastLoadPositionFP = f;
12873     lastLoadPositionNumber = positionNumber;
12874     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12875     if (first.pr == NoProc && !appData.noChessProgram) {
12876       StartChessProgram(&first);
12877       InitChessProgram(&first, FALSE);
12878     }
12879     pn = positionNumber;
12880     if (positionNumber < 0) {
12881         /* Negative position number means to seek to that byte offset */
12882         if (fseek(f, -positionNumber, 0) == -1) {
12883             DisplayError(_("Can't seek on position file"), 0);
12884             return FALSE;
12885         };
12886         pn = 1;
12887     } else {
12888         if (fseek(f, 0, 0) == -1) {
12889             if (f == lastLoadPositionFP ?
12890                 positionNumber == lastLoadPositionNumber + 1 :
12891                 positionNumber == 1) {
12892                 pn = 1;
12893             } else {
12894                 DisplayError(_("Can't seek on position file"), 0);
12895                 return FALSE;
12896             }
12897         }
12898     }
12899     /* See if this file is FEN or old-style xboard */
12900     if (fgets(line, MSG_SIZ, f) == NULL) {
12901         DisplayError(_("Position not found in file"), 0);
12902         return FALSE;
12903     }
12904     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12905     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12906
12907     if (pn >= 2) {
12908         if (fenMode || line[0] == '#') pn--;
12909         while (pn > 0) {
12910             /* skip positions before number pn */
12911             if (fgets(line, MSG_SIZ, f) == NULL) {
12912                 Reset(TRUE, TRUE);
12913                 DisplayError(_("Position not found in file"), 0);
12914                 return FALSE;
12915             }
12916             if (fenMode || line[0] == '#') pn--;
12917         }
12918     }
12919
12920     if (fenMode) {
12921         if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
12922             DisplayError(_("Bad FEN position in file"), 0);
12923             return FALSE;
12924         }
12925     } else {
12926         (void) fgets(line, MSG_SIZ, f);
12927         (void) fgets(line, MSG_SIZ, f);
12928
12929         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12930             (void) fgets(line, MSG_SIZ, f);
12931             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12932                 if (*p == ' ')
12933                   continue;
12934                 initial_position[i][j++] = CharToPiece(*p);
12935             }
12936         }
12937
12938         blackPlaysFirst = FALSE;
12939         if (!feof(f)) {
12940             (void) fgets(line, MSG_SIZ, f);
12941             if (strncmp(line, "black", strlen("black"))==0)
12942               blackPlaysFirst = TRUE;
12943         }
12944     }
12945     startedFromSetupPosition = TRUE;
12946
12947     CopyBoard(boards[0], initial_position);
12948     if (blackPlaysFirst) {
12949         currentMove = forwardMostMove = backwardMostMove = 1;
12950         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12951         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12952         CopyBoard(boards[1], initial_position);
12953         DisplayMessage("", _("Black to play"));
12954     } else {
12955         currentMove = forwardMostMove = backwardMostMove = 0;
12956         DisplayMessage("", _("White to play"));
12957     }
12958     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12959     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12960         SendToProgram("force\n", &first);
12961         SendBoard(&first, forwardMostMove);
12962     }
12963     if (appData.debugMode) {
12964 int i, j;
12965   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12966   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12967         fprintf(debugFP, "Load Position\n");
12968     }
12969
12970     if (positionNumber > 1) {
12971       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12972         DisplayTitle(line);
12973     } else {
12974         DisplayTitle(title);
12975     }
12976     gameMode = EditGame;
12977     ModeHighlight();
12978     ResetClocks();
12979     timeRemaining[0][1] = whiteTimeRemaining;
12980     timeRemaining[1][1] = blackTimeRemaining;
12981     DrawPosition(FALSE, boards[currentMove]);
12982
12983     return TRUE;
12984 }
12985
12986
12987 void
12988 CopyPlayerNameIntoFileName (char **dest, char *src)
12989 {
12990     while (*src != NULLCHAR && *src != ',') {
12991         if (*src == ' ') {
12992             *(*dest)++ = '_';
12993             src++;
12994         } else {
12995             *(*dest)++ = *src++;
12996         }
12997     }
12998 }
12999
13000 char *
13001 DefaultFileName (char *ext)
13002 {
13003     static char def[MSG_SIZ];
13004     char *p;
13005
13006     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
13007         p = def;
13008         CopyPlayerNameIntoFileName(&p, gameInfo.white);
13009         *p++ = '-';
13010         CopyPlayerNameIntoFileName(&p, gameInfo.black);
13011         *p++ = '.';
13012         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
13013     } else {
13014         def[0] = NULLCHAR;
13015     }
13016     return def;
13017 }
13018
13019 /* Save the current game to the given file */
13020 int
13021 SaveGameToFile (char *filename, int append)
13022 {
13023     FILE *f;
13024     char buf[MSG_SIZ];
13025     int result, i, t,tot=0;
13026
13027     if (strcmp(filename, "-") == 0) {
13028         return SaveGame(stdout, 0, NULL);
13029     } else {
13030         for(i=0; i<10; i++) { // upto 10 tries
13031              f = fopen(filename, append ? "a" : "w");
13032              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
13033              if(f || errno != 13) break;
13034              DoSleep(t = 5 + random()%11); // wait 5-15 msec
13035              tot += t;
13036         }
13037         if (f == NULL) {
13038             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13039             DisplayError(buf, errno);
13040             return FALSE;
13041         } else {
13042             safeStrCpy(buf, lastMsg, MSG_SIZ);
13043             DisplayMessage(_("Waiting for access to save file"), "");
13044             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
13045             DisplayMessage(_("Saving game"), "");
13046             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
13047             result = SaveGame(f, 0, NULL);
13048             DisplayMessage(buf, "");
13049             return result;
13050         }
13051     }
13052 }
13053
13054 char *
13055 SavePart (char *str)
13056 {
13057     static char buf[MSG_SIZ];
13058     char *p;
13059
13060     p = strchr(str, ' ');
13061     if (p == NULL) return str;
13062     strncpy(buf, str, p - str);
13063     buf[p - str] = NULLCHAR;
13064     return buf;
13065 }
13066
13067 #define PGN_MAX_LINE 75
13068
13069 #define PGN_SIDE_WHITE  0
13070 #define PGN_SIDE_BLACK  1
13071
13072 static int
13073 FindFirstMoveOutOfBook (int side)
13074 {
13075     int result = -1;
13076
13077     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13078         int index = backwardMostMove;
13079         int has_book_hit = 0;
13080
13081         if( (index % 2) != side ) {
13082             index++;
13083         }
13084
13085         while( index < forwardMostMove ) {
13086             /* Check to see if engine is in book */
13087             int depth = pvInfoList[index].depth;
13088             int score = pvInfoList[index].score;
13089             int in_book = 0;
13090
13091             if( depth <= 2 ) {
13092                 in_book = 1;
13093             }
13094             else if( score == 0 && depth == 63 ) {
13095                 in_book = 1; /* Zappa */
13096             }
13097             else if( score == 2 && depth == 99 ) {
13098                 in_book = 1; /* Abrok */
13099             }
13100
13101             has_book_hit += in_book;
13102
13103             if( ! in_book ) {
13104                 result = index;
13105
13106                 break;
13107             }
13108
13109             index += 2;
13110         }
13111     }
13112
13113     return result;
13114 }
13115
13116 void
13117 GetOutOfBookInfo (char * buf)
13118 {
13119     int oob[2];
13120     int i;
13121     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13122
13123     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13124     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13125
13126     *buf = '\0';
13127
13128     if( oob[0] >= 0 || oob[1] >= 0 ) {
13129         for( i=0; i<2; i++ ) {
13130             int idx = oob[i];
13131
13132             if( idx >= 0 ) {
13133                 if( i > 0 && oob[0] >= 0 ) {
13134                     strcat( buf, "   " );
13135                 }
13136
13137                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
13138                 sprintf( buf+strlen(buf), "%s%.2f",
13139                     pvInfoList[idx].score >= 0 ? "+" : "",
13140                     pvInfoList[idx].score / 100.0 );
13141             }
13142         }
13143     }
13144 }
13145
13146 /* Save game in PGN style and close the file */
13147 int
13148 SaveGamePGN (FILE *f)
13149 {
13150     int i, offset, linelen, newblock;
13151 //    char *movetext;
13152     char numtext[32];
13153     int movelen, numlen, blank;
13154     char move_buffer[100]; /* [AS] Buffer for move+PV info */
13155
13156     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13157
13158     PrintPGNTags(f, &gameInfo);
13159
13160     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
13161
13162     if (backwardMostMove > 0 || startedFromSetupPosition) {
13163         char *fen = PositionToFEN(backwardMostMove, NULL, 1);
13164         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
13165         fprintf(f, "\n{--------------\n");
13166         PrintPosition(f, backwardMostMove);
13167         fprintf(f, "--------------}\n");
13168         free(fen);
13169     }
13170     else {
13171         /* [AS] Out of book annotation */
13172         if( appData.saveOutOfBookInfo ) {
13173             char buf[64];
13174
13175             GetOutOfBookInfo( buf );
13176
13177             if( buf[0] != '\0' ) {
13178                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
13179             }
13180         }
13181
13182         fprintf(f, "\n");
13183     }
13184
13185     i = backwardMostMove;
13186     linelen = 0;
13187     newblock = TRUE;
13188
13189     while (i < forwardMostMove) {
13190         /* Print comments preceding this move */
13191         if (commentList[i] != NULL) {
13192             if (linelen > 0) fprintf(f, "\n");
13193             fprintf(f, "%s", commentList[i]);
13194             linelen = 0;
13195             newblock = TRUE;
13196         }
13197
13198         /* Format move number */
13199         if ((i % 2) == 0)
13200           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13201         else
13202           if (newblock)
13203             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13204           else
13205             numtext[0] = NULLCHAR;
13206
13207         numlen = strlen(numtext);
13208         newblock = FALSE;
13209
13210         /* Print move number */
13211         blank = linelen > 0 && numlen > 0;
13212         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13213             fprintf(f, "\n");
13214             linelen = 0;
13215             blank = 0;
13216         }
13217         if (blank) {
13218             fprintf(f, " ");
13219             linelen++;
13220         }
13221         fprintf(f, "%s", numtext);
13222         linelen += numlen;
13223
13224         /* Get move */
13225         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13226         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13227
13228         /* Print move */
13229         blank = linelen > 0 && movelen > 0;
13230         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13231             fprintf(f, "\n");
13232             linelen = 0;
13233             blank = 0;
13234         }
13235         if (blank) {
13236             fprintf(f, " ");
13237             linelen++;
13238         }
13239         fprintf(f, "%s", move_buffer);
13240         linelen += movelen;
13241
13242         /* [AS] Add PV info if present */
13243         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13244             /* [HGM] add time */
13245             char buf[MSG_SIZ]; int seconds;
13246
13247             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13248
13249             if( seconds <= 0)
13250               buf[0] = 0;
13251             else
13252               if( seconds < 30 )
13253                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13254               else
13255                 {
13256                   seconds = (seconds + 4)/10; // round to full seconds
13257                   if( seconds < 60 )
13258                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
13259                   else
13260                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
13261                 }
13262
13263             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
13264                       pvInfoList[i].score >= 0 ? "+" : "",
13265                       pvInfoList[i].score / 100.0,
13266                       pvInfoList[i].depth,
13267                       buf );
13268
13269             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
13270
13271             /* Print score/depth */
13272             blank = linelen > 0 && movelen > 0;
13273             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13274                 fprintf(f, "\n");
13275                 linelen = 0;
13276                 blank = 0;
13277             }
13278             if (blank) {
13279                 fprintf(f, " ");
13280                 linelen++;
13281             }
13282             fprintf(f, "%s", move_buffer);
13283             linelen += movelen;
13284         }
13285
13286         i++;
13287     }
13288
13289     /* Start a new line */
13290     if (linelen > 0) fprintf(f, "\n");
13291
13292     /* Print comments after last move */
13293     if (commentList[i] != NULL) {
13294         fprintf(f, "%s\n", commentList[i]);
13295     }
13296
13297     /* Print result */
13298     if (gameInfo.resultDetails != NULL &&
13299         gameInfo.resultDetails[0] != NULLCHAR) {
13300         char buf[MSG_SIZ], *p = gameInfo.resultDetails;
13301         if(gameInfo.result == GameUnfinished && appData.clockMode &&
13302            (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
13303             snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
13304         fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
13305     } else {
13306         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13307     }
13308
13309     fclose(f);
13310     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13311     return TRUE;
13312 }
13313
13314 /* Save game in old style and close the file */
13315 int
13316 SaveGameOldStyle (FILE *f)
13317 {
13318     int i, offset;
13319     time_t tm;
13320
13321     tm = time((time_t *) NULL);
13322
13323     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
13324     PrintOpponents(f);
13325
13326     if (backwardMostMove > 0 || startedFromSetupPosition) {
13327         fprintf(f, "\n[--------------\n");
13328         PrintPosition(f, backwardMostMove);
13329         fprintf(f, "--------------]\n");
13330     } else {
13331         fprintf(f, "\n");
13332     }
13333
13334     i = backwardMostMove;
13335     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13336
13337     while (i < forwardMostMove) {
13338         if (commentList[i] != NULL) {
13339             fprintf(f, "[%s]\n", commentList[i]);
13340         }
13341
13342         if ((i % 2) == 1) {
13343             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
13344             i++;
13345         } else {
13346             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
13347             i++;
13348             if (commentList[i] != NULL) {
13349                 fprintf(f, "\n");
13350                 continue;
13351             }
13352             if (i >= forwardMostMove) {
13353                 fprintf(f, "\n");
13354                 break;
13355             }
13356             fprintf(f, "%s\n", parseList[i]);
13357             i++;
13358         }
13359     }
13360
13361     if (commentList[i] != NULL) {
13362         fprintf(f, "[%s]\n", commentList[i]);
13363     }
13364
13365     /* This isn't really the old style, but it's close enough */
13366     if (gameInfo.resultDetails != NULL &&
13367         gameInfo.resultDetails[0] != NULLCHAR) {
13368         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13369                 gameInfo.resultDetails);
13370     } else {
13371         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13372     }
13373
13374     fclose(f);
13375     return TRUE;
13376 }
13377
13378 /* Save the current game to open file f and close the file */
13379 int
13380 SaveGame (FILE *f, int dummy, char *dummy2)
13381 {
13382     if (gameMode == EditPosition) EditPositionDone(TRUE);
13383     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13384     if (appData.oldSaveStyle)
13385       return SaveGameOldStyle(f);
13386     else
13387       return SaveGamePGN(f);
13388 }
13389
13390 /* Save the current position to the given file */
13391 int
13392 SavePositionToFile (char *filename)
13393 {
13394     FILE *f;
13395     char buf[MSG_SIZ];
13396
13397     if (strcmp(filename, "-") == 0) {
13398         return SavePosition(stdout, 0, NULL);
13399     } else {
13400         f = fopen(filename, "a");
13401         if (f == NULL) {
13402             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13403             DisplayError(buf, errno);
13404             return FALSE;
13405         } else {
13406             safeStrCpy(buf, lastMsg, MSG_SIZ);
13407             DisplayMessage(_("Waiting for access to save file"), "");
13408             flock(fileno(f), LOCK_EX); // [HGM] lock
13409             DisplayMessage(_("Saving position"), "");
13410             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
13411             SavePosition(f, 0, NULL);
13412             DisplayMessage(buf, "");
13413             return TRUE;
13414         }
13415     }
13416 }
13417
13418 /* Save the current position to the given open file and close the file */
13419 int
13420 SavePosition (FILE *f, int dummy, char *dummy2)
13421 {
13422     time_t tm;
13423     char *fen;
13424
13425     if (gameMode == EditPosition) EditPositionDone(TRUE);
13426     if (appData.oldSaveStyle) {
13427         tm = time((time_t *) NULL);
13428
13429         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
13430         PrintOpponents(f);
13431         fprintf(f, "[--------------\n");
13432         PrintPosition(f, currentMove);
13433         fprintf(f, "--------------]\n");
13434     } else {
13435         fen = PositionToFEN(currentMove, NULL, 1);
13436         fprintf(f, "%s\n", fen);
13437         free(fen);
13438     }
13439     fclose(f);
13440     return TRUE;
13441 }
13442
13443 void
13444 ReloadCmailMsgEvent (int unregister)
13445 {
13446 #if !WIN32
13447     static char *inFilename = NULL;
13448     static char *outFilename;
13449     int i;
13450     struct stat inbuf, outbuf;
13451     int status;
13452
13453     /* Any registered moves are unregistered if unregister is set, */
13454     /* i.e. invoked by the signal handler */
13455     if (unregister) {
13456         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13457             cmailMoveRegistered[i] = FALSE;
13458             if (cmailCommentList[i] != NULL) {
13459                 free(cmailCommentList[i]);
13460                 cmailCommentList[i] = NULL;
13461             }
13462         }
13463         nCmailMovesRegistered = 0;
13464     }
13465
13466     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13467         cmailResult[i] = CMAIL_NOT_RESULT;
13468     }
13469     nCmailResults = 0;
13470
13471     if (inFilename == NULL) {
13472         /* Because the filenames are static they only get malloced once  */
13473         /* and they never get freed                                      */
13474         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
13475         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
13476
13477         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
13478         sprintf(outFilename, "%s.out", appData.cmailGameName);
13479     }
13480
13481     status = stat(outFilename, &outbuf);
13482     if (status < 0) {
13483         cmailMailedMove = FALSE;
13484     } else {
13485         status = stat(inFilename, &inbuf);
13486         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
13487     }
13488
13489     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
13490        counts the games, notes how each one terminated, etc.
13491
13492        It would be nice to remove this kludge and instead gather all
13493        the information while building the game list.  (And to keep it
13494        in the game list nodes instead of having a bunch of fixed-size
13495        parallel arrays.)  Note this will require getting each game's
13496        termination from the PGN tags, as the game list builder does
13497        not process the game moves.  --mann
13498        */
13499     cmailMsgLoaded = TRUE;
13500     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
13501
13502     /* Load first game in the file or popup game menu */
13503     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
13504
13505 #endif /* !WIN32 */
13506     return;
13507 }
13508
13509 int
13510 RegisterMove ()
13511 {
13512     FILE *f;
13513     char string[MSG_SIZ];
13514
13515     if (   cmailMailedMove
13516         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
13517         return TRUE;            /* Allow free viewing  */
13518     }
13519
13520     /* Unregister move to ensure that we don't leave RegisterMove        */
13521     /* with the move registered when the conditions for registering no   */
13522     /* longer hold                                                       */
13523     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
13524         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
13525         nCmailMovesRegistered --;
13526
13527         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
13528           {
13529               free(cmailCommentList[lastLoadGameNumber - 1]);
13530               cmailCommentList[lastLoadGameNumber - 1] = NULL;
13531           }
13532     }
13533
13534     if (cmailOldMove == -1) {
13535         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
13536         return FALSE;
13537     }
13538
13539     if (currentMove > cmailOldMove + 1) {
13540         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
13541         return FALSE;
13542     }
13543
13544     if (currentMove < cmailOldMove) {
13545         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
13546         return FALSE;
13547     }
13548
13549     if (forwardMostMove > currentMove) {
13550         /* Silently truncate extra moves */
13551         TruncateGame();
13552     }
13553
13554     if (   (currentMove == cmailOldMove + 1)
13555         || (   (currentMove == cmailOldMove)
13556             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
13557                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
13558         if (gameInfo.result != GameUnfinished) {
13559             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
13560         }
13561
13562         if (commentList[currentMove] != NULL) {
13563             cmailCommentList[lastLoadGameNumber - 1]
13564               = StrSave(commentList[currentMove]);
13565         }
13566         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
13567
13568         if (appData.debugMode)
13569           fprintf(debugFP, "Saving %s for game %d\n",
13570                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
13571
13572         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
13573
13574         f = fopen(string, "w");
13575         if (appData.oldSaveStyle) {
13576             SaveGameOldStyle(f); /* also closes the file */
13577
13578             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
13579             f = fopen(string, "w");
13580             SavePosition(f, 0, NULL); /* also closes the file */
13581         } else {
13582             fprintf(f, "{--------------\n");
13583             PrintPosition(f, currentMove);
13584             fprintf(f, "--------------}\n\n");
13585
13586             SaveGame(f, 0, NULL); /* also closes the file*/
13587         }
13588
13589         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
13590         nCmailMovesRegistered ++;
13591     } else if (nCmailGames == 1) {
13592         DisplayError(_("You have not made a move yet"), 0);
13593         return FALSE;
13594     }
13595
13596     return TRUE;
13597 }
13598
13599 void
13600 MailMoveEvent ()
13601 {
13602 #if !WIN32
13603     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
13604     FILE *commandOutput;
13605     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
13606     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
13607     int nBuffers;
13608     int i;
13609     int archived;
13610     char *arcDir;
13611
13612     if (! cmailMsgLoaded) {
13613         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
13614         return;
13615     }
13616
13617     if (nCmailGames == nCmailResults) {
13618         DisplayError(_("No unfinished games"), 0);
13619         return;
13620     }
13621
13622 #if CMAIL_PROHIBIT_REMAIL
13623     if (cmailMailedMove) {
13624       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);
13625         DisplayError(msg, 0);
13626         return;
13627     }
13628 #endif
13629
13630     if (! (cmailMailedMove || RegisterMove())) return;
13631
13632     if (   cmailMailedMove
13633         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13634       snprintf(string, MSG_SIZ, partCommandString,
13635                appData.debugMode ? " -v" : "", appData.cmailGameName);
13636         commandOutput = popen(string, "r");
13637
13638         if (commandOutput == NULL) {
13639             DisplayError(_("Failed to invoke cmail"), 0);
13640         } else {
13641             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13642                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13643             }
13644             if (nBuffers > 1) {
13645                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13646                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13647                 nBytes = MSG_SIZ - 1;
13648             } else {
13649                 (void) memcpy(msg, buffer, nBytes);
13650             }
13651             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13652
13653             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13654                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
13655
13656                 archived = TRUE;
13657                 for (i = 0; i < nCmailGames; i ++) {
13658                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
13659                         archived = FALSE;
13660                     }
13661                 }
13662                 if (   archived
13663                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13664                         != NULL)) {
13665                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13666                            arcDir,
13667                            appData.cmailGameName,
13668                            gameInfo.date);
13669                     LoadGameFromFile(buffer, 1, buffer, FALSE);
13670                     cmailMsgLoaded = FALSE;
13671                 }
13672             }
13673
13674             DisplayInformation(msg);
13675             pclose(commandOutput);
13676         }
13677     } else {
13678         if ((*cmailMsg) != '\0') {
13679             DisplayInformation(cmailMsg);
13680         }
13681     }
13682
13683     return;
13684 #endif /* !WIN32 */
13685 }
13686
13687 char *
13688 CmailMsg ()
13689 {
13690 #if WIN32
13691     return NULL;
13692 #else
13693     int  prependComma = 0;
13694     char number[5];
13695     char string[MSG_SIZ];       /* Space for game-list */
13696     int  i;
13697
13698     if (!cmailMsgLoaded) return "";
13699
13700     if (cmailMailedMove) {
13701       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13702     } else {
13703         /* Create a list of games left */
13704       snprintf(string, MSG_SIZ, "[");
13705         for (i = 0; i < nCmailGames; i ++) {
13706             if (! (   cmailMoveRegistered[i]
13707                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13708                 if (prependComma) {
13709                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13710                 } else {
13711                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13712                     prependComma = 1;
13713                 }
13714
13715                 strcat(string, number);
13716             }
13717         }
13718         strcat(string, "]");
13719
13720         if (nCmailMovesRegistered + nCmailResults == 0) {
13721             switch (nCmailGames) {
13722               case 1:
13723                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13724                 break;
13725
13726               case 2:
13727                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13728                 break;
13729
13730               default:
13731                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13732                          nCmailGames);
13733                 break;
13734             }
13735         } else {
13736             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13737               case 1:
13738                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13739                          string);
13740                 break;
13741
13742               case 0:
13743                 if (nCmailResults == nCmailGames) {
13744                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13745                 } else {
13746                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13747                 }
13748                 break;
13749
13750               default:
13751                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13752                          string);
13753             }
13754         }
13755     }
13756     return cmailMsg;
13757 #endif /* WIN32 */
13758 }
13759
13760 void
13761 ResetGameEvent ()
13762 {
13763     if (gameMode == Training)
13764       SetTrainingModeOff();
13765
13766     Reset(TRUE, TRUE);
13767     cmailMsgLoaded = FALSE;
13768     if (appData.icsActive) {
13769       SendToICS(ics_prefix);
13770       SendToICS("refresh\n");
13771     }
13772 }
13773
13774 void
13775 ExitEvent (int status)
13776 {
13777     exiting++;
13778     if (exiting > 2) {
13779       /* Give up on clean exit */
13780       exit(status);
13781     }
13782     if (exiting > 1) {
13783       /* Keep trying for clean exit */
13784       return;
13785     }
13786
13787     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
13788
13789     if (telnetISR != NULL) {
13790       RemoveInputSource(telnetISR);
13791     }
13792     if (icsPR != NoProc) {
13793       DestroyChildProcess(icsPR, TRUE);
13794     }
13795
13796     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
13797     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
13798
13799     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
13800     /* make sure this other one finishes before killing it!                  */
13801     if(endingGame) { int count = 0;
13802         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
13803         while(endingGame && count++ < 10) DoSleep(1);
13804         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
13805     }
13806
13807     /* Kill off chess programs */
13808     if (first.pr != NoProc) {
13809         ExitAnalyzeMode();
13810
13811         DoSleep( appData.delayBeforeQuit );
13812         SendToProgram("quit\n", &first);
13813         DoSleep( appData.delayAfterQuit );
13814         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
13815     }
13816     if (second.pr != NoProc) {
13817         DoSleep( appData.delayBeforeQuit );
13818         SendToProgram("quit\n", &second);
13819         DoSleep( appData.delayAfterQuit );
13820         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
13821     }
13822     if (first.isr != NULL) {
13823         RemoveInputSource(first.isr);
13824     }
13825     if (second.isr != NULL) {
13826         RemoveInputSource(second.isr);
13827     }
13828
13829     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13830     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13831
13832     ShutDownFrontEnd();
13833     exit(status);
13834 }
13835
13836 void
13837 PauseEngine (ChessProgramState *cps)
13838 {
13839     SendToProgram("pause\n", cps);
13840     cps->pause = 2;
13841 }
13842
13843 void
13844 UnPauseEngine (ChessProgramState *cps)
13845 {
13846     SendToProgram("resume\n", cps);
13847     cps->pause = 1;
13848 }
13849
13850 void
13851 PauseEvent ()
13852 {
13853     if (appData.debugMode)
13854         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13855     if (pausing) {
13856         pausing = FALSE;
13857         ModeHighlight();
13858         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
13859             StartClocks();
13860             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
13861                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
13862                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
13863             }
13864             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
13865             HandleMachineMove(stashedInputMove, stalledEngine);
13866             stalledEngine = NULL;
13867             return;
13868         }
13869         if (gameMode == MachinePlaysWhite ||
13870             gameMode == TwoMachinesPlay   ||
13871             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
13872             if(first.pause)  UnPauseEngine(&first);
13873             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
13874             if(second.pause) UnPauseEngine(&second);
13875             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
13876             StartClocks();
13877         } else {
13878             DisplayBothClocks();
13879         }
13880         if (gameMode == PlayFromGameFile) {
13881             if (appData.timeDelay >= 0)
13882                 AutoPlayGameLoop();
13883         } else if (gameMode == IcsExamining && pauseExamInvalid) {
13884             Reset(FALSE, TRUE);
13885             SendToICS(ics_prefix);
13886             SendToICS("refresh\n");
13887         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
13888             ForwardInner(forwardMostMove);
13889         }
13890         pauseExamInvalid = FALSE;
13891     } else {
13892         switch (gameMode) {
13893           default:
13894             return;
13895           case IcsExamining:
13896             pauseExamForwardMostMove = forwardMostMove;
13897             pauseExamInvalid = FALSE;
13898             /* fall through */
13899           case IcsObserving:
13900           case IcsPlayingWhite:
13901           case IcsPlayingBlack:
13902             pausing = TRUE;
13903             ModeHighlight();
13904             return;
13905           case PlayFromGameFile:
13906             (void) StopLoadGameTimer();
13907             pausing = TRUE;
13908             ModeHighlight();
13909             break;
13910           case BeginningOfGame:
13911             if (appData.icsActive) return;
13912             /* else fall through */
13913           case MachinePlaysWhite:
13914           case MachinePlaysBlack:
13915           case TwoMachinesPlay:
13916             if (forwardMostMove == 0)
13917               return;           /* don't pause if no one has moved */
13918             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
13919                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
13920                 if(onMove->pause) {           // thinking engine can be paused
13921                     PauseEngine(onMove);      // do it
13922                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
13923                         PauseEngine(onMove->other);
13924                     else
13925                         SendToProgram("easy\n", onMove->other);
13926                     StopClocks();
13927                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
13928             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
13929                 if(first.pause) {
13930                     PauseEngine(&first);
13931                     StopClocks();
13932                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
13933             } else { // human on move, pause pondering by either method
13934                 if(first.pause)
13935                     PauseEngine(&first);
13936                 else if(appData.ponderNextMove)
13937                     SendToProgram("easy\n", &first);
13938                 StopClocks();
13939             }
13940             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
13941           case AnalyzeMode:
13942             pausing = TRUE;
13943             ModeHighlight();
13944             break;
13945         }
13946     }
13947 }
13948
13949 void
13950 EditCommentEvent ()
13951 {
13952     char title[MSG_SIZ];
13953
13954     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13955       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13956     } else {
13957       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13958                WhiteOnMove(currentMove - 1) ? " " : ".. ",
13959                parseList[currentMove - 1]);
13960     }
13961
13962     EditCommentPopUp(currentMove, title, commentList[currentMove]);
13963 }
13964
13965
13966 void
13967 EditTagsEvent ()
13968 {
13969     char *tags = PGNTags(&gameInfo);
13970     bookUp = FALSE;
13971     EditTagsPopUp(tags, NULL);
13972     free(tags);
13973 }
13974
13975 void
13976 ToggleSecond ()
13977 {
13978   if(second.analyzing) {
13979     SendToProgram("exit\n", &second);
13980     second.analyzing = FALSE;
13981   } else {
13982     if (second.pr == NoProc) StartChessProgram(&second);
13983     InitChessProgram(&second, FALSE);
13984     FeedMovesToProgram(&second, currentMove);
13985
13986     SendToProgram("analyze\n", &second);
13987     second.analyzing = TRUE;
13988   }
13989 }
13990
13991 /* Toggle ShowThinking */
13992 void
13993 ToggleShowThinking()
13994 {
13995   appData.showThinking = !appData.showThinking;
13996   ShowThinkingEvent();
13997 }
13998
13999 int
14000 AnalyzeModeEvent ()
14001 {
14002     char buf[MSG_SIZ];
14003
14004     if (!first.analysisSupport) {
14005       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14006       DisplayError(buf, 0);
14007       return 0;
14008     }
14009     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
14010     if (appData.icsActive) {
14011         if (gameMode != IcsObserving) {
14012           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
14013             DisplayError(buf, 0);
14014             /* secure check */
14015             if (appData.icsEngineAnalyze) {
14016                 if (appData.debugMode)
14017                     fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
14018                 ExitAnalyzeMode();
14019                 ModeHighlight();
14020             }
14021             return 0;
14022         }
14023         /* if enable, user wants to disable icsEngineAnalyze */
14024         if (appData.icsEngineAnalyze) {
14025                 ExitAnalyzeMode();
14026                 ModeHighlight();
14027                 return 0;
14028         }
14029         appData.icsEngineAnalyze = TRUE;
14030         if (appData.debugMode)
14031             fprintf(debugFP, "ICS engine analyze starting... \n");
14032     }
14033
14034     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
14035     if (appData.noChessProgram || gameMode == AnalyzeMode)
14036       return 0;
14037
14038     if (gameMode != AnalyzeFile) {
14039         if (!appData.icsEngineAnalyze) {
14040                EditGameEvent();
14041                if (gameMode != EditGame) return 0;
14042         }
14043         if (!appData.showThinking) ToggleShowThinking();
14044         ResurrectChessProgram();
14045         SendToProgram("analyze\n", &first);
14046         first.analyzing = TRUE;
14047         /*first.maybeThinking = TRUE;*/
14048         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14049         EngineOutputPopUp();
14050     }
14051     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
14052     pausing = FALSE;
14053     ModeHighlight();
14054     SetGameInfo();
14055
14056     StartAnalysisClock();
14057     GetTimeMark(&lastNodeCountTime);
14058     lastNodeCount = 0;
14059     return 1;
14060 }
14061
14062 void
14063 AnalyzeFileEvent ()
14064 {
14065     if (appData.noChessProgram || gameMode == AnalyzeFile)
14066       return;
14067
14068     if (!first.analysisSupport) {
14069       char buf[MSG_SIZ];
14070       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14071       DisplayError(buf, 0);
14072       return;
14073     }
14074
14075     if (gameMode != AnalyzeMode) {
14076         keepInfo = 1; // mere annotating should not alter PGN tags
14077         EditGameEvent();
14078         keepInfo = 0;
14079         if (gameMode != EditGame) return;
14080         if (!appData.showThinking) ToggleShowThinking();
14081         ResurrectChessProgram();
14082         SendToProgram("analyze\n", &first);
14083         first.analyzing = TRUE;
14084         /*first.maybeThinking = TRUE;*/
14085         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14086         EngineOutputPopUp();
14087     }
14088     gameMode = AnalyzeFile;
14089     pausing = FALSE;
14090     ModeHighlight();
14091
14092     StartAnalysisClock();
14093     GetTimeMark(&lastNodeCountTime);
14094     lastNodeCount = 0;
14095     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14096     AnalysisPeriodicEvent(1);
14097 }
14098
14099 void
14100 MachineWhiteEvent ()
14101 {
14102     char buf[MSG_SIZ];
14103     char *bookHit = NULL;
14104
14105     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14106       return;
14107
14108
14109     if (gameMode == PlayFromGameFile ||
14110         gameMode == TwoMachinesPlay  ||
14111         gameMode == Training         ||
14112         gameMode == AnalyzeMode      ||
14113         gameMode == EndOfGame)
14114         EditGameEvent();
14115
14116     if (gameMode == EditPosition)
14117         EditPositionDone(TRUE);
14118
14119     if (!WhiteOnMove(currentMove)) {
14120         DisplayError(_("It is not White's turn"), 0);
14121         return;
14122     }
14123
14124     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14125       ExitAnalyzeMode();
14126
14127     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14128         gameMode == AnalyzeFile)
14129         TruncateGame();
14130
14131     ResurrectChessProgram();    /* in case it isn't running */
14132     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
14133         gameMode = MachinePlaysWhite;
14134         ResetClocks();
14135     } else
14136     gameMode = MachinePlaysWhite;
14137     pausing = FALSE;
14138     ModeHighlight();
14139     SetGameInfo();
14140     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14141     DisplayTitle(buf);
14142     if (first.sendName) {
14143       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
14144       SendToProgram(buf, &first);
14145     }
14146     if (first.sendTime) {
14147       if (first.useColors) {
14148         SendToProgram("black\n", &first); /*gnu kludge*/
14149       }
14150       SendTimeRemaining(&first, TRUE);
14151     }
14152     if (first.useColors) {
14153       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
14154     }
14155     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14156     SetMachineThinkingEnables();
14157     first.maybeThinking = TRUE;
14158     StartClocks();
14159     firstMove = FALSE;
14160
14161     if (appData.autoFlipView && !flipView) {
14162       flipView = !flipView;
14163       DrawPosition(FALSE, NULL);
14164       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14165     }
14166
14167     if(bookHit) { // [HGM] book: simulate book reply
14168         static char bookMove[MSG_SIZ]; // a bit generous?
14169
14170         programStats.nodes = programStats.depth = programStats.time =
14171         programStats.score = programStats.got_only_move = 0;
14172         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14173
14174         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14175         strcat(bookMove, bookHit);
14176         HandleMachineMove(bookMove, &first);
14177     }
14178 }
14179
14180 void
14181 MachineBlackEvent ()
14182 {
14183   char buf[MSG_SIZ];
14184   char *bookHit = NULL;
14185
14186     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
14187         return;
14188
14189
14190     if (gameMode == PlayFromGameFile ||
14191         gameMode == TwoMachinesPlay  ||
14192         gameMode == Training         ||
14193         gameMode == AnalyzeMode      ||
14194         gameMode == EndOfGame)
14195         EditGameEvent();
14196
14197     if (gameMode == EditPosition)
14198         EditPositionDone(TRUE);
14199
14200     if (WhiteOnMove(currentMove)) {
14201         DisplayError(_("It is not Black's turn"), 0);
14202         return;
14203     }
14204
14205     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14206       ExitAnalyzeMode();
14207
14208     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14209         gameMode == AnalyzeFile)
14210         TruncateGame();
14211
14212     ResurrectChessProgram();    /* in case it isn't running */
14213     gameMode = MachinePlaysBlack;
14214     pausing = FALSE;
14215     ModeHighlight();
14216     SetGameInfo();
14217     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14218     DisplayTitle(buf);
14219     if (first.sendName) {
14220       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14221       SendToProgram(buf, &first);
14222     }
14223     if (first.sendTime) {
14224       if (first.useColors) {
14225         SendToProgram("white\n", &first); /*gnu kludge*/
14226       }
14227       SendTimeRemaining(&first, FALSE);
14228     }
14229     if (first.useColors) {
14230       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14231     }
14232     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14233     SetMachineThinkingEnables();
14234     first.maybeThinking = TRUE;
14235     StartClocks();
14236
14237     if (appData.autoFlipView && flipView) {
14238       flipView = !flipView;
14239       DrawPosition(FALSE, NULL);
14240       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14241     }
14242     if(bookHit) { // [HGM] book: simulate book reply
14243         static char bookMove[MSG_SIZ]; // a bit generous?
14244
14245         programStats.nodes = programStats.depth = programStats.time =
14246         programStats.score = programStats.got_only_move = 0;
14247         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14248
14249         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14250         strcat(bookMove, bookHit);
14251         HandleMachineMove(bookMove, &first);
14252     }
14253 }
14254
14255
14256 void
14257 DisplayTwoMachinesTitle ()
14258 {
14259     char buf[MSG_SIZ];
14260     if (appData.matchGames > 0) {
14261         if(appData.tourneyFile[0]) {
14262           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14263                    gameInfo.white, _("vs."), gameInfo.black,
14264                    nextGame+1, appData.matchGames+1,
14265                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14266         } else
14267         if (first.twoMachinesColor[0] == 'w') {
14268           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14269                    gameInfo.white, _("vs."),  gameInfo.black,
14270                    first.matchWins, second.matchWins,
14271                    matchGame - 1 - (first.matchWins + second.matchWins));
14272         } else {
14273           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14274                    gameInfo.white, _("vs."), gameInfo.black,
14275                    second.matchWins, first.matchWins,
14276                    matchGame - 1 - (first.matchWins + second.matchWins));
14277         }
14278     } else {
14279       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14280     }
14281     DisplayTitle(buf);
14282 }
14283
14284 void
14285 SettingsMenuIfReady ()
14286 {
14287   if (second.lastPing != second.lastPong) {
14288     DisplayMessage("", _("Waiting for second chess program"));
14289     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14290     return;
14291   }
14292   ThawUI();
14293   DisplayMessage("", "");
14294   SettingsPopUp(&second);
14295 }
14296
14297 int
14298 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
14299 {
14300     char buf[MSG_SIZ];
14301     if (cps->pr == NoProc) {
14302         StartChessProgram(cps);
14303         if (cps->protocolVersion == 1) {
14304           retry();
14305           ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
14306         } else {
14307           /* kludge: allow timeout for initial "feature" command */
14308           if(retry != TwoMachinesEventIfReady) FreezeUI();
14309           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
14310           DisplayMessage("", buf);
14311           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
14312         }
14313         return 1;
14314     }
14315     return 0;
14316 }
14317
14318 void
14319 TwoMachinesEvent P((void))
14320 {
14321     int i;
14322     char buf[MSG_SIZ];
14323     ChessProgramState *onmove;
14324     char *bookHit = NULL;
14325     static int stalling = 0;
14326     TimeMark now;
14327     long wait;
14328
14329     if (appData.noChessProgram) return;
14330
14331     switch (gameMode) {
14332       case TwoMachinesPlay:
14333         return;
14334       case MachinePlaysWhite:
14335       case MachinePlaysBlack:
14336         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14337             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
14338             return;
14339         }
14340         /* fall through */
14341       case BeginningOfGame:
14342       case PlayFromGameFile:
14343       case EndOfGame:
14344         EditGameEvent();
14345         if (gameMode != EditGame) return;
14346         break;
14347       case EditPosition:
14348         EditPositionDone(TRUE);
14349         break;
14350       case AnalyzeMode:
14351       case AnalyzeFile:
14352         ExitAnalyzeMode();
14353         break;
14354       case EditGame:
14355       default:
14356         break;
14357     }
14358
14359 //    forwardMostMove = currentMove;
14360     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
14361     startingEngine = TRUE;
14362
14363     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
14364
14365     if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14366     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14367       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14368       return;
14369     }
14370     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14371
14372     if(!SupportedVariant(second.variants, gameInfo.variant, gameInfo.boardWidth,
14373                          gameInfo.boardHeight, gameInfo.holdingsSize, second.protocolVersion, second.tidy)) {
14374         startingEngine = FALSE;
14375         DisplayError("second engine does not play this", 0);
14376         return;
14377     }
14378
14379     if(!stalling) {
14380       InitChessProgram(&second, FALSE); // unbalances ping of second engine
14381       SendToProgram("force\n", &second);
14382       stalling = 1;
14383       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14384       return;
14385     }
14386     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14387     if(appData.matchPause>10000 || appData.matchPause<10)
14388                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14389     wait = SubtractTimeMarks(&now, &pauseStart);
14390     if(wait < appData.matchPause) {
14391         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14392         return;
14393     }
14394     // we are now committed to starting the game
14395     stalling = 0;
14396     DisplayMessage("", "");
14397     if (startedFromSetupPosition) {
14398         SendBoard(&second, backwardMostMove);
14399     if (appData.debugMode) {
14400         fprintf(debugFP, "Two Machines\n");
14401     }
14402     }
14403     for (i = backwardMostMove; i < forwardMostMove; i++) {
14404         SendMoveToProgram(i, &second);
14405     }
14406
14407     gameMode = TwoMachinesPlay;
14408     pausing = startingEngine = FALSE;
14409     ModeHighlight(); // [HGM] logo: this triggers display update of logos
14410     SetGameInfo();
14411     DisplayTwoMachinesTitle();
14412     firstMove = TRUE;
14413     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14414         onmove = &first;
14415     } else {
14416         onmove = &second;
14417     }
14418     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14419     SendToProgram(first.computerString, &first);
14420     if (first.sendName) {
14421       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14422       SendToProgram(buf, &first);
14423     }
14424     SendToProgram(second.computerString, &second);
14425     if (second.sendName) {
14426       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
14427       SendToProgram(buf, &second);
14428     }
14429
14430     ResetClocks();
14431     if (!first.sendTime || !second.sendTime) {
14432         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14433         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14434     }
14435     if (onmove->sendTime) {
14436       if (onmove->useColors) {
14437         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
14438       }
14439       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
14440     }
14441     if (onmove->useColors) {
14442       SendToProgram(onmove->twoMachinesColor, onmove);
14443     }
14444     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
14445 //    SendToProgram("go\n", onmove);
14446     onmove->maybeThinking = TRUE;
14447     SetMachineThinkingEnables();
14448
14449     StartClocks();
14450
14451     if(bookHit) { // [HGM] book: simulate book reply
14452         static char bookMove[MSG_SIZ]; // a bit generous?
14453
14454         programStats.nodes = programStats.depth = programStats.time =
14455         programStats.score = programStats.got_only_move = 0;
14456         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14457
14458         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14459         strcat(bookMove, bookHit);
14460         savedMessage = bookMove; // args for deferred call
14461         savedState = onmove;
14462         ScheduleDelayedEvent(DeferredBookMove, 1);
14463     }
14464 }
14465
14466 void
14467 TrainingEvent ()
14468 {
14469     if (gameMode == Training) {
14470       SetTrainingModeOff();
14471       gameMode = PlayFromGameFile;
14472       DisplayMessage("", _("Training mode off"));
14473     } else {
14474       gameMode = Training;
14475       animateTraining = appData.animate;
14476
14477       /* make sure we are not already at the end of the game */
14478       if (currentMove < forwardMostMove) {
14479         SetTrainingModeOn();
14480         DisplayMessage("", _("Training mode on"));
14481       } else {
14482         gameMode = PlayFromGameFile;
14483         DisplayError(_("Already at end of game"), 0);
14484       }
14485     }
14486     ModeHighlight();
14487 }
14488
14489 void
14490 IcsClientEvent ()
14491 {
14492     if (!appData.icsActive) return;
14493     switch (gameMode) {
14494       case IcsPlayingWhite:
14495       case IcsPlayingBlack:
14496       case IcsObserving:
14497       case IcsIdle:
14498       case BeginningOfGame:
14499       case IcsExamining:
14500         return;
14501
14502       case EditGame:
14503         break;
14504
14505       case EditPosition:
14506         EditPositionDone(TRUE);
14507         break;
14508
14509       case AnalyzeMode:
14510       case AnalyzeFile:
14511         ExitAnalyzeMode();
14512         break;
14513
14514       default:
14515         EditGameEvent();
14516         break;
14517     }
14518
14519     gameMode = IcsIdle;
14520     ModeHighlight();
14521     return;
14522 }
14523
14524 void
14525 EditGameEvent ()
14526 {
14527     int i;
14528
14529     switch (gameMode) {
14530       case Training:
14531         SetTrainingModeOff();
14532         break;
14533       case MachinePlaysWhite:
14534       case MachinePlaysBlack:
14535       case BeginningOfGame:
14536         SendToProgram("force\n", &first);
14537         SetUserThinkingEnables();
14538         break;
14539       case PlayFromGameFile:
14540         (void) StopLoadGameTimer();
14541         if (gameFileFP != NULL) {
14542             gameFileFP = NULL;
14543         }
14544         break;
14545       case EditPosition:
14546         EditPositionDone(TRUE);
14547         break;
14548       case AnalyzeMode:
14549       case AnalyzeFile:
14550         ExitAnalyzeMode();
14551         SendToProgram("force\n", &first);
14552         break;
14553       case TwoMachinesPlay:
14554         GameEnds(EndOfFile, NULL, GE_PLAYER);
14555         ResurrectChessProgram();
14556         SetUserThinkingEnables();
14557         break;
14558       case EndOfGame:
14559         ResurrectChessProgram();
14560         break;
14561       case IcsPlayingBlack:
14562       case IcsPlayingWhite:
14563         DisplayError(_("Warning: You are still playing a game"), 0);
14564         break;
14565       case IcsObserving:
14566         DisplayError(_("Warning: You are still observing a game"), 0);
14567         break;
14568       case IcsExamining:
14569         DisplayError(_("Warning: You are still examining a game"), 0);
14570         break;
14571       case IcsIdle:
14572         break;
14573       case EditGame:
14574       default:
14575         return;
14576     }
14577
14578     pausing = FALSE;
14579     StopClocks();
14580     first.offeredDraw = second.offeredDraw = 0;
14581
14582     if (gameMode == PlayFromGameFile) {
14583         whiteTimeRemaining = timeRemaining[0][currentMove];
14584         blackTimeRemaining = timeRemaining[1][currentMove];
14585         DisplayTitle("");
14586     }
14587
14588     if (gameMode == MachinePlaysWhite ||
14589         gameMode == MachinePlaysBlack ||
14590         gameMode == TwoMachinesPlay ||
14591         gameMode == EndOfGame) {
14592         i = forwardMostMove;
14593         while (i > currentMove) {
14594             SendToProgram("undo\n", &first);
14595             i--;
14596         }
14597         if(!adjustedClock) {
14598         whiteTimeRemaining = timeRemaining[0][currentMove];
14599         blackTimeRemaining = timeRemaining[1][currentMove];
14600         DisplayBothClocks();
14601         }
14602         if (whiteFlag || blackFlag) {
14603             whiteFlag = blackFlag = 0;
14604         }
14605         DisplayTitle("");
14606     }
14607
14608     gameMode = EditGame;
14609     ModeHighlight();
14610     SetGameInfo();
14611 }
14612
14613
14614 void
14615 EditPositionEvent ()
14616 {
14617     if (gameMode == EditPosition) {
14618         EditGameEvent();
14619         return;
14620     }
14621
14622     EditGameEvent();
14623     if (gameMode != EditGame) return;
14624
14625     gameMode = EditPosition;
14626     ModeHighlight();
14627     SetGameInfo();
14628     if (currentMove > 0)
14629       CopyBoard(boards[0], boards[currentMove]);
14630
14631     blackPlaysFirst = !WhiteOnMove(currentMove);
14632     ResetClocks();
14633     currentMove = forwardMostMove = backwardMostMove = 0;
14634     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14635     DisplayMove(-1);
14636     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
14637 }
14638
14639 void
14640 ExitAnalyzeMode ()
14641 {
14642     /* [DM] icsEngineAnalyze - possible call from other functions */
14643     if (appData.icsEngineAnalyze) {
14644         appData.icsEngineAnalyze = FALSE;
14645
14646         DisplayMessage("",_("Close ICS engine analyze..."));
14647     }
14648     if (first.analysisSupport && first.analyzing) {
14649       SendToBoth("exit\n");
14650       first.analyzing = second.analyzing = FALSE;
14651     }
14652     thinkOutput[0] = NULLCHAR;
14653 }
14654
14655 void
14656 EditPositionDone (Boolean fakeRights)
14657 {
14658     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
14659
14660     startedFromSetupPosition = TRUE;
14661     InitChessProgram(&first, FALSE);
14662     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
14663       boards[0][EP_STATUS] = EP_NONE;
14664       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
14665       if(boards[0][0][BOARD_WIDTH>>1] == king) {
14666         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
14667         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
14668       } else boards[0][CASTLING][2] = NoRights;
14669       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
14670         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
14671         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
14672       } else boards[0][CASTLING][5] = NoRights;
14673       if(gameInfo.variant == VariantSChess) {
14674         int i;
14675         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
14676           boards[0][VIRGIN][i] = 0;
14677           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
14678           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
14679         }
14680       }
14681     }
14682     SendToProgram("force\n", &first);
14683     if (blackPlaysFirst) {
14684         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
14685         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
14686         currentMove = forwardMostMove = backwardMostMove = 1;
14687         CopyBoard(boards[1], boards[0]);
14688     } else {
14689         currentMove = forwardMostMove = backwardMostMove = 0;
14690     }
14691     SendBoard(&first, forwardMostMove);
14692     if (appData.debugMode) {
14693         fprintf(debugFP, "EditPosDone\n");
14694     }
14695     DisplayTitle("");
14696     DisplayMessage("", "");
14697     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14698     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14699     gameMode = EditGame;
14700     ModeHighlight();
14701     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14702     ClearHighlights(); /* [AS] */
14703 }
14704
14705 /* Pause for `ms' milliseconds */
14706 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14707 void
14708 TimeDelay (long ms)
14709 {
14710     TimeMark m1, m2;
14711
14712     GetTimeMark(&m1);
14713     do {
14714         GetTimeMark(&m2);
14715     } while (SubtractTimeMarks(&m2, &m1) < ms);
14716 }
14717
14718 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14719 void
14720 SendMultiLineToICS (char *buf)
14721 {
14722     char temp[MSG_SIZ+1], *p;
14723     int len;
14724
14725     len = strlen(buf);
14726     if (len > MSG_SIZ)
14727       len = MSG_SIZ;
14728
14729     strncpy(temp, buf, len);
14730     temp[len] = 0;
14731
14732     p = temp;
14733     while (*p) {
14734         if (*p == '\n' || *p == '\r')
14735           *p = ' ';
14736         ++p;
14737     }
14738
14739     strcat(temp, "\n");
14740     SendToICS(temp);
14741     SendToPlayer(temp, strlen(temp));
14742 }
14743
14744 void
14745 SetWhiteToPlayEvent ()
14746 {
14747     if (gameMode == EditPosition) {
14748         blackPlaysFirst = FALSE;
14749         DisplayBothClocks();    /* works because currentMove is 0 */
14750     } else if (gameMode == IcsExamining) {
14751         SendToICS(ics_prefix);
14752         SendToICS("tomove white\n");
14753     }
14754 }
14755
14756 void
14757 SetBlackToPlayEvent ()
14758 {
14759     if (gameMode == EditPosition) {
14760         blackPlaysFirst = TRUE;
14761         currentMove = 1;        /* kludge */
14762         DisplayBothClocks();
14763         currentMove = 0;
14764     } else if (gameMode == IcsExamining) {
14765         SendToICS(ics_prefix);
14766         SendToICS("tomove black\n");
14767     }
14768 }
14769
14770 void
14771 EditPositionMenuEvent (ChessSquare selection, int x, int y)
14772 {
14773     char buf[MSG_SIZ];
14774     ChessSquare piece = boards[0][y][x];
14775     static Board erasedBoard, currentBoard, menuBoard, nullBoard;
14776     static int lastVariant;
14777
14778     if (gameMode != EditPosition && gameMode != IcsExamining) return;
14779
14780     switch (selection) {
14781       case ClearBoard:
14782         CopyBoard(currentBoard, boards[0]);
14783         CopyBoard(menuBoard, initialPosition);
14784         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
14785             SendToICS(ics_prefix);
14786             SendToICS("bsetup clear\n");
14787         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
14788             SendToICS(ics_prefix);
14789             SendToICS("clearboard\n");
14790         } else {
14791             int nonEmpty = 0;
14792             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
14793                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
14794                 for (y = 0; y < BOARD_HEIGHT; y++) {
14795                     if (gameMode == IcsExamining) {
14796                         if (boards[currentMove][y][x] != EmptySquare) {
14797                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
14798                                     AAA + x, ONE + y);
14799                             SendToICS(buf);
14800                         }
14801                     } else {
14802                         if(boards[0][y][x] != p) nonEmpty++;
14803                         boards[0][y][x] = p;
14804                     }
14805                 }
14806                 menuBoard[1][x] = menuBoard[BOARD_HEIGHT-2][x] = p;
14807             }
14808             if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
14809                 for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates 
14810                     ChessSquare p = menuBoard[0][x];
14811                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[0][y] == p) menuBoard[0][y] = EmptySquare;
14812                     p = menuBoard[BOARD_HEIGHT-1][x];
14813                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[BOARD_HEIGHT-1][y] == p) menuBoard[BOARD_HEIGHT-1][y] = EmptySquare;
14814                 }
14815                 DisplayMessage("Clicking clock again restores position", "");
14816                 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
14817                 if(!nonEmpty) { // asked to clear an empty board
14818                     CopyBoard(boards[0], menuBoard);
14819                 } else
14820                 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
14821                     CopyBoard(boards[0], initialPosition);
14822                 } else
14823                 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
14824                                                                  && !CompareBoards(nullBoard, erasedBoard)) {
14825                     CopyBoard(boards[0], erasedBoard);
14826                 } else
14827                     CopyBoard(erasedBoard, currentBoard);
14828
14829             }
14830         }
14831         if (gameMode == EditPosition) {
14832             DrawPosition(FALSE, boards[0]);
14833         }
14834         break;
14835
14836       case WhitePlay:
14837         SetWhiteToPlayEvent();
14838         break;
14839
14840       case BlackPlay:
14841         SetBlackToPlayEvent();
14842         break;
14843
14844       case EmptySquare:
14845         if (gameMode == IcsExamining) {
14846             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14847             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
14848             SendToICS(buf);
14849         } else {
14850             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14851                 if(x == BOARD_LEFT-2) {
14852                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
14853                     boards[0][y][1] = 0;
14854                 } else
14855                 if(x == BOARD_RGHT+1) {
14856                     if(y >= gameInfo.holdingsSize) break;
14857                     boards[0][y][BOARD_WIDTH-2] = 0;
14858                 } else break;
14859             }
14860             boards[0][y][x] = EmptySquare;
14861             DrawPosition(FALSE, boards[0]);
14862         }
14863         break;
14864
14865       case PromotePiece:
14866         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
14867            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
14868             selection = (ChessSquare) (PROMOTED piece);
14869         } else if(piece == EmptySquare) selection = WhiteSilver;
14870         else selection = (ChessSquare)((int)piece - 1);
14871         goto defaultlabel;
14872
14873       case DemotePiece:
14874         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
14875            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
14876             selection = (ChessSquare) (DEMOTED piece);
14877         } else if(piece == EmptySquare) selection = BlackSilver;
14878         else selection = (ChessSquare)((int)piece + 1);
14879         goto defaultlabel;
14880
14881       case WhiteQueen:
14882       case BlackQueen:
14883         if(gameInfo.variant == VariantShatranj ||
14884            gameInfo.variant == VariantXiangqi  ||
14885            gameInfo.variant == VariantCourier  ||
14886            gameInfo.variant == VariantASEAN    ||
14887            gameInfo.variant == VariantMakruk     )
14888             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
14889         goto defaultlabel;
14890
14891       case WhiteKing:
14892       case BlackKing:
14893         if(gameInfo.variant == VariantXiangqi)
14894             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
14895         if(gameInfo.variant == VariantKnightmate)
14896             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
14897       default:
14898         defaultlabel:
14899         if (gameMode == IcsExamining) {
14900             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14901             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
14902                      PieceToChar(selection), AAA + x, ONE + y);
14903             SendToICS(buf);
14904         } else {
14905             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14906                 int n;
14907                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
14908                     n = PieceToNumber(selection - BlackPawn);
14909                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
14910                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
14911                     boards[0][BOARD_HEIGHT-1-n][1]++;
14912                 } else
14913                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
14914                     n = PieceToNumber(selection);
14915                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
14916                     boards[0][n][BOARD_WIDTH-1] = selection;
14917                     boards[0][n][BOARD_WIDTH-2]++;
14918                 }
14919             } else
14920             boards[0][y][x] = selection;
14921             DrawPosition(TRUE, boards[0]);
14922             ClearHighlights();
14923             fromX = fromY = -1;
14924         }
14925         break;
14926     }
14927 }
14928
14929
14930 void
14931 DropMenuEvent (ChessSquare selection, int x, int y)
14932 {
14933     ChessMove moveType;
14934
14935     switch (gameMode) {
14936       case IcsPlayingWhite:
14937       case MachinePlaysBlack:
14938         if (!WhiteOnMove(currentMove)) {
14939             DisplayMoveError(_("It is Black's turn"));
14940             return;
14941         }
14942         moveType = WhiteDrop;
14943         break;
14944       case IcsPlayingBlack:
14945       case MachinePlaysWhite:
14946         if (WhiteOnMove(currentMove)) {
14947             DisplayMoveError(_("It is White's turn"));
14948             return;
14949         }
14950         moveType = BlackDrop;
14951         break;
14952       case EditGame:
14953         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
14954         break;
14955       default:
14956         return;
14957     }
14958
14959     if (moveType == BlackDrop && selection < BlackPawn) {
14960       selection = (ChessSquare) ((int) selection
14961                                  + (int) BlackPawn - (int) WhitePawn);
14962     }
14963     if (boards[currentMove][y][x] != EmptySquare) {
14964         DisplayMoveError(_("That square is occupied"));
14965         return;
14966     }
14967
14968     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
14969 }
14970
14971 void
14972 AcceptEvent ()
14973 {
14974     /* Accept a pending offer of any kind from opponent */
14975
14976     if (appData.icsActive) {
14977         SendToICS(ics_prefix);
14978         SendToICS("accept\n");
14979     } else if (cmailMsgLoaded) {
14980         if (currentMove == cmailOldMove &&
14981             commentList[cmailOldMove] != NULL &&
14982             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14983                    "Black offers a draw" : "White offers a draw")) {
14984             TruncateGame();
14985             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14986             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14987         } else {
14988             DisplayError(_("There is no pending offer on this move"), 0);
14989             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14990         }
14991     } else {
14992         /* Not used for offers from chess program */
14993     }
14994 }
14995
14996 void
14997 DeclineEvent ()
14998 {
14999     /* Decline a pending offer of any kind from opponent */
15000
15001     if (appData.icsActive) {
15002         SendToICS(ics_prefix);
15003         SendToICS("decline\n");
15004     } else if (cmailMsgLoaded) {
15005         if (currentMove == cmailOldMove &&
15006             commentList[cmailOldMove] != NULL &&
15007             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15008                    "Black offers a draw" : "White offers a draw")) {
15009 #ifdef NOTDEF
15010             AppendComment(cmailOldMove, "Draw declined", TRUE);
15011             DisplayComment(cmailOldMove - 1, "Draw declined");
15012 #endif /*NOTDEF*/
15013         } else {
15014             DisplayError(_("There is no pending offer on this move"), 0);
15015         }
15016     } else {
15017         /* Not used for offers from chess program */
15018     }
15019 }
15020
15021 void
15022 RematchEvent ()
15023 {
15024     /* Issue ICS rematch command */
15025     if (appData.icsActive) {
15026         SendToICS(ics_prefix);
15027         SendToICS("rematch\n");
15028     }
15029 }
15030
15031 void
15032 CallFlagEvent ()
15033 {
15034     /* Call your opponent's flag (claim a win on time) */
15035     if (appData.icsActive) {
15036         SendToICS(ics_prefix);
15037         SendToICS("flag\n");
15038     } else {
15039         switch (gameMode) {
15040           default:
15041             return;
15042           case MachinePlaysWhite:
15043             if (whiteFlag) {
15044                 if (blackFlag)
15045                   GameEnds(GameIsDrawn, "Both players ran out of time",
15046                            GE_PLAYER);
15047                 else
15048                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15049             } else {
15050                 DisplayError(_("Your opponent is not out of time"), 0);
15051             }
15052             break;
15053           case MachinePlaysBlack:
15054             if (blackFlag) {
15055                 if (whiteFlag)
15056                   GameEnds(GameIsDrawn, "Both players ran out of time",
15057                            GE_PLAYER);
15058                 else
15059                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15060             } else {
15061                 DisplayError(_("Your opponent is not out of time"), 0);
15062             }
15063             break;
15064         }
15065     }
15066 }
15067
15068 void
15069 ClockClick (int which)
15070 {       // [HGM] code moved to back-end from winboard.c
15071         if(which) { // black clock
15072           if (gameMode == EditPosition || gameMode == IcsExamining) {
15073             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15074             SetBlackToPlayEvent();
15075           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
15076           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
15077           } else if (shiftKey) {
15078             AdjustClock(which, -1);
15079           } else if (gameMode == IcsPlayingWhite ||
15080                      gameMode == MachinePlaysBlack) {
15081             CallFlagEvent();
15082           }
15083         } else { // white clock
15084           if (gameMode == EditPosition || gameMode == IcsExamining) {
15085             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15086             SetWhiteToPlayEvent();
15087           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
15088           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
15089           } else if (shiftKey) {
15090             AdjustClock(which, -1);
15091           } else if (gameMode == IcsPlayingBlack ||
15092                    gameMode == MachinePlaysWhite) {
15093             CallFlagEvent();
15094           }
15095         }
15096 }
15097
15098 void
15099 DrawEvent ()
15100 {
15101     /* Offer draw or accept pending draw offer from opponent */
15102
15103     if (appData.icsActive) {
15104         /* Note: tournament rules require draw offers to be
15105            made after you make your move but before you punch
15106            your clock.  Currently ICS doesn't let you do that;
15107            instead, you immediately punch your clock after making
15108            a move, but you can offer a draw at any time. */
15109
15110         SendToICS(ics_prefix);
15111         SendToICS("draw\n");
15112         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
15113     } else if (cmailMsgLoaded) {
15114         if (currentMove == cmailOldMove &&
15115             commentList[cmailOldMove] != NULL &&
15116             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15117                    "Black offers a draw" : "White offers a draw")) {
15118             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15119             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15120         } else if (currentMove == cmailOldMove + 1) {
15121             char *offer = WhiteOnMove(cmailOldMove) ?
15122               "White offers a draw" : "Black offers a draw";
15123             AppendComment(currentMove, offer, TRUE);
15124             DisplayComment(currentMove - 1, offer);
15125             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
15126         } else {
15127             DisplayError(_("You must make your move before offering a draw"), 0);
15128             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15129         }
15130     } else if (first.offeredDraw) {
15131         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
15132     } else {
15133         if (first.sendDrawOffers) {
15134             SendToProgram("draw\n", &first);
15135             userOfferedDraw = TRUE;
15136         }
15137     }
15138 }
15139
15140 void
15141 AdjournEvent ()
15142 {
15143     /* Offer Adjourn or accept pending Adjourn offer from opponent */
15144
15145     if (appData.icsActive) {
15146         SendToICS(ics_prefix);
15147         SendToICS("adjourn\n");
15148     } else {
15149         /* Currently GNU Chess doesn't offer or accept Adjourns */
15150     }
15151 }
15152
15153
15154 void
15155 AbortEvent ()
15156 {
15157     /* Offer Abort or accept pending Abort offer from opponent */
15158
15159     if (appData.icsActive) {
15160         SendToICS(ics_prefix);
15161         SendToICS("abort\n");
15162     } else {
15163         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
15164     }
15165 }
15166
15167 void
15168 ResignEvent ()
15169 {
15170     /* Resign.  You can do this even if it's not your turn. */
15171
15172     if (appData.icsActive) {
15173         SendToICS(ics_prefix);
15174         SendToICS("resign\n");
15175     } else {
15176         switch (gameMode) {
15177           case MachinePlaysWhite:
15178             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15179             break;
15180           case MachinePlaysBlack:
15181             GameEnds(BlackWins, "White resigns", GE_PLAYER);
15182             break;
15183           case EditGame:
15184             if (cmailMsgLoaded) {
15185                 TruncateGame();
15186                 if (WhiteOnMove(cmailOldMove)) {
15187                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
15188                 } else {
15189                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15190                 }
15191                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
15192             }
15193             break;
15194           default:
15195             break;
15196         }
15197     }
15198 }
15199
15200
15201 void
15202 StopObservingEvent ()
15203 {
15204     /* Stop observing current games */
15205     SendToICS(ics_prefix);
15206     SendToICS("unobserve\n");
15207 }
15208
15209 void
15210 StopExaminingEvent ()
15211 {
15212     /* Stop observing current game */
15213     SendToICS(ics_prefix);
15214     SendToICS("unexamine\n");
15215 }
15216
15217 void
15218 ForwardInner (int target)
15219 {
15220     int limit; int oldSeekGraphUp = seekGraphUp;
15221
15222     if (appData.debugMode)
15223         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
15224                 target, currentMove, forwardMostMove);
15225
15226     if (gameMode == EditPosition)
15227       return;
15228
15229     seekGraphUp = FALSE;
15230     MarkTargetSquares(1);
15231
15232     if (gameMode == PlayFromGameFile && !pausing)
15233       PauseEvent();
15234
15235     if (gameMode == IcsExamining && pausing)
15236       limit = pauseExamForwardMostMove;
15237     else
15238       limit = forwardMostMove;
15239
15240     if (target > limit) target = limit;
15241
15242     if (target > 0 && moveList[target - 1][0]) {
15243         int fromX, fromY, toX, toY;
15244         toX = moveList[target - 1][2] - AAA;
15245         toY = moveList[target - 1][3] - ONE;
15246         if (moveList[target - 1][1] == '@') {
15247             if (appData.highlightLastMove) {
15248                 SetHighlights(-1, -1, toX, toY);
15249             }
15250         } else {
15251             fromX = moveList[target - 1][0] - AAA;
15252             fromY = moveList[target - 1][1] - ONE;
15253             if (target == currentMove + 1) {
15254                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
15255             }
15256             if (appData.highlightLastMove) {
15257                 SetHighlights(fromX, fromY, toX, toY);
15258             }
15259         }
15260     }
15261     if (gameMode == EditGame || gameMode == AnalyzeMode ||
15262         gameMode == Training || gameMode == PlayFromGameFile ||
15263         gameMode == AnalyzeFile) {
15264         while (currentMove < target) {
15265             if(second.analyzing) SendMoveToProgram(currentMove, &second);
15266             SendMoveToProgram(currentMove++, &first);
15267         }
15268     } else {
15269         currentMove = target;
15270     }
15271
15272     if (gameMode == EditGame || gameMode == EndOfGame) {
15273         whiteTimeRemaining = timeRemaining[0][currentMove];
15274         blackTimeRemaining = timeRemaining[1][currentMove];
15275     }
15276     DisplayBothClocks();
15277     DisplayMove(currentMove - 1);
15278     DrawPosition(oldSeekGraphUp, boards[currentMove]);
15279     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15280     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
15281         DisplayComment(currentMove - 1, commentList[currentMove]);
15282     }
15283     ClearMap(); // [HGM] exclude: invalidate map
15284 }
15285
15286
15287 void
15288 ForwardEvent ()
15289 {
15290     if (gameMode == IcsExamining && !pausing) {
15291         SendToICS(ics_prefix);
15292         SendToICS("forward\n");
15293     } else {
15294         ForwardInner(currentMove + 1);
15295     }
15296 }
15297
15298 void
15299 ToEndEvent ()
15300 {
15301     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15302         /* to optimze, we temporarily turn off analysis mode while we feed
15303          * the remaining moves to the engine. Otherwise we get analysis output
15304          * after each move.
15305          */
15306         if (first.analysisSupport) {
15307           SendToProgram("exit\nforce\n", &first);
15308           first.analyzing = FALSE;
15309         }
15310     }
15311
15312     if (gameMode == IcsExamining && !pausing) {
15313         SendToICS(ics_prefix);
15314         SendToICS("forward 999999\n");
15315     } else {
15316         ForwardInner(forwardMostMove);
15317     }
15318
15319     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15320         /* we have fed all the moves, so reactivate analysis mode */
15321         SendToProgram("analyze\n", &first);
15322         first.analyzing = TRUE;
15323         /*first.maybeThinking = TRUE;*/
15324         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15325     }
15326 }
15327
15328 void
15329 BackwardInner (int target)
15330 {
15331     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
15332
15333     if (appData.debugMode)
15334         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
15335                 target, currentMove, forwardMostMove);
15336
15337     if (gameMode == EditPosition) return;
15338     seekGraphUp = FALSE;
15339     MarkTargetSquares(1);
15340     if (currentMove <= backwardMostMove) {
15341         ClearHighlights();
15342         DrawPosition(full_redraw, boards[currentMove]);
15343         return;
15344     }
15345     if (gameMode == PlayFromGameFile && !pausing)
15346       PauseEvent();
15347
15348     if (moveList[target][0]) {
15349         int fromX, fromY, toX, toY;
15350         toX = moveList[target][2] - AAA;
15351         toY = moveList[target][3] - ONE;
15352         if (moveList[target][1] == '@') {
15353             if (appData.highlightLastMove) {
15354                 SetHighlights(-1, -1, toX, toY);
15355             }
15356         } else {
15357             fromX = moveList[target][0] - AAA;
15358             fromY = moveList[target][1] - ONE;
15359             if (target == currentMove - 1) {
15360                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
15361             }
15362             if (appData.highlightLastMove) {
15363                 SetHighlights(fromX, fromY, toX, toY);
15364             }
15365         }
15366     }
15367     if (gameMode == EditGame || gameMode==AnalyzeMode ||
15368         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
15369         while (currentMove > target) {
15370             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
15371                 // null move cannot be undone. Reload program with move history before it.
15372                 int i;
15373                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
15374                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
15375                 }
15376                 SendBoard(&first, i);
15377               if(second.analyzing) SendBoard(&second, i);
15378                 for(currentMove=i; currentMove<target; currentMove++) {
15379                     SendMoveToProgram(currentMove, &first);
15380                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
15381                 }
15382                 break;
15383             }
15384             SendToBoth("undo\n");
15385             currentMove--;
15386         }
15387     } else {
15388         currentMove = target;
15389     }
15390
15391     if (gameMode == EditGame || gameMode == EndOfGame) {
15392         whiteTimeRemaining = timeRemaining[0][currentMove];
15393         blackTimeRemaining = timeRemaining[1][currentMove];
15394     }
15395     DisplayBothClocks();
15396     DisplayMove(currentMove - 1);
15397     DrawPosition(full_redraw, boards[currentMove]);
15398     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15399     // [HGM] PV info: routine tests if comment empty
15400     DisplayComment(currentMove - 1, commentList[currentMove]);
15401     ClearMap(); // [HGM] exclude: invalidate map
15402 }
15403
15404 void
15405 BackwardEvent ()
15406 {
15407     if (gameMode == IcsExamining && !pausing) {
15408         SendToICS(ics_prefix);
15409         SendToICS("backward\n");
15410     } else {
15411         BackwardInner(currentMove - 1);
15412     }
15413 }
15414
15415 void
15416 ToStartEvent ()
15417 {
15418     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15419         /* to optimize, we temporarily turn off analysis mode while we undo
15420          * all the moves. Otherwise we get analysis output after each undo.
15421          */
15422         if (first.analysisSupport) {
15423           SendToProgram("exit\nforce\n", &first);
15424           first.analyzing = FALSE;
15425         }
15426     }
15427
15428     if (gameMode == IcsExamining && !pausing) {
15429         SendToICS(ics_prefix);
15430         SendToICS("backward 999999\n");
15431     } else {
15432         BackwardInner(backwardMostMove);
15433     }
15434
15435     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15436         /* we have fed all the moves, so reactivate analysis mode */
15437         SendToProgram("analyze\n", &first);
15438         first.analyzing = TRUE;
15439         /*first.maybeThinking = TRUE;*/
15440         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15441     }
15442 }
15443
15444 void
15445 ToNrEvent (int to)
15446 {
15447   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
15448   if (to >= forwardMostMove) to = forwardMostMove;
15449   if (to <= backwardMostMove) to = backwardMostMove;
15450   if (to < currentMove) {
15451     BackwardInner(to);
15452   } else {
15453     ForwardInner(to);
15454   }
15455 }
15456
15457 void
15458 RevertEvent (Boolean annotate)
15459 {
15460     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
15461         return;
15462     }
15463     if (gameMode != IcsExamining) {
15464         DisplayError(_("You are not examining a game"), 0);
15465         return;
15466     }
15467     if (pausing) {
15468         DisplayError(_("You can't revert while pausing"), 0);
15469         return;
15470     }
15471     SendToICS(ics_prefix);
15472     SendToICS("revert\n");
15473 }
15474
15475 void
15476 RetractMoveEvent ()
15477 {
15478     switch (gameMode) {
15479       case MachinePlaysWhite:
15480       case MachinePlaysBlack:
15481         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15482             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
15483             return;
15484         }
15485         if (forwardMostMove < 2) return;
15486         currentMove = forwardMostMove = forwardMostMove - 2;
15487         whiteTimeRemaining = timeRemaining[0][currentMove];
15488         blackTimeRemaining = timeRemaining[1][currentMove];
15489         DisplayBothClocks();
15490         DisplayMove(currentMove - 1);
15491         ClearHighlights();/*!! could figure this out*/
15492         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
15493         SendToProgram("remove\n", &first);
15494         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
15495         break;
15496
15497       case BeginningOfGame:
15498       default:
15499         break;
15500
15501       case IcsPlayingWhite:
15502       case IcsPlayingBlack:
15503         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
15504             SendToICS(ics_prefix);
15505             SendToICS("takeback 2\n");
15506         } else {
15507             SendToICS(ics_prefix);
15508             SendToICS("takeback 1\n");
15509         }
15510         break;
15511     }
15512 }
15513
15514 void
15515 MoveNowEvent ()
15516 {
15517     ChessProgramState *cps;
15518
15519     switch (gameMode) {
15520       case MachinePlaysWhite:
15521         if (!WhiteOnMove(forwardMostMove)) {
15522             DisplayError(_("It is your turn"), 0);
15523             return;
15524         }
15525         cps = &first;
15526         break;
15527       case MachinePlaysBlack:
15528         if (WhiteOnMove(forwardMostMove)) {
15529             DisplayError(_("It is your turn"), 0);
15530             return;
15531         }
15532         cps = &first;
15533         break;
15534       case TwoMachinesPlay:
15535         if (WhiteOnMove(forwardMostMove) ==
15536             (first.twoMachinesColor[0] == 'w')) {
15537             cps = &first;
15538         } else {
15539             cps = &second;
15540         }
15541         break;
15542       case BeginningOfGame:
15543       default:
15544         return;
15545     }
15546     SendToProgram("?\n", cps);
15547 }
15548
15549 void
15550 TruncateGameEvent ()
15551 {
15552     EditGameEvent();
15553     if (gameMode != EditGame) return;
15554     TruncateGame();
15555 }
15556
15557 void
15558 TruncateGame ()
15559 {
15560     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
15561     if (forwardMostMove > currentMove) {
15562         if (gameInfo.resultDetails != NULL) {
15563             free(gameInfo.resultDetails);
15564             gameInfo.resultDetails = NULL;
15565             gameInfo.result = GameUnfinished;
15566         }
15567         forwardMostMove = currentMove;
15568         HistorySet(parseList, backwardMostMove, forwardMostMove,
15569                    currentMove-1);
15570     }
15571 }
15572
15573 void
15574 HintEvent ()
15575 {
15576     if (appData.noChessProgram) return;
15577     switch (gameMode) {
15578       case MachinePlaysWhite:
15579         if (WhiteOnMove(forwardMostMove)) {
15580             DisplayError(_("Wait until your turn."), 0);
15581             return;
15582         }
15583         break;
15584       case BeginningOfGame:
15585       case MachinePlaysBlack:
15586         if (!WhiteOnMove(forwardMostMove)) {
15587             DisplayError(_("Wait until your turn."), 0);
15588             return;
15589         }
15590         break;
15591       default:
15592         DisplayError(_("No hint available"), 0);
15593         return;
15594     }
15595     SendToProgram("hint\n", &first);
15596     hintRequested = TRUE;
15597 }
15598
15599 void
15600 CreateBookEvent ()
15601 {
15602     ListGame * lg = (ListGame *) gameList.head;
15603     FILE *f, *g;
15604     int nItem;
15605     static int secondTime = FALSE;
15606
15607     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
15608         DisplayError(_("Game list not loaded or empty"), 0);
15609         return;
15610     }
15611
15612     if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
15613         fclose(g);
15614         secondTime++;
15615         DisplayNote(_("Book file exists! Try again for overwrite."));
15616         return;
15617     }
15618
15619     creatingBook = TRUE;
15620     secondTime = FALSE;
15621
15622     /* Get list size */
15623     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
15624         LoadGame(f, nItem, "", TRUE);
15625         AddGameToBook(TRUE);
15626         lg = (ListGame *) lg->node.succ;
15627     }
15628
15629     creatingBook = FALSE;
15630     FlushBook();
15631 }
15632
15633 void
15634 BookEvent ()
15635 {
15636     if (appData.noChessProgram) return;
15637     switch (gameMode) {
15638       case MachinePlaysWhite:
15639         if (WhiteOnMove(forwardMostMove)) {
15640             DisplayError(_("Wait until your turn."), 0);
15641             return;
15642         }
15643         break;
15644       case BeginningOfGame:
15645       case MachinePlaysBlack:
15646         if (!WhiteOnMove(forwardMostMove)) {
15647             DisplayError(_("Wait until your turn."), 0);
15648             return;
15649         }
15650         break;
15651       case EditPosition:
15652         EditPositionDone(TRUE);
15653         break;
15654       case TwoMachinesPlay:
15655         return;
15656       default:
15657         break;
15658     }
15659     SendToProgram("bk\n", &first);
15660     bookOutput[0] = NULLCHAR;
15661     bookRequested = TRUE;
15662 }
15663
15664 void
15665 AboutGameEvent ()
15666 {
15667     char *tags = PGNTags(&gameInfo);
15668     TagsPopUp(tags, CmailMsg());
15669     free(tags);
15670 }
15671
15672 /* end button procedures */
15673
15674 void
15675 PrintPosition (FILE *fp, int move)
15676 {
15677     int i, j;
15678
15679     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15680         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15681             char c = PieceToChar(boards[move][i][j]);
15682             fputc(c == 'x' ? '.' : c, fp);
15683             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
15684         }
15685     }
15686     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
15687       fprintf(fp, "white to play\n");
15688     else
15689       fprintf(fp, "black to play\n");
15690 }
15691
15692 void
15693 PrintOpponents (FILE *fp)
15694 {
15695     if (gameInfo.white != NULL) {
15696         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
15697     } else {
15698         fprintf(fp, "\n");
15699     }
15700 }
15701
15702 /* Find last component of program's own name, using some heuristics */
15703 void
15704 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
15705 {
15706     char *p, *q, c;
15707     int local = (strcmp(host, "localhost") == 0);
15708     while (!local && (p = strchr(prog, ';')) != NULL) {
15709         p++;
15710         while (*p == ' ') p++;
15711         prog = p;
15712     }
15713     if (*prog == '"' || *prog == '\'') {
15714         q = strchr(prog + 1, *prog);
15715     } else {
15716         q = strchr(prog, ' ');
15717     }
15718     if (q == NULL) q = prog + strlen(prog);
15719     p = q;
15720     while (p >= prog && *p != '/' && *p != '\\') p--;
15721     p++;
15722     if(p == prog && *p == '"') p++;
15723     c = *q; *q = 0;
15724     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
15725     memcpy(buf, p, q - p);
15726     buf[q - p] = NULLCHAR;
15727     if (!local) {
15728         strcat(buf, "@");
15729         strcat(buf, host);
15730     }
15731 }
15732
15733 char *
15734 TimeControlTagValue ()
15735 {
15736     char buf[MSG_SIZ];
15737     if (!appData.clockMode) {
15738       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
15739     } else if (movesPerSession > 0) {
15740       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
15741     } else if (timeIncrement == 0) {
15742       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
15743     } else {
15744       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
15745     }
15746     return StrSave(buf);
15747 }
15748
15749 void
15750 SetGameInfo ()
15751 {
15752     /* This routine is used only for certain modes */
15753     VariantClass v = gameInfo.variant;
15754     ChessMove r = GameUnfinished;
15755     char *p = NULL;
15756
15757     if(keepInfo) return;
15758
15759     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
15760         r = gameInfo.result;
15761         p = gameInfo.resultDetails;
15762         gameInfo.resultDetails = NULL;
15763     }
15764     ClearGameInfo(&gameInfo);
15765     gameInfo.variant = v;
15766
15767     switch (gameMode) {
15768       case MachinePlaysWhite:
15769         gameInfo.event = StrSave( appData.pgnEventHeader );
15770         gameInfo.site = StrSave(HostName());
15771         gameInfo.date = PGNDate();
15772         gameInfo.round = StrSave("-");
15773         gameInfo.white = StrSave(first.tidy);
15774         gameInfo.black = StrSave(UserName());
15775         gameInfo.timeControl = TimeControlTagValue();
15776         break;
15777
15778       case MachinePlaysBlack:
15779         gameInfo.event = StrSave( appData.pgnEventHeader );
15780         gameInfo.site = StrSave(HostName());
15781         gameInfo.date = PGNDate();
15782         gameInfo.round = StrSave("-");
15783         gameInfo.white = StrSave(UserName());
15784         gameInfo.black = StrSave(first.tidy);
15785         gameInfo.timeControl = TimeControlTagValue();
15786         break;
15787
15788       case TwoMachinesPlay:
15789         gameInfo.event = StrSave( appData.pgnEventHeader );
15790         gameInfo.site = StrSave(HostName());
15791         gameInfo.date = PGNDate();
15792         if (roundNr > 0) {
15793             char buf[MSG_SIZ];
15794             snprintf(buf, MSG_SIZ, "%d", roundNr);
15795             gameInfo.round = StrSave(buf);
15796         } else {
15797             gameInfo.round = StrSave("-");
15798         }
15799         if (first.twoMachinesColor[0] == 'w') {
15800             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15801             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15802         } else {
15803             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15804             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15805         }
15806         gameInfo.timeControl = TimeControlTagValue();
15807         break;
15808
15809       case EditGame:
15810         gameInfo.event = StrSave("Edited game");
15811         gameInfo.site = StrSave(HostName());
15812         gameInfo.date = PGNDate();
15813         gameInfo.round = StrSave("-");
15814         gameInfo.white = StrSave("-");
15815         gameInfo.black = StrSave("-");
15816         gameInfo.result = r;
15817         gameInfo.resultDetails = p;
15818         break;
15819
15820       case EditPosition:
15821         gameInfo.event = StrSave("Edited position");
15822         gameInfo.site = StrSave(HostName());
15823         gameInfo.date = PGNDate();
15824         gameInfo.round = StrSave("-");
15825         gameInfo.white = StrSave("-");
15826         gameInfo.black = StrSave("-");
15827         break;
15828
15829       case IcsPlayingWhite:
15830       case IcsPlayingBlack:
15831       case IcsObserving:
15832       case IcsExamining:
15833         break;
15834
15835       case PlayFromGameFile:
15836         gameInfo.event = StrSave("Game from non-PGN file");
15837         gameInfo.site = StrSave(HostName());
15838         gameInfo.date = PGNDate();
15839         gameInfo.round = StrSave("-");
15840         gameInfo.white = StrSave("?");
15841         gameInfo.black = StrSave("?");
15842         break;
15843
15844       default:
15845         break;
15846     }
15847 }
15848
15849 void
15850 ReplaceComment (int index, char *text)
15851 {
15852     int len;
15853     char *p;
15854     float score;
15855
15856     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
15857        pvInfoList[index-1].depth == len &&
15858        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
15859        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
15860     while (*text == '\n') text++;
15861     len = strlen(text);
15862     while (len > 0 && text[len - 1] == '\n') len--;
15863
15864     if (commentList[index] != NULL)
15865       free(commentList[index]);
15866
15867     if (len == 0) {
15868         commentList[index] = NULL;
15869         return;
15870     }
15871   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
15872       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
15873       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
15874     commentList[index] = (char *) malloc(len + 2);
15875     strncpy(commentList[index], text, len);
15876     commentList[index][len] = '\n';
15877     commentList[index][len + 1] = NULLCHAR;
15878   } else {
15879     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
15880     char *p;
15881     commentList[index] = (char *) malloc(len + 7);
15882     safeStrCpy(commentList[index], "{\n", 3);
15883     safeStrCpy(commentList[index]+2, text, len+1);
15884     commentList[index][len+2] = NULLCHAR;
15885     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
15886     strcat(commentList[index], "\n}\n");
15887   }
15888 }
15889
15890 void
15891 CrushCRs (char *text)
15892 {
15893   char *p = text;
15894   char *q = text;
15895   char ch;
15896
15897   do {
15898     ch = *p++;
15899     if (ch == '\r') continue;
15900     *q++ = ch;
15901   } while (ch != '\0');
15902 }
15903
15904 void
15905 AppendComment (int index, char *text, Boolean addBraces)
15906 /* addBraces  tells if we should add {} */
15907 {
15908     int oldlen, len;
15909     char *old;
15910
15911 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
15912     if(addBraces == 3) addBraces = 0; else // force appending literally
15913     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
15914
15915     CrushCRs(text);
15916     while (*text == '\n') text++;
15917     len = strlen(text);
15918     while (len > 0 && text[len - 1] == '\n') len--;
15919     text[len] = NULLCHAR;
15920
15921     if (len == 0) return;
15922
15923     if (commentList[index] != NULL) {
15924       Boolean addClosingBrace = addBraces;
15925         old = commentList[index];
15926         oldlen = strlen(old);
15927         while(commentList[index][oldlen-1] ==  '\n')
15928           commentList[index][--oldlen] = NULLCHAR;
15929         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
15930         safeStrCpy(commentList[index], old, oldlen + len + 6);
15931         free(old);
15932         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
15933         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
15934           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
15935           while (*text == '\n') { text++; len--; }
15936           commentList[index][--oldlen] = NULLCHAR;
15937       }
15938         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
15939         else          strcat(commentList[index], "\n");
15940         strcat(commentList[index], text);
15941         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
15942         else          strcat(commentList[index], "\n");
15943     } else {
15944         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
15945         if(addBraces)
15946           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
15947         else commentList[index][0] = NULLCHAR;
15948         strcat(commentList[index], text);
15949         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
15950         if(addBraces == TRUE) strcat(commentList[index], "}\n");
15951     }
15952 }
15953
15954 static char *
15955 FindStr (char * text, char * sub_text)
15956 {
15957     char * result = strstr( text, sub_text );
15958
15959     if( result != NULL ) {
15960         result += strlen( sub_text );
15961     }
15962
15963     return result;
15964 }
15965
15966 /* [AS] Try to extract PV info from PGN comment */
15967 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
15968 char *
15969 GetInfoFromComment (int index, char * text)
15970 {
15971     char * sep = text, *p;
15972
15973     if( text != NULL && index > 0 ) {
15974         int score = 0;
15975         int depth = 0;
15976         int time = -1, sec = 0, deci;
15977         char * s_eval = FindStr( text, "[%eval " );
15978         char * s_emt = FindStr( text, "[%emt " );
15979 #if 0
15980         if( s_eval != NULL || s_emt != NULL ) {
15981 #else
15982         if(0) { // [HGM] this code is not finished, and could actually be detrimental
15983 #endif
15984             /* New style */
15985             char delim;
15986
15987             if( s_eval != NULL ) {
15988                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
15989                     return text;
15990                 }
15991
15992                 if( delim != ']' ) {
15993                     return text;
15994                 }
15995             }
15996
15997             if( s_emt != NULL ) {
15998             }
15999                 return text;
16000         }
16001         else {
16002             /* We expect something like: [+|-]nnn.nn/dd */
16003             int score_lo = 0;
16004
16005             if(*text != '{') return text; // [HGM] braces: must be normal comment
16006
16007             sep = strchr( text, '/' );
16008             if( sep == NULL || sep < (text+4) ) {
16009                 return text;
16010             }
16011
16012             p = text;
16013             if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
16014             if(p[1] == '(') { // comment starts with PV
16015                p = strchr(p, ')'); // locate end of PV
16016                if(p == NULL || sep < p+5) return text;
16017                // at this point we have something like "{(.*) +0.23/6 ..."
16018                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
16019                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
16020                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
16021             }
16022             time = -1; sec = -1; deci = -1;
16023             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
16024                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
16025                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
16026                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
16027                 return text;
16028             }
16029
16030             if( score_lo < 0 || score_lo >= 100 ) {
16031                 return text;
16032             }
16033
16034             if(sec >= 0) time = 600*time + 10*sec; else
16035             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
16036
16037             score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
16038
16039             /* [HGM] PV time: now locate end of PV info */
16040             while( *++sep >= '0' && *sep <= '9'); // strip depth
16041             if(time >= 0)
16042             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
16043             if(sec >= 0)
16044             while( *++sep >= '0' && *sep <= '9'); // strip seconds
16045             if(deci >= 0)
16046             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
16047             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
16048         }
16049
16050         if( depth <= 0 ) {
16051             return text;
16052         }
16053
16054         if( time < 0 ) {
16055             time = -1;
16056         }
16057
16058         pvInfoList[index-1].depth = depth;
16059         pvInfoList[index-1].score = score;
16060         pvInfoList[index-1].time  = 10*time; // centi-sec
16061         if(*sep == '}') *sep = 0; else *--sep = '{';
16062         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
16063     }
16064     return sep;
16065 }
16066
16067 void
16068 SendToProgram (char *message, ChessProgramState *cps)
16069 {
16070     int count, outCount, error;
16071     char buf[MSG_SIZ];
16072
16073     if (cps->pr == NoProc) return;
16074     Attention(cps);
16075
16076     if (appData.debugMode) {
16077         TimeMark now;
16078         GetTimeMark(&now);
16079         fprintf(debugFP, "%ld >%-6s: %s",
16080                 SubtractTimeMarks(&now, &programStartTime),
16081                 cps->which, message);
16082         if(serverFP)
16083             fprintf(serverFP, "%ld >%-6s: %s",
16084                 SubtractTimeMarks(&now, &programStartTime),
16085                 cps->which, message), fflush(serverFP);
16086     }
16087
16088     count = strlen(message);
16089     outCount = OutputToProcess(cps->pr, message, count, &error);
16090     if (outCount < count && !exiting
16091                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
16092       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
16093       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
16094         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16095             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16096                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16097                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16098                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16099             } else {
16100                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16101                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16102                 gameInfo.result = res;
16103             }
16104             gameInfo.resultDetails = StrSave(buf);
16105         }
16106         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16107         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16108     }
16109 }
16110
16111 void
16112 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
16113 {
16114     char *end_str;
16115     char buf[MSG_SIZ];
16116     ChessProgramState *cps = (ChessProgramState *)closure;
16117
16118     if (isr != cps->isr) return; /* Killed intentionally */
16119     if (count <= 0) {
16120         if (count == 0) {
16121             RemoveInputSource(cps->isr);
16122             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
16123                     _(cps->which), cps->program);
16124             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
16125             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16126                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16127                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16128                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16129                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16130                 } else {
16131                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16132                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16133                     gameInfo.result = res;
16134                 }
16135                 gameInfo.resultDetails = StrSave(buf);
16136             }
16137             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16138             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
16139         } else {
16140             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
16141                     _(cps->which), cps->program);
16142             RemoveInputSource(cps->isr);
16143
16144             /* [AS] Program is misbehaving badly... kill it */
16145             if( count == -2 ) {
16146                 DestroyChildProcess( cps->pr, 9 );
16147                 cps->pr = NoProc;
16148             }
16149
16150             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16151         }
16152         return;
16153     }
16154
16155     if ((end_str = strchr(message, '\r')) != NULL)
16156       *end_str = NULLCHAR;
16157     if ((end_str = strchr(message, '\n')) != NULL)
16158       *end_str = NULLCHAR;
16159
16160     if (appData.debugMode) {
16161         TimeMark now; int print = 1;
16162         char *quote = ""; char c; int i;
16163
16164         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
16165                 char start = message[0];
16166                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
16167                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
16168                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
16169                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
16170                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
16171                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
16172                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
16173                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
16174                    sscanf(message, "hint: %c", &c)!=1 &&
16175                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
16176                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
16177                     print = (appData.engineComments >= 2);
16178                 }
16179                 message[0] = start; // restore original message
16180         }
16181         if(print) {
16182                 GetTimeMark(&now);
16183                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
16184                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16185                         quote,
16186                         message);
16187                 if(serverFP)
16188                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
16189                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16190                         quote,
16191                         message), fflush(serverFP);
16192         }
16193     }
16194
16195     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
16196     if (appData.icsEngineAnalyze) {
16197         if (strstr(message, "whisper") != NULL ||
16198              strstr(message, "kibitz") != NULL ||
16199             strstr(message, "tellics") != NULL) return;
16200     }
16201
16202     HandleMachineMove(message, cps);
16203 }
16204
16205
16206 void
16207 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
16208 {
16209     char buf[MSG_SIZ];
16210     int seconds;
16211
16212     if( timeControl_2 > 0 ) {
16213         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
16214             tc = timeControl_2;
16215         }
16216     }
16217     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
16218     inc /= cps->timeOdds;
16219     st  /= cps->timeOdds;
16220
16221     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
16222
16223     if (st > 0) {
16224       /* Set exact time per move, normally using st command */
16225       if (cps->stKludge) {
16226         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
16227         seconds = st % 60;
16228         if (seconds == 0) {
16229           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
16230         } else {
16231           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
16232         }
16233       } else {
16234         snprintf(buf, MSG_SIZ, "st %d\n", st);
16235       }
16236     } else {
16237       /* Set conventional or incremental time control, using level command */
16238       if (seconds == 0) {
16239         /* Note old gnuchess bug -- minutes:seconds used to not work.
16240            Fixed in later versions, but still avoid :seconds
16241            when seconds is 0. */
16242         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
16243       } else {
16244         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
16245                  seconds, inc/1000.);
16246       }
16247     }
16248     SendToProgram(buf, cps);
16249
16250     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
16251     /* Orthogonally, limit search to given depth */
16252     if (sd > 0) {
16253       if (cps->sdKludge) {
16254         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
16255       } else {
16256         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
16257       }
16258       SendToProgram(buf, cps);
16259     }
16260
16261     if(cps->nps >= 0) { /* [HGM] nps */
16262         if(cps->supportsNPS == FALSE)
16263           cps->nps = -1; // don't use if engine explicitly says not supported!
16264         else {
16265           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
16266           SendToProgram(buf, cps);
16267         }
16268     }
16269 }
16270
16271 ChessProgramState *
16272 WhitePlayer ()
16273 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
16274 {
16275     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
16276        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
16277         return &second;
16278     return &first;
16279 }
16280
16281 void
16282 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
16283 {
16284     char message[MSG_SIZ];
16285     long time, otime;
16286
16287     /* Note: this routine must be called when the clocks are stopped
16288        or when they have *just* been set or switched; otherwise
16289        it will be off by the time since the current tick started.
16290     */
16291     if (machineWhite) {
16292         time = whiteTimeRemaining / 10;
16293         otime = blackTimeRemaining / 10;
16294     } else {
16295         time = blackTimeRemaining / 10;
16296         otime = whiteTimeRemaining / 10;
16297     }
16298     /* [HGM] translate opponent's time by time-odds factor */
16299     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
16300
16301     if (time <= 0) time = 1;
16302     if (otime <= 0) otime = 1;
16303
16304     snprintf(message, MSG_SIZ, "time %ld\n", time);
16305     SendToProgram(message, cps);
16306
16307     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
16308     SendToProgram(message, cps);
16309 }
16310
16311 char *
16312 EngineDefinedVariant (ChessProgramState *cps, int n)
16313 {   // return name of n-th unknown variant that engine supports
16314     static char buf[MSG_SIZ];
16315     char *p, *s = cps->variants;
16316     if(!s) return NULL;
16317     do { // parse string from variants feature
16318       VariantClass v;
16319         p = strchr(s, ',');
16320         if(p) *p = NULLCHAR;
16321       v = StringToVariant(s);
16322       if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
16323         if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
16324             if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
16325         }
16326         if(p) *p++ = ',';
16327         if(n < 0) return buf;
16328     } while(s = p);
16329     return NULL;
16330 }
16331
16332 int
16333 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16334 {
16335   char buf[MSG_SIZ];
16336   int len = strlen(name);
16337   int val;
16338
16339   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16340     (*p) += len + 1;
16341     sscanf(*p, "%d", &val);
16342     *loc = (val != 0);
16343     while (**p && **p != ' ')
16344       (*p)++;
16345     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16346     SendToProgram(buf, cps);
16347     return TRUE;
16348   }
16349   return FALSE;
16350 }
16351
16352 int
16353 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16354 {
16355   char buf[MSG_SIZ];
16356   int len = strlen(name);
16357   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16358     (*p) += len + 1;
16359     sscanf(*p, "%d", loc);
16360     while (**p && **p != ' ') (*p)++;
16361     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16362     SendToProgram(buf, cps);
16363     return TRUE;
16364   }
16365   return FALSE;
16366 }
16367
16368 int
16369 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
16370 {
16371   char buf[MSG_SIZ];
16372   int len = strlen(name);
16373   if (strncmp((*p), name, len) == 0
16374       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
16375     (*p) += len + 2;
16376     ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
16377     sscanf(*p, "%[^\"]", *loc);
16378     while (**p && **p != '\"') (*p)++;
16379     if (**p == '\"') (*p)++;
16380     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16381     SendToProgram(buf, cps);
16382     return TRUE;
16383   }
16384   return FALSE;
16385 }
16386
16387 int
16388 ParseOption (Option *opt, ChessProgramState *cps)
16389 // [HGM] options: process the string that defines an engine option, and determine
16390 // name, type, default value, and allowed value range
16391 {
16392         char *p, *q, buf[MSG_SIZ];
16393         int n, min = (-1)<<31, max = 1<<31, def;
16394
16395         if(p = strstr(opt->name, " -spin ")) {
16396             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16397             if(max < min) max = min; // enforce consistency
16398             if(def < min) def = min;
16399             if(def > max) def = max;
16400             opt->value = def;
16401             opt->min = min;
16402             opt->max = max;
16403             opt->type = Spin;
16404         } else if((p = strstr(opt->name, " -slider "))) {
16405             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
16406             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16407             if(max < min) max = min; // enforce consistency
16408             if(def < min) def = min;
16409             if(def > max) def = max;
16410             opt->value = def;
16411             opt->min = min;
16412             opt->max = max;
16413             opt->type = Spin; // Slider;
16414         } else if((p = strstr(opt->name, " -string "))) {
16415             opt->textValue = p+9;
16416             opt->type = TextBox;
16417         } else if((p = strstr(opt->name, " -file "))) {
16418             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16419             opt->textValue = p+7;
16420             opt->type = FileName; // FileName;
16421         } else if((p = strstr(opt->name, " -path "))) {
16422             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16423             opt->textValue = p+7;
16424             opt->type = PathName; // PathName;
16425         } else if(p = strstr(opt->name, " -check ")) {
16426             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
16427             opt->value = (def != 0);
16428             opt->type = CheckBox;
16429         } else if(p = strstr(opt->name, " -combo ")) {
16430             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
16431             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
16432             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
16433             opt->value = n = 0;
16434             while(q = StrStr(q, " /// ")) {
16435                 n++; *q = 0;    // count choices, and null-terminate each of them
16436                 q += 5;
16437                 if(*q == '*') { // remember default, which is marked with * prefix
16438                     q++;
16439                     opt->value = n;
16440                 }
16441                 cps->comboList[cps->comboCnt++] = q;
16442             }
16443             cps->comboList[cps->comboCnt++] = NULL;
16444             opt->max = n + 1;
16445             opt->type = ComboBox;
16446         } else if(p = strstr(opt->name, " -button")) {
16447             opt->type = Button;
16448         } else if(p = strstr(opt->name, " -save")) {
16449             opt->type = SaveButton;
16450         } else return FALSE;
16451         *p = 0; // terminate option name
16452         // now look if the command-line options define a setting for this engine option.
16453         if(cps->optionSettings && cps->optionSettings[0])
16454             p = strstr(cps->optionSettings, opt->name); else p = NULL;
16455         if(p && (p == cps->optionSettings || p[-1] == ',')) {
16456           snprintf(buf, MSG_SIZ, "option %s", p);
16457                 if(p = strstr(buf, ",")) *p = 0;
16458                 if(q = strchr(buf, '=')) switch(opt->type) {
16459                     case ComboBox:
16460                         for(n=0; n<opt->max; n++)
16461                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
16462                         break;
16463                     case TextBox:
16464                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
16465                         break;
16466                     case Spin:
16467                     case CheckBox:
16468                         opt->value = atoi(q+1);
16469                     default:
16470                         break;
16471                 }
16472                 strcat(buf, "\n");
16473                 SendToProgram(buf, cps);
16474         }
16475         return TRUE;
16476 }
16477
16478 void
16479 FeatureDone (ChessProgramState *cps, int val)
16480 {
16481   DelayedEventCallback cb = GetDelayedEvent();
16482   if ((cb == InitBackEnd3 && cps == &first) ||
16483       (cb == SettingsMenuIfReady && cps == &second) ||
16484       (cb == LoadEngine) ||
16485       (cb == TwoMachinesEventIfReady)) {
16486     CancelDelayedEvent();
16487     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
16488   }
16489   cps->initDone = val;
16490   if(val) cps->reload = FALSE;
16491 }
16492
16493 /* Parse feature command from engine */
16494 void
16495 ParseFeatures (char *args, ChessProgramState *cps)
16496 {
16497   char *p = args;
16498   char *q = NULL;
16499   int val;
16500   char buf[MSG_SIZ];
16501
16502   for (;;) {
16503     while (*p == ' ') p++;
16504     if (*p == NULLCHAR) return;
16505
16506     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
16507     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
16508     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
16509     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
16510     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
16511     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
16512     if (BoolFeature(&p, "reuse", &val, cps)) {
16513       /* Engine can disable reuse, but can't enable it if user said no */
16514       if (!val) cps->reuse = FALSE;
16515       continue;
16516     }
16517     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
16518     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
16519       if (gameMode == TwoMachinesPlay) {
16520         DisplayTwoMachinesTitle();
16521       } else {
16522         DisplayTitle("");
16523       }
16524       continue;
16525     }
16526     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
16527     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
16528     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
16529     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
16530     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
16531     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
16532     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
16533     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
16534     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
16535     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
16536     if (IntFeature(&p, "done", &val, cps)) {
16537       FeatureDone(cps, val);
16538       continue;
16539     }
16540     /* Added by Tord: */
16541     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
16542     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
16543     /* End of additions by Tord */
16544
16545     /* [HGM] added features: */
16546     if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
16547     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
16548     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
16549     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
16550     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
16551     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
16552     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
16553     if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
16554         if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
16555         FREE(cps->option[cps->nrOptions].name);
16556         cps->option[cps->nrOptions].name = q; q = NULL;
16557         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
16558           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
16559             SendToProgram(buf, cps);
16560             continue;
16561         }
16562         if(cps->nrOptions >= MAX_OPTIONS) {
16563             cps->nrOptions--;
16564             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
16565             DisplayError(buf, 0);
16566         }
16567         continue;
16568     }
16569     /* End of additions by HGM */
16570
16571     /* unknown feature: complain and skip */
16572     q = p;
16573     while (*q && *q != '=') q++;
16574     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
16575     SendToProgram(buf, cps);
16576     p = q;
16577     if (*p == '=') {
16578       p++;
16579       if (*p == '\"') {
16580         p++;
16581         while (*p && *p != '\"') p++;
16582         if (*p == '\"') p++;
16583       } else {
16584         while (*p && *p != ' ') p++;
16585       }
16586     }
16587   }
16588
16589 }
16590
16591 void
16592 PeriodicUpdatesEvent (int newState)
16593 {
16594     if (newState == appData.periodicUpdates)
16595       return;
16596
16597     appData.periodicUpdates=newState;
16598
16599     /* Display type changes, so update it now */
16600 //    DisplayAnalysis();
16601
16602     /* Get the ball rolling again... */
16603     if (newState) {
16604         AnalysisPeriodicEvent(1);
16605         StartAnalysisClock();
16606     }
16607 }
16608
16609 void
16610 PonderNextMoveEvent (int newState)
16611 {
16612     if (newState == appData.ponderNextMove) return;
16613     if (gameMode == EditPosition) EditPositionDone(TRUE);
16614     if (newState) {
16615         SendToProgram("hard\n", &first);
16616         if (gameMode == TwoMachinesPlay) {
16617             SendToProgram("hard\n", &second);
16618         }
16619     } else {
16620         SendToProgram("easy\n", &first);
16621         thinkOutput[0] = NULLCHAR;
16622         if (gameMode == TwoMachinesPlay) {
16623             SendToProgram("easy\n", &second);
16624         }
16625     }
16626     appData.ponderNextMove = newState;
16627 }
16628
16629 void
16630 NewSettingEvent (int option, int *feature, char *command, int value)
16631 {
16632     char buf[MSG_SIZ];
16633
16634     if (gameMode == EditPosition) EditPositionDone(TRUE);
16635     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
16636     if(feature == NULL || *feature) SendToProgram(buf, &first);
16637     if (gameMode == TwoMachinesPlay) {
16638         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
16639     }
16640 }
16641
16642 void
16643 ShowThinkingEvent ()
16644 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
16645 {
16646     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
16647     int newState = appData.showThinking
16648         // [HGM] thinking: other features now need thinking output as well
16649         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
16650
16651     if (oldState == newState) return;
16652     oldState = newState;
16653     if (gameMode == EditPosition) EditPositionDone(TRUE);
16654     if (oldState) {
16655         SendToProgram("post\n", &first);
16656         if (gameMode == TwoMachinesPlay) {
16657             SendToProgram("post\n", &second);
16658         }
16659     } else {
16660         SendToProgram("nopost\n", &first);
16661         thinkOutput[0] = NULLCHAR;
16662         if (gameMode == TwoMachinesPlay) {
16663             SendToProgram("nopost\n", &second);
16664         }
16665     }
16666 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
16667 }
16668
16669 void
16670 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
16671 {
16672   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
16673   if (pr == NoProc) return;
16674   AskQuestion(title, question, replyPrefix, pr);
16675 }
16676
16677 void
16678 TypeInEvent (char firstChar)
16679 {
16680     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
16681         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
16682         gameMode == AnalyzeMode || gameMode == EditGame ||
16683         gameMode == EditPosition || gameMode == IcsExamining ||
16684         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
16685         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
16686                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
16687                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
16688         gameMode == Training) PopUpMoveDialog(firstChar);
16689 }
16690
16691 void
16692 TypeInDoneEvent (char *move)
16693 {
16694         Board board;
16695         int n, fromX, fromY, toX, toY;
16696         char promoChar;
16697         ChessMove moveType;
16698
16699         // [HGM] FENedit
16700         if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
16701                 EditPositionPasteFEN(move);
16702                 return;
16703         }
16704         // [HGM] movenum: allow move number to be typed in any mode
16705         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
16706           ToNrEvent(2*n-1);
16707           return;
16708         }
16709         // undocumented kludge: allow command-line option to be typed in!
16710         // (potentially fatal, and does not implement the effect of the option.)
16711         // should only be used for options that are values on which future decisions will be made,
16712         // and definitely not on options that would be used during initialization.
16713         if(strstr(move, "!!! -") == move) {
16714             ParseArgsFromString(move+4);
16715             return;
16716         }
16717
16718       if (gameMode != EditGame && currentMove != forwardMostMove &&
16719         gameMode != Training) {
16720         DisplayMoveError(_("Displayed move is not current"));
16721       } else {
16722         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16723           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
16724         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
16725         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16726           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
16727           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
16728         } else {
16729           DisplayMoveError(_("Could not parse move"));
16730         }
16731       }
16732 }
16733
16734 void
16735 DisplayMove (int moveNumber)
16736 {
16737     char message[MSG_SIZ];
16738     char res[MSG_SIZ];
16739     char cpThinkOutput[MSG_SIZ];
16740
16741     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
16742
16743     if (moveNumber == forwardMostMove - 1 ||
16744         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16745
16746         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
16747
16748         if (strchr(cpThinkOutput, '\n')) {
16749             *strchr(cpThinkOutput, '\n') = NULLCHAR;
16750         }
16751     } else {
16752         *cpThinkOutput = NULLCHAR;
16753     }
16754
16755     /* [AS] Hide thinking from human user */
16756     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
16757         *cpThinkOutput = NULLCHAR;
16758         if( thinkOutput[0] != NULLCHAR ) {
16759             int i;
16760
16761             for( i=0; i<=hiddenThinkOutputState; i++ ) {
16762                 cpThinkOutput[i] = '.';
16763             }
16764             cpThinkOutput[i] = NULLCHAR;
16765             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
16766         }
16767     }
16768
16769     if (moveNumber == forwardMostMove - 1 &&
16770         gameInfo.resultDetails != NULL) {
16771         if (gameInfo.resultDetails[0] == NULLCHAR) {
16772           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
16773         } else {
16774           snprintf(res, MSG_SIZ, " {%s} %s",
16775                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
16776         }
16777     } else {
16778         res[0] = NULLCHAR;
16779     }
16780
16781     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16782         DisplayMessage(res, cpThinkOutput);
16783     } else {
16784       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
16785                 WhiteOnMove(moveNumber) ? " " : ".. ",
16786                 parseList[moveNumber], res);
16787         DisplayMessage(message, cpThinkOutput);
16788     }
16789 }
16790
16791 void
16792 DisplayComment (int moveNumber, char *text)
16793 {
16794     char title[MSG_SIZ];
16795
16796     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16797       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
16798     } else {
16799       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
16800               WhiteOnMove(moveNumber) ? " " : ".. ",
16801               parseList[moveNumber]);
16802     }
16803     if (text != NULL && (appData.autoDisplayComment || commentUp))
16804         CommentPopUp(title, text);
16805 }
16806
16807 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
16808  * might be busy thinking or pondering.  It can be omitted if your
16809  * gnuchess is configured to stop thinking immediately on any user
16810  * input.  However, that gnuchess feature depends on the FIONREAD
16811  * ioctl, which does not work properly on some flavors of Unix.
16812  */
16813 void
16814 Attention (ChessProgramState *cps)
16815 {
16816 #if ATTENTION
16817     if (!cps->useSigint) return;
16818     if (appData.noChessProgram || (cps->pr == NoProc)) return;
16819     switch (gameMode) {
16820       case MachinePlaysWhite:
16821       case MachinePlaysBlack:
16822       case TwoMachinesPlay:
16823       case IcsPlayingWhite:
16824       case IcsPlayingBlack:
16825       case AnalyzeMode:
16826       case AnalyzeFile:
16827         /* Skip if we know it isn't thinking */
16828         if (!cps->maybeThinking) return;
16829         if (appData.debugMode)
16830           fprintf(debugFP, "Interrupting %s\n", cps->which);
16831         InterruptChildProcess(cps->pr);
16832         cps->maybeThinking = FALSE;
16833         break;
16834       default:
16835         break;
16836     }
16837 #endif /*ATTENTION*/
16838 }
16839
16840 int
16841 CheckFlags ()
16842 {
16843     if (whiteTimeRemaining <= 0) {
16844         if (!whiteFlag) {
16845             whiteFlag = TRUE;
16846             if (appData.icsActive) {
16847                 if (appData.autoCallFlag &&
16848                     gameMode == IcsPlayingBlack && !blackFlag) {
16849                   SendToICS(ics_prefix);
16850                   SendToICS("flag\n");
16851                 }
16852             } else {
16853                 if (blackFlag) {
16854                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16855                 } else {
16856                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
16857                     if (appData.autoCallFlag) {
16858                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
16859                         return TRUE;
16860                     }
16861                 }
16862             }
16863         }
16864     }
16865     if (blackTimeRemaining <= 0) {
16866         if (!blackFlag) {
16867             blackFlag = TRUE;
16868             if (appData.icsActive) {
16869                 if (appData.autoCallFlag &&
16870                     gameMode == IcsPlayingWhite && !whiteFlag) {
16871                   SendToICS(ics_prefix);
16872                   SendToICS("flag\n");
16873                 }
16874             } else {
16875                 if (whiteFlag) {
16876                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16877                 } else {
16878                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
16879                     if (appData.autoCallFlag) {
16880                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
16881                         return TRUE;
16882                     }
16883                 }
16884             }
16885         }
16886     }
16887     return FALSE;
16888 }
16889
16890 void
16891 CheckTimeControl ()
16892 {
16893     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
16894         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
16895
16896     /*
16897      * add time to clocks when time control is achieved ([HGM] now also used for increment)
16898      */
16899     if ( !WhiteOnMove(forwardMostMove) ) {
16900         /* White made time control */
16901         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
16902         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
16903         /* [HGM] time odds: correct new time quota for time odds! */
16904                                             / WhitePlayer()->timeOdds;
16905         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
16906     } else {
16907         lastBlack -= blackTimeRemaining;
16908         /* Black made time control */
16909         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
16910                                             / WhitePlayer()->other->timeOdds;
16911         lastWhite = whiteTimeRemaining;
16912     }
16913 }
16914
16915 void
16916 DisplayBothClocks ()
16917 {
16918     int wom = gameMode == EditPosition ?
16919       !blackPlaysFirst : WhiteOnMove(currentMove);
16920     DisplayWhiteClock(whiteTimeRemaining, wom);
16921     DisplayBlackClock(blackTimeRemaining, !wom);
16922 }
16923
16924
16925 /* Timekeeping seems to be a portability nightmare.  I think everyone
16926    has ftime(), but I'm really not sure, so I'm including some ifdefs
16927    to use other calls if you don't.  Clocks will be less accurate if
16928    you have neither ftime nor gettimeofday.
16929 */
16930
16931 /* VS 2008 requires the #include outside of the function */
16932 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
16933 #include <sys/timeb.h>
16934 #endif
16935
16936 /* Get the current time as a TimeMark */
16937 void
16938 GetTimeMark (TimeMark *tm)
16939 {
16940 #if HAVE_GETTIMEOFDAY
16941
16942     struct timeval timeVal;
16943     struct timezone timeZone;
16944
16945     gettimeofday(&timeVal, &timeZone);
16946     tm->sec = (long) timeVal.tv_sec;
16947     tm->ms = (int) (timeVal.tv_usec / 1000L);
16948
16949 #else /*!HAVE_GETTIMEOFDAY*/
16950 #if HAVE_FTIME
16951
16952 // include <sys/timeb.h> / moved to just above start of function
16953     struct timeb timeB;
16954
16955     ftime(&timeB);
16956     tm->sec = (long) timeB.time;
16957     tm->ms = (int) timeB.millitm;
16958
16959 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
16960     tm->sec = (long) time(NULL);
16961     tm->ms = 0;
16962 #endif
16963 #endif
16964 }
16965
16966 /* Return the difference in milliseconds between two
16967    time marks.  We assume the difference will fit in a long!
16968 */
16969 long
16970 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
16971 {
16972     return 1000L*(tm2->sec - tm1->sec) +
16973            (long) (tm2->ms - tm1->ms);
16974 }
16975
16976
16977 /*
16978  * Code to manage the game clocks.
16979  *
16980  * In tournament play, black starts the clock and then white makes a move.
16981  * We give the human user a slight advantage if he is playing white---the
16982  * clocks don't run until he makes his first move, so it takes zero time.
16983  * Also, we don't account for network lag, so we could get out of sync
16984  * with GNU Chess's clock -- but then, referees are always right.
16985  */
16986
16987 static TimeMark tickStartTM;
16988 static long intendedTickLength;
16989
16990 long
16991 NextTickLength (long timeRemaining)
16992 {
16993     long nominalTickLength, nextTickLength;
16994
16995     if (timeRemaining > 0L && timeRemaining <= 10000L)
16996       nominalTickLength = 100L;
16997     else
16998       nominalTickLength = 1000L;
16999     nextTickLength = timeRemaining % nominalTickLength;
17000     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
17001
17002     return nextTickLength;
17003 }
17004
17005 /* Adjust clock one minute up or down */
17006 void
17007 AdjustClock (Boolean which, int dir)
17008 {
17009     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
17010     if(which) blackTimeRemaining += 60000*dir;
17011     else      whiteTimeRemaining += 60000*dir;
17012     DisplayBothClocks();
17013     adjustedClock = TRUE;
17014 }
17015
17016 /* Stop clocks and reset to a fresh time control */
17017 void
17018 ResetClocks ()
17019 {
17020     (void) StopClockTimer();
17021     if (appData.icsActive) {
17022         whiteTimeRemaining = blackTimeRemaining = 0;
17023     } else if (searchTime) {
17024         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17025         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17026     } else { /* [HGM] correct new time quote for time odds */
17027         whiteTC = blackTC = fullTimeControlString;
17028         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
17029         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
17030     }
17031     if (whiteFlag || blackFlag) {
17032         DisplayTitle("");
17033         whiteFlag = blackFlag = FALSE;
17034     }
17035     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
17036     DisplayBothClocks();
17037     adjustedClock = FALSE;
17038 }
17039
17040 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
17041
17042 /* Decrement running clock by amount of time that has passed */
17043 void
17044 DecrementClocks ()
17045 {
17046     long timeRemaining;
17047     long lastTickLength, fudge;
17048     TimeMark now;
17049
17050     if (!appData.clockMode) return;
17051     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
17052
17053     GetTimeMark(&now);
17054
17055     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17056
17057     /* Fudge if we woke up a little too soon */
17058     fudge = intendedTickLength - lastTickLength;
17059     if (fudge < 0 || fudge > FUDGE) fudge = 0;
17060
17061     if (WhiteOnMove(forwardMostMove)) {
17062         if(whiteNPS >= 0) lastTickLength = 0;
17063         timeRemaining = whiteTimeRemaining -= lastTickLength;
17064         if(timeRemaining < 0 && !appData.icsActive) {
17065             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
17066             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
17067                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
17068                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
17069             }
17070         }
17071         DisplayWhiteClock(whiteTimeRemaining - fudge,
17072                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17073     } else {
17074         if(blackNPS >= 0) lastTickLength = 0;
17075         timeRemaining = blackTimeRemaining -= lastTickLength;
17076         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
17077             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
17078             if(suddenDeath) {
17079                 blackStartMove = forwardMostMove;
17080                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
17081             }
17082         }
17083         DisplayBlackClock(blackTimeRemaining - fudge,
17084                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17085     }
17086     if (CheckFlags()) return;
17087
17088     if(twoBoards) { // count down secondary board's clocks as well
17089         activePartnerTime -= lastTickLength;
17090         partnerUp = 1;
17091         if(activePartner == 'W')
17092             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
17093         else
17094             DisplayBlackClock(activePartnerTime, TRUE);
17095         partnerUp = 0;
17096     }
17097
17098     tickStartTM = now;
17099     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
17100     StartClockTimer(intendedTickLength);
17101
17102     /* if the time remaining has fallen below the alarm threshold, sound the
17103      * alarm. if the alarm has sounded and (due to a takeback or time control
17104      * with increment) the time remaining has increased to a level above the
17105      * threshold, reset the alarm so it can sound again.
17106      */
17107
17108     if (appData.icsActive && appData.icsAlarm) {
17109
17110         /* make sure we are dealing with the user's clock */
17111         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
17112                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
17113            )) return;
17114
17115         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
17116             alarmSounded = FALSE;
17117         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
17118             PlayAlarmSound();
17119             alarmSounded = TRUE;
17120         }
17121     }
17122 }
17123
17124
17125 /* A player has just moved, so stop the previously running
17126    clock and (if in clock mode) start the other one.
17127    We redisplay both clocks in case we're in ICS mode, because
17128    ICS gives us an update to both clocks after every move.
17129    Note that this routine is called *after* forwardMostMove
17130    is updated, so the last fractional tick must be subtracted
17131    from the color that is *not* on move now.
17132 */
17133 void
17134 SwitchClocks (int newMoveNr)
17135 {
17136     long lastTickLength;
17137     TimeMark now;
17138     int flagged = FALSE;
17139
17140     GetTimeMark(&now);
17141
17142     if (StopClockTimer() && appData.clockMode) {
17143         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17144         if (!WhiteOnMove(forwardMostMove)) {
17145             if(blackNPS >= 0) lastTickLength = 0;
17146             blackTimeRemaining -= lastTickLength;
17147            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17148 //         if(pvInfoList[forwardMostMove].time == -1)
17149                  pvInfoList[forwardMostMove].time =               // use GUI time
17150                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
17151         } else {
17152            if(whiteNPS >= 0) lastTickLength = 0;
17153            whiteTimeRemaining -= lastTickLength;
17154            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17155 //         if(pvInfoList[forwardMostMove].time == -1)
17156                  pvInfoList[forwardMostMove].time =
17157                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
17158         }
17159         flagged = CheckFlags();
17160     }
17161     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
17162     CheckTimeControl();
17163
17164     if (flagged || !appData.clockMode) return;
17165
17166     switch (gameMode) {
17167       case MachinePlaysBlack:
17168       case MachinePlaysWhite:
17169       case BeginningOfGame:
17170         if (pausing) return;
17171         break;
17172
17173       case EditGame:
17174       case PlayFromGameFile:
17175       case IcsExamining:
17176         return;
17177
17178       default:
17179         break;
17180     }
17181
17182     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
17183         if(WhiteOnMove(forwardMostMove))
17184              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17185         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17186     }
17187
17188     tickStartTM = now;
17189     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17190       whiteTimeRemaining : blackTimeRemaining);
17191     StartClockTimer(intendedTickLength);
17192 }
17193
17194
17195 /* Stop both clocks */
17196 void
17197 StopClocks ()
17198 {
17199     long lastTickLength;
17200     TimeMark now;
17201
17202     if (!StopClockTimer()) return;
17203     if (!appData.clockMode) return;
17204
17205     GetTimeMark(&now);
17206
17207     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17208     if (WhiteOnMove(forwardMostMove)) {
17209         if(whiteNPS >= 0) lastTickLength = 0;
17210         whiteTimeRemaining -= lastTickLength;
17211         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
17212     } else {
17213         if(blackNPS >= 0) lastTickLength = 0;
17214         blackTimeRemaining -= lastTickLength;
17215         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
17216     }
17217     CheckFlags();
17218 }
17219
17220 /* Start clock of player on move.  Time may have been reset, so
17221    if clock is already running, stop and restart it. */
17222 void
17223 StartClocks ()
17224 {
17225     (void) StopClockTimer(); /* in case it was running already */
17226     DisplayBothClocks();
17227     if (CheckFlags()) return;
17228
17229     if (!appData.clockMode) return;
17230     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
17231
17232     GetTimeMark(&tickStartTM);
17233     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17234       whiteTimeRemaining : blackTimeRemaining);
17235
17236    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
17237     whiteNPS = blackNPS = -1;
17238     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
17239        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
17240         whiteNPS = first.nps;
17241     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
17242        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
17243         blackNPS = first.nps;
17244     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
17245         whiteNPS = second.nps;
17246     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
17247         blackNPS = second.nps;
17248     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
17249
17250     StartClockTimer(intendedTickLength);
17251 }
17252
17253 char *
17254 TimeString (long ms)
17255 {
17256     long second, minute, hour, day;
17257     char *sign = "";
17258     static char buf[32];
17259
17260     if (ms > 0 && ms <= 9900) {
17261       /* convert milliseconds to tenths, rounding up */
17262       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
17263
17264       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
17265       return buf;
17266     }
17267
17268     /* convert milliseconds to seconds, rounding up */
17269     /* use floating point to avoid strangeness of integer division
17270        with negative dividends on many machines */
17271     second = (long) floor(((double) (ms + 999L)) / 1000.0);
17272
17273     if (second < 0) {
17274         sign = "-";
17275         second = -second;
17276     }
17277
17278     day = second / (60 * 60 * 24);
17279     second = second % (60 * 60 * 24);
17280     hour = second / (60 * 60);
17281     second = second % (60 * 60);
17282     minute = second / 60;
17283     second = second % 60;
17284
17285     if (day > 0)
17286       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
17287               sign, day, hour, minute, second);
17288     else if (hour > 0)
17289       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
17290     else
17291       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
17292
17293     return buf;
17294 }
17295
17296
17297 /*
17298  * This is necessary because some C libraries aren't ANSI C compliant yet.
17299  */
17300 char *
17301 StrStr (char *string, char *match)
17302 {
17303     int i, length;
17304
17305     length = strlen(match);
17306
17307     for (i = strlen(string) - length; i >= 0; i--, string++)
17308       if (!strncmp(match, string, length))
17309         return string;
17310
17311     return NULL;
17312 }
17313
17314 char *
17315 StrCaseStr (char *string, char *match)
17316 {
17317     int i, j, length;
17318
17319     length = strlen(match);
17320
17321     for (i = strlen(string) - length; i >= 0; i--, string++) {
17322         for (j = 0; j < length; j++) {
17323             if (ToLower(match[j]) != ToLower(string[j]))
17324               break;
17325         }
17326         if (j == length) return string;
17327     }
17328
17329     return NULL;
17330 }
17331
17332 #ifndef _amigados
17333 int
17334 StrCaseCmp (char *s1, char *s2)
17335 {
17336     char c1, c2;
17337
17338     for (;;) {
17339         c1 = ToLower(*s1++);
17340         c2 = ToLower(*s2++);
17341         if (c1 > c2) return 1;
17342         if (c1 < c2) return -1;
17343         if (c1 == NULLCHAR) return 0;
17344     }
17345 }
17346
17347
17348 int
17349 ToLower (int c)
17350 {
17351     return isupper(c) ? tolower(c) : c;
17352 }
17353
17354
17355 int
17356 ToUpper (int c)
17357 {
17358     return islower(c) ? toupper(c) : c;
17359 }
17360 #endif /* !_amigados    */
17361
17362 char *
17363 StrSave (char *s)
17364 {
17365   char *ret;
17366
17367   if ((ret = (char *) malloc(strlen(s) + 1)))
17368     {
17369       safeStrCpy(ret, s, strlen(s)+1);
17370     }
17371   return ret;
17372 }
17373
17374 char *
17375 StrSavePtr (char *s, char **savePtr)
17376 {
17377     if (*savePtr) {
17378         free(*savePtr);
17379     }
17380     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
17381       safeStrCpy(*savePtr, s, strlen(s)+1);
17382     }
17383     return(*savePtr);
17384 }
17385
17386 char *
17387 PGNDate ()
17388 {
17389     time_t clock;
17390     struct tm *tm;
17391     char buf[MSG_SIZ];
17392
17393     clock = time((time_t *)NULL);
17394     tm = localtime(&clock);
17395     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
17396             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
17397     return StrSave(buf);
17398 }
17399
17400
17401 char *
17402 PositionToFEN (int move, char *overrideCastling, int moveCounts)
17403 {
17404     int i, j, fromX, fromY, toX, toY;
17405     int whiteToPlay;
17406     char buf[MSG_SIZ];
17407     char *p, *q;
17408     int emptycount;
17409     ChessSquare piece;
17410
17411     whiteToPlay = (gameMode == EditPosition) ?
17412       !blackPlaysFirst : (move % 2 == 0);
17413     p = buf;
17414
17415     /* Piece placement data */
17416     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17417         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
17418         emptycount = 0;
17419         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
17420             if (boards[move][i][j] == EmptySquare) {
17421                 emptycount++;
17422             } else { ChessSquare piece = boards[move][i][j];
17423                 if (emptycount > 0) {
17424                     if(emptycount<10) /* [HGM] can be >= 10 */
17425                         *p++ = '0' + emptycount;
17426                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17427                     emptycount = 0;
17428                 }
17429                 if(PieceToChar(piece) == '+') {
17430                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
17431                     *p++ = '+';
17432                     piece = (ChessSquare)(DEMOTED piece);
17433                 }
17434                 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
17435                 if(p[-1] == '~') {
17436                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
17437                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
17438                     *p++ = '~';
17439                 }
17440             }
17441         }
17442         if (emptycount > 0) {
17443             if(emptycount<10) /* [HGM] can be >= 10 */
17444                 *p++ = '0' + emptycount;
17445             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17446             emptycount = 0;
17447         }
17448         *p++ = '/';
17449     }
17450     *(p - 1) = ' ';
17451
17452     /* [HGM] print Crazyhouse or Shogi holdings */
17453     if( gameInfo.holdingsWidth ) {
17454         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
17455         q = p;
17456         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
17457             piece = boards[move][i][BOARD_WIDTH-1];
17458             if( piece != EmptySquare )
17459               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
17460                   *p++ = PieceToChar(piece);
17461         }
17462         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
17463             piece = boards[move][BOARD_HEIGHT-i-1][0];
17464             if( piece != EmptySquare )
17465               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
17466                   *p++ = PieceToChar(piece);
17467         }
17468
17469         if( q == p ) *p++ = '-';
17470         *p++ = ']';
17471         *p++ = ' ';
17472     }
17473
17474     /* Active color */
17475     *p++ = whiteToPlay ? 'w' : 'b';
17476     *p++ = ' ';
17477
17478   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
17479     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
17480   } else {
17481   if(nrCastlingRights) {
17482      q = p;
17483      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
17484        /* [HGM] write directly from rights */
17485            if(boards[move][CASTLING][2] != NoRights &&
17486               boards[move][CASTLING][0] != NoRights   )
17487                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
17488            if(boards[move][CASTLING][2] != NoRights &&
17489               boards[move][CASTLING][1] != NoRights   )
17490                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
17491            if(boards[move][CASTLING][5] != NoRights &&
17492               boards[move][CASTLING][3] != NoRights   )
17493                 *p++ = boards[move][CASTLING][3] + AAA;
17494            if(boards[move][CASTLING][5] != NoRights &&
17495               boards[move][CASTLING][4] != NoRights   )
17496                 *p++ = boards[move][CASTLING][4] + AAA;
17497      } else {
17498
17499         /* [HGM] write true castling rights */
17500         if( nrCastlingRights == 6 ) {
17501             int q, k=0;
17502             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
17503                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
17504             q = (boards[move][CASTLING][1] == BOARD_LEFT &&
17505                  boards[move][CASTLING][2] != NoRights  );
17506             if(gameInfo.variant == VariantSChess) { // for S-Chess, indicate all vrgin backrank pieces
17507                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_RGHT]; // count white held pieces
17508                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17509                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
17510                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
17511             }
17512             if(q) *p++ = 'Q';
17513             k = 0;
17514             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
17515                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
17516             q = (boards[move][CASTLING][4] == BOARD_LEFT &&
17517                  boards[move][CASTLING][5] != NoRights  );
17518             if(gameInfo.variant == VariantSChess) {
17519                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_LEFT-1]; // count black held pieces
17520                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17521                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
17522                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17523             }
17524             if(q) *p++ = 'q';
17525         }
17526      }
17527      if (q == p) *p++ = '-'; /* No castling rights */
17528      *p++ = ' ';
17529   }
17530
17531   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17532      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17533      gameInfo.variant != VariantMakruk   && gameInfo.variant != VariantASEAN ) {
17534     /* En passant target square */
17535     if (move > backwardMostMove) {
17536         fromX = moveList[move - 1][0] - AAA;
17537         fromY = moveList[move - 1][1] - ONE;
17538         toX = moveList[move - 1][2] - AAA;
17539         toY = moveList[move - 1][3] - ONE;
17540         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
17541             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
17542             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
17543             fromX == toX) {
17544             /* 2-square pawn move just happened */
17545             *p++ = toX + AAA;
17546             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17547         } else {
17548             *p++ = '-';
17549         }
17550     } else if(move == backwardMostMove) {
17551         // [HGM] perhaps we should always do it like this, and forget the above?
17552         if((signed char)boards[move][EP_STATUS] >= 0) {
17553             *p++ = boards[move][EP_STATUS] + AAA;
17554             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17555         } else {
17556             *p++ = '-';
17557         }
17558     } else {
17559         *p++ = '-';
17560     }
17561     *p++ = ' ';
17562   }
17563   }
17564
17565     if(moveCounts)
17566     {   int i = 0, j=move;
17567
17568         /* [HGM] find reversible plies */
17569         if (appData.debugMode) { int k;
17570             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
17571             for(k=backwardMostMove; k<=forwardMostMove; k++)
17572                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
17573
17574         }
17575
17576         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
17577         if( j == backwardMostMove ) i += initialRulePlies;
17578         sprintf(p, "%d ", i);
17579         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
17580
17581         /* Fullmove number */
17582         sprintf(p, "%d", (move / 2) + 1);
17583     } else *--p = NULLCHAR;
17584
17585     return StrSave(buf);
17586 }
17587
17588 Boolean
17589 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
17590 {
17591     int i, j, k, w=0;
17592     char *p, c;
17593     int emptycount, virgin[BOARD_FILES];
17594     ChessSquare piece;
17595
17596     p = fen;
17597
17598     /* Piece placement data */
17599     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17600         j = 0;
17601         for (;;) {
17602             if (*p == '/' || *p == ' ' || *p == '[' ) {
17603                 if(j > w) w = j;
17604                 emptycount = gameInfo.boardWidth - j;
17605                 while (emptycount--)
17606                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17607                 if (*p == '/') p++;
17608                 else if(autoSize) { // we stumbled unexpectedly into end of board
17609                     for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
17610                         for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
17611                     }
17612                     appData.NrRanks = gameInfo.boardHeight - i; i=0;
17613                 }
17614                 break;
17615 #if(BOARD_FILES >= 10)
17616             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
17617                 p++; emptycount=10;
17618                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17619                 while (emptycount--)
17620                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17621 #endif
17622             } else if (*p == '*') {
17623                 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
17624             } else if (isdigit(*p)) {
17625                 emptycount = *p++ - '0';
17626                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
17627                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17628                 while (emptycount--)
17629                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17630             } else if (*p == '+' || isalpha(*p)) {
17631                 if (j >= gameInfo.boardWidth) return FALSE;
17632                 if(*p=='+') {
17633                     piece = CharToPiece(*++p);
17634                     if(piece == EmptySquare) return FALSE; /* unknown piece */
17635                     piece = (ChessSquare) (CHUPROMOTED piece ); p++;
17636                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
17637                 } else piece = CharToPiece(*p++);
17638
17639                 if(piece==EmptySquare) return FALSE; /* unknown piece */
17640                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
17641                     piece = (ChessSquare) (PROMOTED piece);
17642                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
17643                     p++;
17644                 }
17645                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
17646             } else {
17647                 return FALSE;
17648             }
17649         }
17650     }
17651     while (*p == '/' || *p == ' ') p++;
17652
17653     if(autoSize) appData.NrFiles = w, InitPosition(TRUE);
17654
17655     /* [HGM] by default clear Crazyhouse holdings, if present */
17656     if(gameInfo.holdingsWidth) {
17657        for(i=0; i<BOARD_HEIGHT; i++) {
17658            board[i][0]             = EmptySquare; /* black holdings */
17659            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
17660            board[i][1]             = (ChessSquare) 0; /* black counts */
17661            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
17662        }
17663     }
17664
17665     /* [HGM] look for Crazyhouse holdings here */
17666     while(*p==' ') p++;
17667     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
17668         if(*p == '[') p++;
17669         if(*p == '-' ) p++; /* empty holdings */ else {
17670             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
17671             /* if we would allow FEN reading to set board size, we would   */
17672             /* have to add holdings and shift the board read so far here   */
17673             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
17674                 p++;
17675                 if((int) piece >= (int) BlackPawn ) {
17676                     i = (int)piece - (int)BlackPawn;
17677                     i = PieceToNumber((ChessSquare)i);
17678                     if( i >= gameInfo.holdingsSize ) return FALSE;
17679                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
17680                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
17681                 } else {
17682                     i = (int)piece - (int)WhitePawn;
17683                     i = PieceToNumber((ChessSquare)i);
17684                     if( i >= gameInfo.holdingsSize ) return FALSE;
17685                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
17686                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
17687                 }
17688             }
17689         }
17690         if(*p == ']') p++;
17691     }
17692
17693     while(*p == ' ') p++;
17694
17695     /* Active color */
17696     c = *p++;
17697     if(appData.colorNickNames) {
17698       if( c == appData.colorNickNames[0] ) c = 'w'; else
17699       if( c == appData.colorNickNames[1] ) c = 'b';
17700     }
17701     switch (c) {
17702       case 'w':
17703         *blackPlaysFirst = FALSE;
17704         break;
17705       case 'b':
17706         *blackPlaysFirst = TRUE;
17707         break;
17708       default:
17709         return FALSE;
17710     }
17711
17712     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
17713     /* return the extra info in global variiables             */
17714
17715     /* set defaults in case FEN is incomplete */
17716     board[EP_STATUS] = EP_UNKNOWN;
17717     for(i=0; i<nrCastlingRights; i++ ) {
17718         board[CASTLING][i] =
17719             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
17720     }   /* assume possible unless obviously impossible */
17721     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
17722     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
17723     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
17724                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
17725     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
17726     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
17727     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
17728                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
17729     FENrulePlies = 0;
17730
17731     while(*p==' ') p++;
17732     if(nrCastlingRights) {
17733       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
17734       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
17735           /* castling indicator present, so default becomes no castlings */
17736           for(i=0; i<nrCastlingRights; i++ ) {
17737                  board[CASTLING][i] = NoRights;
17738           }
17739       }
17740       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
17741              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess) &&
17742              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
17743              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
17744         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
17745
17746         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
17747             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
17748             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
17749         }
17750         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
17751             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
17752         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
17753                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
17754         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
17755                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
17756         switch(c) {
17757           case'K':
17758               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
17759               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
17760               board[CASTLING][2] = whiteKingFile;
17761               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
17762               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17763               break;
17764           case'Q':
17765               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
17766               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
17767               board[CASTLING][2] = whiteKingFile;
17768               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
17769               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17770               break;
17771           case'k':
17772               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
17773               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
17774               board[CASTLING][5] = blackKingFile;
17775               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
17776               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17777               break;
17778           case'q':
17779               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
17780               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
17781               board[CASTLING][5] = blackKingFile;
17782               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
17783               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17784           case '-':
17785               break;
17786           default: /* FRC castlings */
17787               if(c >= 'a') { /* black rights */
17788                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
17789                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17790                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
17791                   if(i == BOARD_RGHT) break;
17792                   board[CASTLING][5] = i;
17793                   c -= AAA;
17794                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
17795                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
17796                   if(c > i)
17797                       board[CASTLING][3] = c;
17798                   else
17799                       board[CASTLING][4] = c;
17800               } else { /* white rights */
17801                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
17802                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17803                     if(board[0][i] == WhiteKing) break;
17804                   if(i == BOARD_RGHT) break;
17805                   board[CASTLING][2] = i;
17806                   c -= AAA - 'a' + 'A';
17807                   if(board[0][c] >= WhiteKing) break;
17808                   if(c > i)
17809                       board[CASTLING][0] = c;
17810                   else
17811                       board[CASTLING][1] = c;
17812               }
17813         }
17814       }
17815       for(i=0; i<nrCastlingRights; i++)
17816         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
17817       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = virgin[i];
17818     if (appData.debugMode) {
17819         fprintf(debugFP, "FEN castling rights:");
17820         for(i=0; i<nrCastlingRights; i++)
17821         fprintf(debugFP, " %d", board[CASTLING][i]);
17822         fprintf(debugFP, "\n");
17823     }
17824
17825       while(*p==' ') p++;
17826     }
17827
17828     /* read e.p. field in games that know e.p. capture */
17829     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17830        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17831        gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
17832       if(*p=='-') {
17833         p++; board[EP_STATUS] = EP_NONE;
17834       } else {
17835          char c = *p++ - AAA;
17836
17837          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
17838          if(*p >= '0' && *p <='9') p++;
17839          board[EP_STATUS] = c;
17840       }
17841     }
17842
17843
17844     if(sscanf(p, "%d", &i) == 1) {
17845         FENrulePlies = i; /* 50-move ply counter */
17846         /* (The move number is still ignored)    */
17847     }
17848
17849     return TRUE;
17850 }
17851
17852 void
17853 EditPositionPasteFEN (char *fen)
17854 {
17855   if (fen != NULL) {
17856     Board initial_position;
17857
17858     if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
17859       DisplayError(_("Bad FEN position in clipboard"), 0);
17860       return ;
17861     } else {
17862       int savedBlackPlaysFirst = blackPlaysFirst;
17863       EditPositionEvent();
17864       blackPlaysFirst = savedBlackPlaysFirst;
17865       CopyBoard(boards[0], initial_position);
17866       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
17867       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
17868       DisplayBothClocks();
17869       DrawPosition(FALSE, boards[currentMove]);
17870     }
17871   }
17872 }
17873
17874 static char cseq[12] = "\\   ";
17875
17876 Boolean
17877 set_cont_sequence (char *new_seq)
17878 {
17879     int len;
17880     Boolean ret;
17881
17882     // handle bad attempts to set the sequence
17883         if (!new_seq)
17884                 return 0; // acceptable error - no debug
17885
17886     len = strlen(new_seq);
17887     ret = (len > 0) && (len < sizeof(cseq));
17888     if (ret)
17889       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
17890     else if (appData.debugMode)
17891       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
17892     return ret;
17893 }
17894
17895 /*
17896     reformat a source message so words don't cross the width boundary.  internal
17897     newlines are not removed.  returns the wrapped size (no null character unless
17898     included in source message).  If dest is NULL, only calculate the size required
17899     for the dest buffer.  lp argument indicats line position upon entry, and it's
17900     passed back upon exit.
17901 */
17902 int
17903 wrap (char *dest, char *src, int count, int width, int *lp)
17904 {
17905     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
17906
17907     cseq_len = strlen(cseq);
17908     old_line = line = *lp;
17909     ansi = len = clen = 0;
17910
17911     for (i=0; i < count; i++)
17912     {
17913         if (src[i] == '\033')
17914             ansi = 1;
17915
17916         // if we hit the width, back up
17917         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
17918         {
17919             // store i & len in case the word is too long
17920             old_i = i, old_len = len;
17921
17922             // find the end of the last word
17923             while (i && src[i] != ' ' && src[i] != '\n')
17924             {
17925                 i--;
17926                 len--;
17927             }
17928
17929             // word too long?  restore i & len before splitting it
17930             if ((old_i-i+clen) >= width)
17931             {
17932                 i = old_i;
17933                 len = old_len;
17934             }
17935
17936             // extra space?
17937             if (i && src[i-1] == ' ')
17938                 len--;
17939
17940             if (src[i] != ' ' && src[i] != '\n')
17941             {
17942                 i--;
17943                 if (len)
17944                     len--;
17945             }
17946
17947             // now append the newline and continuation sequence
17948             if (dest)
17949                 dest[len] = '\n';
17950             len++;
17951             if (dest)
17952                 strncpy(dest+len, cseq, cseq_len);
17953             len += cseq_len;
17954             line = cseq_len;
17955             clen = cseq_len;
17956             continue;
17957         }
17958
17959         if (dest)
17960             dest[len] = src[i];
17961         len++;
17962         if (!ansi)
17963             line++;
17964         if (src[i] == '\n')
17965             line = 0;
17966         if (src[i] == 'm')
17967             ansi = 0;
17968     }
17969     if (dest && appData.debugMode)
17970     {
17971         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
17972             count, width, line, len, *lp);
17973         show_bytes(debugFP, src, count);
17974         fprintf(debugFP, "\ndest: ");
17975         show_bytes(debugFP, dest, len);
17976         fprintf(debugFP, "\n");
17977     }
17978     *lp = dest ? line : old_line;
17979
17980     return len;
17981 }
17982
17983 // [HGM] vari: routines for shelving variations
17984 Boolean modeRestore = FALSE;
17985
17986 void
17987 PushInner (int firstMove, int lastMove)
17988 {
17989         int i, j, nrMoves = lastMove - firstMove;
17990
17991         // push current tail of game on stack
17992         savedResult[storedGames] = gameInfo.result;
17993         savedDetails[storedGames] = gameInfo.resultDetails;
17994         gameInfo.resultDetails = NULL;
17995         savedFirst[storedGames] = firstMove;
17996         savedLast [storedGames] = lastMove;
17997         savedFramePtr[storedGames] = framePtr;
17998         framePtr -= nrMoves; // reserve space for the boards
17999         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
18000             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
18001             for(j=0; j<MOVE_LEN; j++)
18002                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
18003             for(j=0; j<2*MOVE_LEN; j++)
18004                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
18005             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
18006             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
18007             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
18008             pvInfoList[firstMove+i-1].depth = 0;
18009             commentList[framePtr+i] = commentList[firstMove+i];
18010             commentList[firstMove+i] = NULL;
18011         }
18012
18013         storedGames++;
18014         forwardMostMove = firstMove; // truncate game so we can start variation
18015 }
18016
18017 void
18018 PushTail (int firstMove, int lastMove)
18019 {
18020         if(appData.icsActive) { // only in local mode
18021                 forwardMostMove = currentMove; // mimic old ICS behavior
18022                 return;
18023         }
18024         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
18025
18026         PushInner(firstMove, lastMove);
18027         if(storedGames == 1) GreyRevert(FALSE);
18028         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
18029 }
18030
18031 void
18032 PopInner (Boolean annotate)
18033 {
18034         int i, j, nrMoves;
18035         char buf[8000], moveBuf[20];
18036
18037         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
18038         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
18039         nrMoves = savedLast[storedGames] - currentMove;
18040         if(annotate) {
18041                 int cnt = 10;
18042                 if(!WhiteOnMove(currentMove))
18043                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
18044                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
18045                 for(i=currentMove; i<forwardMostMove; i++) {
18046                         if(WhiteOnMove(i))
18047                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
18048                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
18049                         strcat(buf, moveBuf);
18050                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
18051                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
18052                 }
18053                 strcat(buf, ")");
18054         }
18055         for(i=1; i<=nrMoves; i++) { // copy last variation back
18056             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
18057             for(j=0; j<MOVE_LEN; j++)
18058                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
18059             for(j=0; j<2*MOVE_LEN; j++)
18060                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
18061             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
18062             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
18063             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
18064             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
18065             commentList[currentMove+i] = commentList[framePtr+i];
18066             commentList[framePtr+i] = NULL;
18067         }
18068         if(annotate) AppendComment(currentMove+1, buf, FALSE);
18069         framePtr = savedFramePtr[storedGames];
18070         gameInfo.result = savedResult[storedGames];
18071         if(gameInfo.resultDetails != NULL) {
18072             free(gameInfo.resultDetails);
18073       }
18074         gameInfo.resultDetails = savedDetails[storedGames];
18075         forwardMostMove = currentMove + nrMoves;
18076 }
18077
18078 Boolean
18079 PopTail (Boolean annotate)
18080 {
18081         if(appData.icsActive) return FALSE; // only in local mode
18082         if(!storedGames) return FALSE; // sanity
18083         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
18084
18085         PopInner(annotate);
18086         if(currentMove < forwardMostMove) ForwardEvent(); else
18087         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
18088
18089         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
18090         return TRUE;
18091 }
18092
18093 void
18094 CleanupTail ()
18095 {       // remove all shelved variations
18096         int i;
18097         for(i=0; i<storedGames; i++) {
18098             if(savedDetails[i])
18099                 free(savedDetails[i]);
18100             savedDetails[i] = NULL;
18101         }
18102         for(i=framePtr; i<MAX_MOVES; i++) {
18103                 if(commentList[i]) free(commentList[i]);
18104                 commentList[i] = NULL;
18105         }
18106         framePtr = MAX_MOVES-1;
18107         storedGames = 0;
18108 }
18109
18110 void
18111 LoadVariation (int index, char *text)
18112 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
18113         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
18114         int level = 0, move;
18115
18116         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
18117         // first find outermost bracketing variation
18118         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
18119             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
18120                 if(*p == '{') wait = '}'; else
18121                 if(*p == '[') wait = ']'; else
18122                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
18123                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
18124             }
18125             if(*p == wait) wait = NULLCHAR; // closing ]} found
18126             p++;
18127         }
18128         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
18129         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
18130         end[1] = NULLCHAR; // clip off comment beyond variation
18131         ToNrEvent(currentMove-1);
18132         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
18133         // kludge: use ParsePV() to append variation to game
18134         move = currentMove;
18135         ParsePV(start, TRUE, TRUE);
18136         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
18137         ClearPremoveHighlights();
18138         CommentPopDown();
18139         ToNrEvent(currentMove+1);
18140 }
18141
18142 void
18143 LoadTheme ()
18144 {
18145     char *p, *q, buf[MSG_SIZ];
18146     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
18147         snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
18148         ParseArgsFromString(buf);
18149         ActivateTheme(TRUE); // also redo colors
18150         return;
18151     }
18152     p = nickName;
18153     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
18154     {
18155         int len;
18156         q = appData.themeNames;
18157         snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
18158       if(appData.useBitmaps) {
18159         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
18160                 appData.liteBackTextureFile, appData.darkBackTextureFile,
18161                 appData.liteBackTextureMode,
18162                 appData.darkBackTextureMode );
18163       } else {
18164         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
18165                 Col2Text(2),   // lightSquareColor
18166                 Col2Text(3) ); // darkSquareColor
18167       }
18168       if(appData.useBorder) {
18169         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
18170                 appData.border);
18171       } else {
18172         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
18173       }
18174       if(appData.useFont) {
18175         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
18176                 appData.renderPiecesWithFont,
18177                 appData.fontToPieceTable,
18178                 Col2Text(9),    // appData.fontBackColorWhite
18179                 Col2Text(10) ); // appData.fontForeColorBlack
18180       } else {
18181         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
18182                 appData.pieceDirectory);
18183         if(!appData.pieceDirectory[0])
18184           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
18185                 Col2Text(0),   // whitePieceColor
18186                 Col2Text(1) ); // blackPieceColor
18187       }
18188       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
18189                 Col2Text(4),   // highlightSquareColor
18190                 Col2Text(5) ); // premoveHighlightColor
18191         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
18192         if(insert != q) insert[-1] = NULLCHAR;
18193         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
18194         if(q)   free(q);
18195     }
18196     ActivateTheme(FALSE);
18197 }