Fix some warnings
[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
229 #ifdef WIN32
230        extern void ConsoleCreate();
231 #endif
232
233 ChessProgramState *WhitePlayer();
234 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
235 int VerifyDisplayMode P(());
236
237 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
238 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
239 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
240 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
241 void ics_update_width P((int new_width));
242 extern char installDir[MSG_SIZ];
243 VariantClass startVariant; /* [HGM] nicks: initial variant */
244 Boolean abortMatch;
245
246 extern int tinyLayout, smallLayout;
247 ChessProgramStats programStats;
248 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
249 int endPV = -1;
250 static int exiting = 0; /* [HGM] moved to top */
251 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
252 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
253 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
254 int partnerHighlight[2];
255 Boolean partnerBoardValid = 0;
256 char partnerStatus[MSG_SIZ];
257 Boolean partnerUp;
258 Boolean originalFlip;
259 Boolean twoBoards = 0;
260 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
261 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
262 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
263 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
264 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
265 int opponentKibitzes;
266 int lastSavedGame; /* [HGM] save: ID of game */
267 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
268 extern int chatCount;
269 int chattingPartner;
270 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
271 char lastMsg[MSG_SIZ];
272 ChessSquare pieceSweep = EmptySquare;
273 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
274 int promoDefaultAltered;
275 int keepInfo = 0; /* [HGM] to protect PGN tags in auto-step game analysis */
276
277 /* States for ics_getting_history */
278 #define H_FALSE 0
279 #define H_REQUESTED 1
280 #define H_GOT_REQ_HEADER 2
281 #define H_GOT_UNREQ_HEADER 3
282 #define H_GETTING_MOVES 4
283 #define H_GOT_UNWANTED_HEADER 5
284
285 /* whosays values for GameEnds */
286 #define GE_ICS 0
287 #define GE_ENGINE 1
288 #define GE_PLAYER 2
289 #define GE_FILE 3
290 #define GE_XBOARD 4
291 #define GE_ENGINE1 5
292 #define GE_ENGINE2 6
293
294 /* Maximum number of games in a cmail message */
295 #define CMAIL_MAX_GAMES 20
296
297 /* Different types of move when calling RegisterMove */
298 #define CMAIL_MOVE   0
299 #define CMAIL_RESIGN 1
300 #define CMAIL_DRAW   2
301 #define CMAIL_ACCEPT 3
302
303 /* Different types of result to remember for each game */
304 #define CMAIL_NOT_RESULT 0
305 #define CMAIL_OLD_RESULT 1
306 #define CMAIL_NEW_RESULT 2
307
308 /* Telnet protocol constants */
309 #define TN_WILL 0373
310 #define TN_WONT 0374
311 #define TN_DO   0375
312 #define TN_DONT 0376
313 #define TN_IAC  0377
314 #define TN_ECHO 0001
315 #define TN_SGA  0003
316 #define TN_PORT 23
317
318 char*
319 safeStrCpy (char *dst, const char *src, size_t count)
320 { // [HGM] made safe
321   int i;
322   assert( dst != NULL );
323   assert( src != NULL );
324   assert( count > 0 );
325
326   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
327   if(  i == count && dst[count-1] != NULLCHAR)
328     {
329       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
330       if(appData.debugMode)
331       fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
332     }
333
334   return dst;
335 }
336
337 /* Some compiler can't cast u64 to double
338  * This function do the job for us:
339
340  * We use the highest bit for cast, this only
341  * works if the highest bit is not
342  * in use (This should not happen)
343  *
344  * We used this for all compiler
345  */
346 double
347 u64ToDouble (u64 value)
348 {
349   double r;
350   u64 tmp = value & u64Const(0x7fffffffffffffff);
351   r = (double)(s64)tmp;
352   if (value & u64Const(0x8000000000000000))
353        r +=  9.2233720368547758080e18; /* 2^63 */
354  return r;
355 }
356
357 /* Fake up flags for now, as we aren't keeping track of castling
358    availability yet. [HGM] Change of logic: the flag now only
359    indicates the type of castlings allowed by the rule of the game.
360    The actual rights themselves are maintained in the array
361    castlingRights, as part of the game history, and are not probed
362    by this function.
363  */
364 int
365 PosFlags (index)
366 {
367   int flags = F_ALL_CASTLE_OK;
368   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
369   switch (gameInfo.variant) {
370   case VariantSuicide:
371     flags &= ~F_ALL_CASTLE_OK;
372   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
373     flags |= F_IGNORE_CHECK;
374   case VariantLosers:
375     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
376     break;
377   case VariantAtomic:
378     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
379     break;
380   case VariantKriegspiel:
381     flags |= F_KRIEGSPIEL_CAPTURE;
382     break;
383   case VariantCapaRandom:
384   case VariantFischeRandom:
385     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
386   case VariantNoCastle:
387   case VariantShatranj:
388   case VariantCourier:
389   case VariantMakruk:
390   case VariantGrand:
391     flags &= ~F_ALL_CASTLE_OK;
392     break;
393   default:
394     break;
395   }
396   return flags;
397 }
398
399 FILE *gameFileFP, *debugFP, *serverFP;
400 char *currentDebugFile; // [HGM] debug split: to remember name
401
402 /*
403     [AS] Note: sometimes, the sscanf() function is used to parse the input
404     into a fixed-size buffer. Because of this, we must be prepared to
405     receive strings as long as the size of the input buffer, which is currently
406     set to 4K for Windows and 8K for the rest.
407     So, we must either allocate sufficiently large buffers here, or
408     reduce the size of the input buffer in the input reading part.
409 */
410
411 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
412 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
413 char thinkOutput1[MSG_SIZ*10];
414
415 ChessProgramState first, second, pairing;
416
417 /* premove variables */
418 int premoveToX = 0;
419 int premoveToY = 0;
420 int premoveFromX = 0;
421 int premoveFromY = 0;
422 int premovePromoChar = 0;
423 int gotPremove = 0;
424 Boolean alarmSounded;
425 /* end premove variables */
426
427 char *ics_prefix = "$";
428 enum ICS_TYPE ics_type = ICS_GENERIC;
429
430 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
431 int pauseExamForwardMostMove = 0;
432 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
433 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
434 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
435 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
436 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
437 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
438 int whiteFlag = FALSE, blackFlag = FALSE;
439 int userOfferedDraw = FALSE;
440 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
441 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
442 int cmailMoveType[CMAIL_MAX_GAMES];
443 long ics_clock_paused = 0;
444 ProcRef icsPR = NoProc, cmailPR = NoProc;
445 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
446 GameMode gameMode = BeginningOfGame;
447 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
448 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
449 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
450 int hiddenThinkOutputState = 0; /* [AS] */
451 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
452 int adjudicateLossPlies = 6;
453 char white_holding[64], black_holding[64];
454 TimeMark lastNodeCountTime;
455 long lastNodeCount=0;
456 int shiftKey, controlKey; // [HGM] set by mouse handler
457
458 int have_sent_ICS_logon = 0;
459 int movesPerSession;
460 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
461 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack, activePartnerTime;
462 Boolean adjustedClock;
463 long timeControl_2; /* [AS] Allow separate time controls */
464 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC, activePartner; /* [HGM] secondary TC: merge of MPS, TC and inc */
465 long timeRemaining[2][MAX_MOVES];
466 int matchGame = 0, nextGame = 0, roundNr = 0;
467 Boolean waitingForGame = FALSE;
468 TimeMark programStartTime, pauseStart;
469 char ics_handle[MSG_SIZ];
470 int have_set_title = 0;
471
472 /* animateTraining preserves the state of appData.animate
473  * when Training mode is activated. This allows the
474  * response to be animated when appData.animate == TRUE and
475  * appData.animateDragging == TRUE.
476  */
477 Boolean animateTraining;
478
479 GameInfo gameInfo;
480
481 AppData appData;
482
483 Board boards[MAX_MOVES];
484 /* [HGM] Following 7 needed for accurate legality tests: */
485 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
486 signed char  initialRights[BOARD_FILES];
487 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
488 int   initialRulePlies, FENrulePlies;
489 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
490 int loadFlag = 0;
491 Boolean shuffleOpenings;
492 int mute; // mute all sounds
493
494 // [HGM] vari: next 12 to save and restore variations
495 #define MAX_VARIATIONS 10
496 int framePtr = MAX_MOVES-1; // points to free stack entry
497 int storedGames = 0;
498 int savedFirst[MAX_VARIATIONS];
499 int savedLast[MAX_VARIATIONS];
500 int savedFramePtr[MAX_VARIATIONS];
501 char *savedDetails[MAX_VARIATIONS];
502 ChessMove savedResult[MAX_VARIATIONS];
503
504 void PushTail P((int firstMove, int lastMove));
505 Boolean PopTail P((Boolean annotate));
506 void PushInner P((int firstMove, int lastMove));
507 void PopInner P((Boolean annotate));
508 void CleanupTail P((void));
509
510 ChessSquare  FIDEArray[2][BOARD_FILES] = {
511     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
512         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
513     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
514         BlackKing, BlackBishop, BlackKnight, BlackRook }
515 };
516
517 ChessSquare twoKingsArray[2][BOARD_FILES] = {
518     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
519         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
520     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
521         BlackKing, BlackKing, BlackKnight, BlackRook }
522 };
523
524 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
525     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
526         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
527     { BlackRook, BlackMan, BlackBishop, BlackQueen,
528         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
529 };
530
531 ChessSquare SpartanArray[2][BOARD_FILES] = {
532     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
533         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
534     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
535         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
536 };
537
538 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
539     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
540         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
541     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
542         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
543 };
544
545 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
546     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
547         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
548     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
549         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
550 };
551
552 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
553     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
554         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
555     { BlackRook, BlackKnight, BlackMan, BlackFerz,
556         BlackKing, BlackMan, BlackKnight, BlackRook }
557 };
558
559
560 #if (BOARD_FILES>=10)
561 ChessSquare ShogiArray[2][BOARD_FILES] = {
562     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
563         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
564     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
565         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
566 };
567
568 ChessSquare XiangqiArray[2][BOARD_FILES] = {
569     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
570         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
571     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
572         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
573 };
574
575 ChessSquare CapablancaArray[2][BOARD_FILES] = {
576     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
577         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
578     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
579         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
580 };
581
582 ChessSquare GreatArray[2][BOARD_FILES] = {
583     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
584         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
585     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
586         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
587 };
588
589 ChessSquare JanusArray[2][BOARD_FILES] = {
590     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
591         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
592     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
593         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
594 };
595
596 ChessSquare GrandArray[2][BOARD_FILES] = {
597     { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
598         WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
599     { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
600         BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
601 };
602
603 #ifdef GOTHIC
604 ChessSquare GothicArray[2][BOARD_FILES] = {
605     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
606         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
607     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
608         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
609 };
610 #else // !GOTHIC
611 #define GothicArray CapablancaArray
612 #endif // !GOTHIC
613
614 #ifdef FALCON
615 ChessSquare FalconArray[2][BOARD_FILES] = {
616     { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
617         WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
618     { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
619         BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
620 };
621 #else // !FALCON
622 #define FalconArray CapablancaArray
623 #endif // !FALCON
624
625 #else // !(BOARD_FILES>=10)
626 #define XiangqiPosition FIDEArray
627 #define CapablancaArray FIDEArray
628 #define GothicArray FIDEArray
629 #define GreatArray FIDEArray
630 #endif // !(BOARD_FILES>=10)
631
632 #if (BOARD_FILES>=12)
633 ChessSquare CourierArray[2][BOARD_FILES] = {
634     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
635         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
636     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
637         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
638 };
639 #else // !(BOARD_FILES>=12)
640 #define CourierArray CapablancaArray
641 #endif // !(BOARD_FILES>=12)
642
643
644 Board initialPosition;
645
646
647 /* Convert str to a rating. Checks for special cases of "----",
648
649    "++++", etc. Also strips ()'s */
650 int
651 string_to_rating (char *str)
652 {
653   while(*str && !isdigit(*str)) ++str;
654   if (!*str)
655     return 0;   /* One of the special "no rating" cases */
656   else
657     return atoi(str);
658 }
659
660 void
661 ClearProgramStats ()
662 {
663     /* Init programStats */
664     programStats.movelist[0] = 0;
665     programStats.depth = 0;
666     programStats.nr_moves = 0;
667     programStats.moves_left = 0;
668     programStats.nodes = 0;
669     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
670     programStats.score = 0;
671     programStats.got_only_move = 0;
672     programStats.got_fail = 0;
673     programStats.line_is_book = 0;
674 }
675
676 void
677 CommonEngineInit ()
678 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
679     if (appData.firstPlaysBlack) {
680         first.twoMachinesColor = "black\n";
681         second.twoMachinesColor = "white\n";
682     } else {
683         first.twoMachinesColor = "white\n";
684         second.twoMachinesColor = "black\n";
685     }
686
687     first.other = &second;
688     second.other = &first;
689
690     { float norm = 1;
691         if(appData.timeOddsMode) {
692             norm = appData.timeOdds[0];
693             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
694         }
695         first.timeOdds  = appData.timeOdds[0]/norm;
696         second.timeOdds = appData.timeOdds[1]/norm;
697     }
698
699     if(programVersion) free(programVersion);
700     if (appData.noChessProgram) {
701         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
702         sprintf(programVersion, "%s", PACKAGE_STRING);
703     } else {
704       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
705       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
706       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
707     }
708 }
709
710 void
711 UnloadEngine (ChessProgramState *cps)
712 {
713         /* Kill off first chess program */
714         if (cps->isr != NULL)
715           RemoveInputSource(cps->isr);
716         cps->isr = NULL;
717
718         if (cps->pr != NoProc) {
719             ExitAnalyzeMode();
720             DoSleep( appData.delayBeforeQuit );
721             SendToProgram("quit\n", cps);
722             DoSleep( appData.delayAfterQuit );
723             DestroyChildProcess(cps->pr, cps->useSigterm);
724         }
725         cps->pr = NoProc;
726         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
727 }
728
729 void
730 ClearOptions (ChessProgramState *cps)
731 {
732     int i;
733     cps->nrOptions = cps->comboCnt = 0;
734     for(i=0; i<MAX_OPTIONS; i++) {
735         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
736         cps->option[i].textValue = 0;
737     }
738 }
739
740 char *engineNames[] = {
741   /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
742      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
743 N_("first"),
744   /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
745      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
746 N_("second")
747 };
748
749 void
750 InitEngine (ChessProgramState *cps, int n)
751 {   // [HGM] all engine initialiation put in a function that does one engine
752
753     ClearOptions(cps);
754
755     cps->which = engineNames[n];
756     cps->maybeThinking = FALSE;
757     cps->pr = NoProc;
758     cps->isr = NULL;
759     cps->sendTime = 2;
760     cps->sendDrawOffers = 1;
761
762     cps->program = appData.chessProgram[n];
763     cps->host = appData.host[n];
764     cps->dir = appData.directory[n];
765     cps->initString = appData.engInitString[n];
766     cps->computerString = appData.computerString[n];
767     cps->useSigint  = TRUE;
768     cps->useSigterm = TRUE;
769     cps->reuse = appData.reuse[n];
770     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
771     cps->useSetboard = FALSE;
772     cps->useSAN = FALSE;
773     cps->usePing = FALSE;
774     cps->lastPing = 0;
775     cps->lastPong = 0;
776     cps->usePlayother = FALSE;
777     cps->useColors = TRUE;
778     cps->useUsermove = FALSE;
779     cps->sendICS = FALSE;
780     cps->sendName = appData.icsActive;
781     cps->sdKludge = FALSE;
782     cps->stKludge = FALSE;
783     TidyProgramName(cps->program, cps->host, cps->tidy);
784     cps->matchWins = 0;
785     safeStrCpy(cps->variants, appData.variant, MSG_SIZ);
786     cps->analysisSupport = 2; /* detect */
787     cps->analyzing = FALSE;
788     cps->initDone = FALSE;
789     cps->reload = FALSE;
790
791     /* New features added by Tord: */
792     cps->useFEN960 = FALSE;
793     cps->useOOCastle = TRUE;
794     /* End of new features added by Tord. */
795     cps->fenOverride  = appData.fenOverride[n];
796
797     /* [HGM] time odds: set factor for each machine */
798     cps->timeOdds  = appData.timeOdds[n];
799
800     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
801     cps->accumulateTC = appData.accumulateTC[n];
802     cps->maxNrOfSessions = 1;
803
804     /* [HGM] debug */
805     cps->debug = FALSE;
806
807     cps->supportsNPS = UNKNOWN;
808     cps->memSize = FALSE;
809     cps->maxCores = FALSE;
810     cps->egtFormats[0] = NULLCHAR;
811
812     /* [HGM] options */
813     cps->optionSettings  = appData.engOptions[n];
814
815     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
816     cps->isUCI = appData.isUCI[n]; /* [AS] */
817     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
818
819     if (appData.protocolVersion[n] > PROTOVER
820         || appData.protocolVersion[n] < 1)
821       {
822         char buf[MSG_SIZ];
823         int len;
824
825         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
826                        appData.protocolVersion[n]);
827         if( (len >= MSG_SIZ) && appData.debugMode )
828           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
829
830         DisplayFatalError(buf, 0, 2);
831       }
832     else
833       {
834         cps->protocolVersion = appData.protocolVersion[n];
835       }
836
837     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
838     ParseFeatures(appData.featureDefaults, cps);
839 }
840
841 ChessProgramState *savCps;
842
843 void
844 LoadEngine ()
845 {
846     int i;
847     if(WaitForEngine(savCps, LoadEngine)) return;
848     CommonEngineInit(); // recalculate time odds
849     if(gameInfo.variant != StringToVariant(appData.variant)) {
850         // we changed variant when loading the engine; this forces us to reset
851         Reset(TRUE, savCps != &first);
852         EditGameEvent(); // for consistency with other path, as Reset changes mode
853     }
854     InitChessProgram(savCps, FALSE);
855     SendToProgram("force\n", savCps);
856     DisplayMessage("", "");
857     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
858     for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
859     ThawUI();
860     SetGNUMode();
861 }
862
863 void
864 ReplaceEngine (ChessProgramState *cps, int n)
865 {
866     EditGameEvent();
867     UnloadEngine(cps);
868     appData.noChessProgram = FALSE;
869     appData.clockMode = TRUE;
870     InitEngine(cps, n);
871     UpdateLogos(TRUE);
872     if(n) return; // only startup first engine immediately; second can wait
873     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
874     LoadEngine();
875 }
876
877 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
878 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
879
880 static char resetOptions[] =
881         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
882         "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
883         "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 "
884         "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
885
886 void
887 FloatToFront(char **list, char *engineLine)
888 {
889     char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
890     int i=0;
891     if(appData.recentEngines <= 0) return;
892     TidyProgramName(engineLine, "localhost", tidy+1);
893     tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
894     strncpy(buf+1, *list, MSG_SIZ-50);
895     if(p = strstr(buf, tidy)) { // tidy name appears in list
896         q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
897         while(*p++ = *++q); // squeeze out
898     }
899     strcat(tidy, buf+1); // put list behind tidy name
900     p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
901     if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
902     ASSIGN(*list, tidy+1);
903 }
904
905 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
906
907 void
908 Load (ChessProgramState *cps, int i)
909 {
910     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ];
911     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
912         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
913         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
914         ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
915         FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
916         appData.firstProtocolVersion = PROTOVER;
917         ParseArgsFromString(buf);
918         SwapEngines(i);
919         ReplaceEngine(cps, i);
920         FloatToFront(&appData.recentEngineList, engineLine);
921         return;
922     }
923     p = engineName;
924     while(q = strchr(p, SLASH)) p = q+1;
925     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
926     if(engineDir[0] != NULLCHAR) {
927         ASSIGN(appData.directory[i], engineDir); p = engineName;
928     } else if(p != engineName) { // derive directory from engine path, when not given
929         p[-1] = 0;
930         ASSIGN(appData.directory[i], engineName);
931         p[-1] = SLASH;
932         if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
933     } else { ASSIGN(appData.directory[i], "."); }
934     if(params[0]) {
935         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
936         snprintf(command, MSG_SIZ, "%s %s", p, params);
937         p = command;
938     }
939     ASSIGN(appData.chessProgram[i], p);
940     appData.isUCI[i] = isUCI;
941     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
942     appData.hasOwnBookUCI[i] = hasBook;
943     if(!nickName[0]) useNick = FALSE;
944     if(useNick) ASSIGN(appData.pgnName[i], nickName);
945     if(addToList) {
946         int len;
947         char quote;
948         q = firstChessProgramNames;
949         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
950         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
951         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
952                         quote, p, quote, appData.directory[i],
953                         useNick ? " -fn \"" : "",
954                         useNick ? nickName : "",
955                         useNick ? "\"" : "",
956                         v1 ? " -firstProtocolVersion 1" : "",
957                         hasBook ? "" : " -fNoOwnBookUCI",
958                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
959                         storeVariant ? " -variant " : "",
960                         storeVariant ? VariantName(gameInfo.variant) : "");
961         if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
962         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
963         if(insert != q) insert[-1] = NULLCHAR;
964         snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
965         if(q)   free(q);
966         FloatToFront(&appData.recentEngineList, buf);
967     }
968     ReplaceEngine(cps, i);
969 }
970
971 void
972 InitTimeControls ()
973 {
974     int matched, min, sec;
975     /*
976      * Parse timeControl resource
977      */
978     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
979                           appData.movesPerSession)) {
980         char buf[MSG_SIZ];
981         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
982         DisplayFatalError(buf, 0, 2);
983     }
984
985     /*
986      * Parse searchTime resource
987      */
988     if (*appData.searchTime != NULLCHAR) {
989         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
990         if (matched == 1) {
991             searchTime = min * 60;
992         } else if (matched == 2) {
993             searchTime = min * 60 + sec;
994         } else {
995             char buf[MSG_SIZ];
996             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
997             DisplayFatalError(buf, 0, 2);
998         }
999     }
1000 }
1001
1002 void
1003 InitBackEnd1 ()
1004 {
1005
1006     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1007     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1008
1009     GetTimeMark(&programStartTime);
1010     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1011     appData.seedBase = random() + (random()<<15);
1012     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1013
1014     ClearProgramStats();
1015     programStats.ok_to_send = 1;
1016     programStats.seen_stat = 0;
1017
1018     /*
1019      * Initialize game list
1020      */
1021     ListNew(&gameList);
1022
1023
1024     /*
1025      * Internet chess server status
1026      */
1027     if (appData.icsActive) {
1028         appData.matchMode = FALSE;
1029         appData.matchGames = 0;
1030 #if ZIPPY
1031         appData.noChessProgram = !appData.zippyPlay;
1032 #else
1033         appData.zippyPlay = FALSE;
1034         appData.zippyTalk = FALSE;
1035         appData.noChessProgram = TRUE;
1036 #endif
1037         if (*appData.icsHelper != NULLCHAR) {
1038             appData.useTelnet = TRUE;
1039             appData.telnetProgram = appData.icsHelper;
1040         }
1041     } else {
1042         appData.zippyTalk = appData.zippyPlay = FALSE;
1043     }
1044
1045     /* [AS] Initialize pv info list [HGM] and game state */
1046     {
1047         int i, j;
1048
1049         for( i=0; i<=framePtr; i++ ) {
1050             pvInfoList[i].depth = -1;
1051             boards[i][EP_STATUS] = EP_NONE;
1052             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1053         }
1054     }
1055
1056     InitTimeControls();
1057
1058     /* [AS] Adjudication threshold */
1059     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1060
1061     InitEngine(&first, 0);
1062     InitEngine(&second, 1);
1063     CommonEngineInit();
1064
1065     pairing.which = "pairing"; // pairing engine
1066     pairing.pr = NoProc;
1067     pairing.isr = NULL;
1068     pairing.program = appData.pairingEngine;
1069     pairing.host = "localhost";
1070     pairing.dir = ".";
1071
1072     if (appData.icsActive) {
1073         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1074     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1075         appData.clockMode = FALSE;
1076         first.sendTime = second.sendTime = 0;
1077     }
1078
1079 #if ZIPPY
1080     /* Override some settings from environment variables, for backward
1081        compatibility.  Unfortunately it's not feasible to have the env
1082        vars just set defaults, at least in xboard.  Ugh.
1083     */
1084     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1085       ZippyInit();
1086     }
1087 #endif
1088
1089     if (!appData.icsActive) {
1090       char buf[MSG_SIZ];
1091       int len;
1092
1093       /* Check for variants that are supported only in ICS mode,
1094          or not at all.  Some that are accepted here nevertheless
1095          have bugs; see comments below.
1096       */
1097       VariantClass variant = StringToVariant(appData.variant);
1098       switch (variant) {
1099       case VariantBughouse:     /* need four players and two boards */
1100       case VariantKriegspiel:   /* need to hide pieces and move details */
1101         /* case VariantFischeRandom: (Fabien: moved below) */
1102         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1103         if( (len >= MSG_SIZ) && appData.debugMode )
1104           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1105
1106         DisplayFatalError(buf, 0, 2);
1107         return;
1108
1109       case VariantUnknown:
1110       case VariantLoadable:
1111       case Variant29:
1112       case Variant30:
1113       case Variant31:
1114       case Variant32:
1115       case Variant33:
1116       case Variant34:
1117       case Variant35:
1118       case Variant36:
1119       default:
1120         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1121         if( (len >= MSG_SIZ) && appData.debugMode )
1122           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1123
1124         DisplayFatalError(buf, 0, 2);
1125         return;
1126
1127       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1128       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1129       case VariantGothic:     /* [HGM] should work */
1130       case VariantCapablanca: /* [HGM] should work */
1131       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1132       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1133       case VariantKnightmate: /* [HGM] should work */
1134       case VariantCylinder:   /* [HGM] untested */
1135       case VariantFalcon:     /* [HGM] untested */
1136       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1137                                  offboard interposition not understood */
1138       case VariantNormal:     /* definitely works! */
1139       case VariantWildCastle: /* pieces not automatically shuffled */
1140       case VariantNoCastle:   /* pieces not automatically shuffled */
1141       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1142       case VariantLosers:     /* should work except for win condition,
1143                                  and doesn't know captures are mandatory */
1144       case VariantSuicide:    /* should work except for win condition,
1145                                  and doesn't know captures are mandatory */
1146       case VariantGiveaway:   /* should work except for win condition,
1147                                  and doesn't know captures are mandatory */
1148       case VariantTwoKings:   /* should work */
1149       case VariantAtomic:     /* should work except for win condition */
1150       case Variant3Check:     /* should work except for win condition */
1151       case VariantShatranj:   /* should work except for all win conditions */
1152       case VariantMakruk:     /* should work except for draw countdown */
1153       case VariantBerolina:   /* might work if TestLegality is off */
1154       case VariantCapaRandom: /* should work */
1155       case VariantJanus:      /* should work */
1156       case VariantSuper:      /* experimental */
1157       case VariantGreat:      /* experimental, requires legality testing to be off */
1158       case VariantSChess:     /* S-Chess, should work */
1159       case VariantGrand:      /* should work */
1160       case VariantSpartan:    /* should work */
1161         break;
1162       }
1163     }
1164
1165 }
1166
1167 int
1168 NextIntegerFromString (char ** str, long * value)
1169 {
1170     int result = -1;
1171     char * s = *str;
1172
1173     while( *s == ' ' || *s == '\t' ) {
1174         s++;
1175     }
1176
1177     *value = 0;
1178
1179     if( *s >= '0' && *s <= '9' ) {
1180         while( *s >= '0' && *s <= '9' ) {
1181             *value = *value * 10 + (*s - '0');
1182             s++;
1183         }
1184
1185         result = 0;
1186     }
1187
1188     *str = s;
1189
1190     return result;
1191 }
1192
1193 int
1194 NextTimeControlFromString (char ** str, long * value)
1195 {
1196     long temp;
1197     int result = NextIntegerFromString( str, &temp );
1198
1199     if( result == 0 ) {
1200         *value = temp * 60; /* Minutes */
1201         if( **str == ':' ) {
1202             (*str)++;
1203             result = NextIntegerFromString( str, &temp );
1204             *value += temp; /* Seconds */
1205         }
1206     }
1207
1208     return result;
1209 }
1210
1211 int
1212 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1213 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1214     int result = -1, type = 0; long temp, temp2;
1215
1216     if(**str != ':') return -1; // old params remain in force!
1217     (*str)++;
1218     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1219     if( NextIntegerFromString( str, &temp ) ) return -1;
1220     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1221
1222     if(**str != '/') {
1223         /* time only: incremental or sudden-death time control */
1224         if(**str == '+') { /* increment follows; read it */
1225             (*str)++;
1226             if(**str == '!') type = *(*str)++; // Bronstein TC
1227             if(result = NextIntegerFromString( str, &temp2)) return -1;
1228             *inc = temp2 * 1000;
1229             if(**str == '.') { // read fraction of increment
1230                 char *start = ++(*str);
1231                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1232                 temp2 *= 1000;
1233                 while(start++ < *str) temp2 /= 10;
1234                 *inc += temp2;
1235             }
1236         } else *inc = 0;
1237         *moves = 0; *tc = temp * 1000; *incType = type;
1238         return 0;
1239     }
1240
1241     (*str)++; /* classical time control */
1242     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1243
1244     if(result == 0) {
1245         *moves = temp;
1246         *tc    = temp2 * 1000;
1247         *inc   = 0;
1248         *incType = type;
1249     }
1250     return result;
1251 }
1252
1253 int
1254 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1255 {   /* [HGM] get time to add from the multi-session time-control string */
1256     int incType, moves=1; /* kludge to force reading of first session */
1257     long time, increment;
1258     char *s = tcString;
1259
1260     if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1261     do {
1262         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1263         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1264         if(movenr == -1) return time;    /* last move before new session     */
1265         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1266         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1267         if(!moves) return increment;     /* current session is incremental   */
1268         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1269     } while(movenr >= -1);               /* try again for next session       */
1270
1271     return 0; // no new time quota on this move
1272 }
1273
1274 int
1275 ParseTimeControl (char *tc, float ti, int mps)
1276 {
1277   long tc1;
1278   long tc2;
1279   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1280   int min, sec=0;
1281
1282   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1283   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1284       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1285   if(ti > 0) {
1286
1287     if(mps)
1288       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1289     else
1290       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1291   } else {
1292     if(mps)
1293       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1294     else
1295       snprintf(buf, MSG_SIZ, ":%s", mytc);
1296   }
1297   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1298
1299   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1300     return FALSE;
1301   }
1302
1303   if( *tc == '/' ) {
1304     /* Parse second time control */
1305     tc++;
1306
1307     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1308       return FALSE;
1309     }
1310
1311     if( tc2 == 0 ) {
1312       return FALSE;
1313     }
1314
1315     timeControl_2 = tc2 * 1000;
1316   }
1317   else {
1318     timeControl_2 = 0;
1319   }
1320
1321   if( tc1 == 0 ) {
1322     return FALSE;
1323   }
1324
1325   timeControl = tc1 * 1000;
1326
1327   if (ti >= 0) {
1328     timeIncrement = ti * 1000;  /* convert to ms */
1329     movesPerSession = 0;
1330   } else {
1331     timeIncrement = 0;
1332     movesPerSession = mps;
1333   }
1334   return TRUE;
1335 }
1336
1337 void
1338 InitBackEnd2 ()
1339 {
1340     if (appData.debugMode) {
1341         fprintf(debugFP, "%s\n", programVersion);
1342     }
1343     ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1344
1345     set_cont_sequence(appData.wrapContSeq);
1346     if (appData.matchGames > 0) {
1347         appData.matchMode = TRUE;
1348     } else if (appData.matchMode) {
1349         appData.matchGames = 1;
1350     }
1351     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1352         appData.matchGames = appData.sameColorGames;
1353     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1354         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1355         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1356     }
1357     Reset(TRUE, FALSE);
1358     if (appData.noChessProgram || first.protocolVersion == 1) {
1359       InitBackEnd3();
1360     } else {
1361       /* kludge: allow timeout for initial "feature" commands */
1362       FreezeUI();
1363       DisplayMessage("", _("Starting chess program"));
1364       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1365     }
1366 }
1367
1368 int
1369 CalculateIndex (int index, int gameNr)
1370 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1371     int res;
1372     if(index > 0) return index; // fixed nmber
1373     if(index == 0) return 1;
1374     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1375     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1376     return res;
1377 }
1378
1379 int
1380 LoadGameOrPosition (int gameNr)
1381 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1382     if (*appData.loadGameFile != NULLCHAR) {
1383         if (!LoadGameFromFile(appData.loadGameFile,
1384                 CalculateIndex(appData.loadGameIndex, gameNr),
1385                               appData.loadGameFile, FALSE)) {
1386             DisplayFatalError(_("Bad game file"), 0, 1);
1387             return 0;
1388         }
1389     } else if (*appData.loadPositionFile != NULLCHAR) {
1390         if (!LoadPositionFromFile(appData.loadPositionFile,
1391                 CalculateIndex(appData.loadPositionIndex, gameNr),
1392                                   appData.loadPositionFile)) {
1393             DisplayFatalError(_("Bad position file"), 0, 1);
1394             return 0;
1395         }
1396     }
1397     return 1;
1398 }
1399
1400 void
1401 ReserveGame (int gameNr, char resChar)
1402 {
1403     FILE *tf = fopen(appData.tourneyFile, "r+");
1404     char *p, *q, c, buf[MSG_SIZ];
1405     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1406     safeStrCpy(buf, lastMsg, MSG_SIZ);
1407     DisplayMessage(_("Pick new game"), "");
1408     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1409     ParseArgsFromFile(tf);
1410     p = q = appData.results;
1411     if(appData.debugMode) {
1412       char *r = appData.participants;
1413       fprintf(debugFP, "results = '%s'\n", p);
1414       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1415       fprintf(debugFP, "\n");
1416     }
1417     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1418     nextGame = q - p;
1419     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1420     safeStrCpy(q, p, strlen(p) + 2);
1421     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1422     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1423     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1424         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1425         q[nextGame] = '*';
1426     }
1427     fseek(tf, -(strlen(p)+4), SEEK_END);
1428     c = fgetc(tf);
1429     if(c != '"') // depending on DOS or Unix line endings we can be one off
1430          fseek(tf, -(strlen(p)+2), SEEK_END);
1431     else fseek(tf, -(strlen(p)+3), SEEK_END);
1432     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1433     DisplayMessage(buf, "");
1434     free(p); appData.results = q;
1435     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1436        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1437       int round = appData.defaultMatchGames * appData.tourneyType;
1438       if(gameNr < 0 || appData.tourneyType < 1 ||  // gauntlet engine can always stay loaded as first engine
1439          appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1440         UnloadEngine(&first);  // next game belongs to other pairing;
1441         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1442     }
1443     if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1444 }
1445
1446 void
1447 MatchEvent (int mode)
1448 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1449         int dummy;
1450         if(matchMode) { // already in match mode: switch it off
1451             abortMatch = TRUE;
1452             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1453             return;
1454         }
1455 //      if(gameMode != BeginningOfGame) {
1456 //          DisplayError(_("You can only start a match from the initial position."), 0);
1457 //          return;
1458 //      }
1459         abortMatch = FALSE;
1460         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1461         /* Set up machine vs. machine match */
1462         nextGame = 0;
1463         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1464         if(appData.tourneyFile[0]) {
1465             ReserveGame(-1, 0);
1466             if(nextGame > appData.matchGames) {
1467                 char buf[MSG_SIZ];
1468                 if(strchr(appData.results, '*') == NULL) {
1469                     FILE *f;
1470                     appData.tourneyCycles++;
1471                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1472                         fclose(f);
1473                         NextTourneyGame(-1, &dummy);
1474                         ReserveGame(-1, 0);
1475                         if(nextGame <= appData.matchGames) {
1476                             DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1477                             matchMode = mode;
1478                             ScheduleDelayedEvent(NextMatchGame, 10000);
1479                             return;
1480                         }
1481                     }
1482                 }
1483                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1484                 DisplayError(buf, 0);
1485                 appData.tourneyFile[0] = 0;
1486                 return;
1487             }
1488         } else
1489         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1490             DisplayFatalError(_("Can't have a match with no chess programs"),
1491                               0, 2);
1492             return;
1493         }
1494         matchMode = mode;
1495         matchGame = roundNr = 1;
1496         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1497         NextMatchGame();
1498 }
1499
1500 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1501
1502 void
1503 InitBackEnd3 P((void))
1504 {
1505     GameMode initialMode;
1506     char buf[MSG_SIZ];
1507     int err, len;
1508
1509     InitChessProgram(&first, startedFromSetupPosition);
1510
1511     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1512         free(programVersion);
1513         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1514         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1515         FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1516     }
1517
1518     if (appData.icsActive) {
1519 #ifdef WIN32
1520         /* [DM] Make a console window if needed [HGM] merged ifs */
1521         ConsoleCreate();
1522 #endif
1523         err = establish();
1524         if (err != 0)
1525           {
1526             if (*appData.icsCommPort != NULLCHAR)
1527               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1528                              appData.icsCommPort);
1529             else
1530               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1531                         appData.icsHost, appData.icsPort);
1532
1533             if( (len >= MSG_SIZ) && appData.debugMode )
1534               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1535
1536             DisplayFatalError(buf, err, 1);
1537             return;
1538         }
1539         SetICSMode();
1540         telnetISR =
1541           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1542         fromUserISR =
1543           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1544         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1545             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1546     } else if (appData.noChessProgram) {
1547         SetNCPMode();
1548     } else {
1549         SetGNUMode();
1550     }
1551
1552     if (*appData.cmailGameName != NULLCHAR) {
1553         SetCmailMode();
1554         OpenLoopback(&cmailPR);
1555         cmailISR =
1556           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1557     }
1558
1559     ThawUI();
1560     DisplayMessage("", "");
1561     if (StrCaseCmp(appData.initialMode, "") == 0) {
1562       initialMode = BeginningOfGame;
1563       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1564         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1565         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1566         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1567         ModeHighlight();
1568       }
1569     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1570       initialMode = TwoMachinesPlay;
1571     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1572       initialMode = AnalyzeFile;
1573     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1574       initialMode = AnalyzeMode;
1575     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1576       initialMode = MachinePlaysWhite;
1577     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1578       initialMode = MachinePlaysBlack;
1579     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1580       initialMode = EditGame;
1581     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1582       initialMode = EditPosition;
1583     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1584       initialMode = Training;
1585     } else {
1586       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1587       if( (len >= MSG_SIZ) && appData.debugMode )
1588         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1589
1590       DisplayFatalError(buf, 0, 2);
1591       return;
1592     }
1593
1594     if (appData.matchMode) {
1595         if(appData.tourneyFile[0]) { // start tourney from command line
1596             FILE *f;
1597             if(f = fopen(appData.tourneyFile, "r")) {
1598                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1599                 fclose(f);
1600                 appData.clockMode = TRUE;
1601                 SetGNUMode();
1602             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1603         }
1604         MatchEvent(TRUE);
1605     } else if (*appData.cmailGameName != NULLCHAR) {
1606         /* Set up cmail mode */
1607         ReloadCmailMsgEvent(TRUE);
1608     } else {
1609         /* Set up other modes */
1610         if (initialMode == AnalyzeFile) {
1611           if (*appData.loadGameFile == NULLCHAR) {
1612             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1613             return;
1614           }
1615         }
1616         if (*appData.loadGameFile != NULLCHAR) {
1617             (void) LoadGameFromFile(appData.loadGameFile,
1618                                     appData.loadGameIndex,
1619                                     appData.loadGameFile, TRUE);
1620         } else if (*appData.loadPositionFile != NULLCHAR) {
1621             (void) LoadPositionFromFile(appData.loadPositionFile,
1622                                         appData.loadPositionIndex,
1623                                         appData.loadPositionFile);
1624             /* [HGM] try to make self-starting even after FEN load */
1625             /* to allow automatic setup of fairy variants with wtm */
1626             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1627                 gameMode = BeginningOfGame;
1628                 setboardSpoiledMachineBlack = 1;
1629             }
1630             /* [HGM] loadPos: make that every new game uses the setup */
1631             /* from file as long as we do not switch variant          */
1632             if(!blackPlaysFirst) {
1633                 startedFromPositionFile = TRUE;
1634                 CopyBoard(filePosition, boards[0]);
1635             }
1636         }
1637         if (initialMode == AnalyzeMode) {
1638           if (appData.noChessProgram) {
1639             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1640             return;
1641           }
1642           if (appData.icsActive) {
1643             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1644             return;
1645           }
1646           AnalyzeModeEvent();
1647         } else if (initialMode == AnalyzeFile) {
1648           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1649           ShowThinkingEvent();
1650           AnalyzeFileEvent();
1651           AnalysisPeriodicEvent(1);
1652         } else if (initialMode == MachinePlaysWhite) {
1653           if (appData.noChessProgram) {
1654             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1655                               0, 2);
1656             return;
1657           }
1658           if (appData.icsActive) {
1659             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1660                               0, 2);
1661             return;
1662           }
1663           MachineWhiteEvent();
1664         } else if (initialMode == MachinePlaysBlack) {
1665           if (appData.noChessProgram) {
1666             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1667                               0, 2);
1668             return;
1669           }
1670           if (appData.icsActive) {
1671             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1672                               0, 2);
1673             return;
1674           }
1675           MachineBlackEvent();
1676         } else if (initialMode == TwoMachinesPlay) {
1677           if (appData.noChessProgram) {
1678             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1679                               0, 2);
1680             return;
1681           }
1682           if (appData.icsActive) {
1683             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1684                               0, 2);
1685             return;
1686           }
1687           TwoMachinesEvent();
1688         } else if (initialMode == EditGame) {
1689           EditGameEvent();
1690         } else if (initialMode == EditPosition) {
1691           EditPositionEvent();
1692         } else if (initialMode == Training) {
1693           if (*appData.loadGameFile == NULLCHAR) {
1694             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1695             return;
1696           }
1697           TrainingEvent();
1698         }
1699     }
1700 }
1701
1702 void
1703 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1704 {
1705     DisplayBook(current+1);
1706
1707     MoveHistorySet( movelist, first, last, current, pvInfoList );
1708
1709     EvalGraphSet( first, last, current, pvInfoList );
1710
1711     MakeEngineOutputTitle();
1712 }
1713
1714 /*
1715  * Establish will establish a contact to a remote host.port.
1716  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1717  *  used to talk to the host.
1718  * Returns 0 if okay, error code if not.
1719  */
1720 int
1721 establish ()
1722 {
1723     char buf[MSG_SIZ];
1724
1725     if (*appData.icsCommPort != NULLCHAR) {
1726         /* Talk to the host through a serial comm port */
1727         return OpenCommPort(appData.icsCommPort, &icsPR);
1728
1729     } else if (*appData.gateway != NULLCHAR) {
1730         if (*appData.remoteShell == NULLCHAR) {
1731             /* Use the rcmd protocol to run telnet program on a gateway host */
1732             snprintf(buf, sizeof(buf), "%s %s %s",
1733                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1734             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1735
1736         } else {
1737             /* Use the rsh program to run telnet program on a gateway host */
1738             if (*appData.remoteUser == NULLCHAR) {
1739                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1740                         appData.gateway, appData.telnetProgram,
1741                         appData.icsHost, appData.icsPort);
1742             } else {
1743                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1744                         appData.remoteShell, appData.gateway,
1745                         appData.remoteUser, appData.telnetProgram,
1746                         appData.icsHost, appData.icsPort);
1747             }
1748             return StartChildProcess(buf, "", &icsPR);
1749
1750         }
1751     } else if (appData.useTelnet) {
1752         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1753
1754     } else {
1755         /* TCP socket interface differs somewhat between
1756            Unix and NT; handle details in the front end.
1757            */
1758         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1759     }
1760 }
1761
1762 void
1763 EscapeExpand (char *p, char *q)
1764 {       // [HGM] initstring: routine to shape up string arguments
1765         while(*p++ = *q++) if(p[-1] == '\\')
1766             switch(*q++) {
1767                 case 'n': p[-1] = '\n'; break;
1768                 case 'r': p[-1] = '\r'; break;
1769                 case 't': p[-1] = '\t'; break;
1770                 case '\\': p[-1] = '\\'; break;
1771                 case 0: *p = 0; return;
1772                 default: p[-1] = q[-1]; break;
1773             }
1774 }
1775
1776 void
1777 show_bytes (FILE *fp, char *buf, int count)
1778 {
1779     while (count--) {
1780         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1781             fprintf(fp, "\\%03o", *buf & 0xff);
1782         } else {
1783             putc(*buf, fp);
1784         }
1785         buf++;
1786     }
1787     fflush(fp);
1788 }
1789
1790 /* Returns an errno value */
1791 int
1792 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1793 {
1794     char buf[8192], *p, *q, *buflim;
1795     int left, newcount, outcount;
1796
1797     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1798         *appData.gateway != NULLCHAR) {
1799         if (appData.debugMode) {
1800             fprintf(debugFP, ">ICS: ");
1801             show_bytes(debugFP, message, count);
1802             fprintf(debugFP, "\n");
1803         }
1804         return OutputToProcess(pr, message, count, outError);
1805     }
1806
1807     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1808     p = message;
1809     q = buf;
1810     left = count;
1811     newcount = 0;
1812     while (left) {
1813         if (q >= buflim) {
1814             if (appData.debugMode) {
1815                 fprintf(debugFP, ">ICS: ");
1816                 show_bytes(debugFP, buf, newcount);
1817                 fprintf(debugFP, "\n");
1818             }
1819             outcount = OutputToProcess(pr, buf, newcount, outError);
1820             if (outcount < newcount) return -1; /* to be sure */
1821             q = buf;
1822             newcount = 0;
1823         }
1824         if (*p == '\n') {
1825             *q++ = '\r';
1826             newcount++;
1827         } else if (((unsigned char) *p) == TN_IAC) {
1828             *q++ = (char) TN_IAC;
1829             newcount ++;
1830         }
1831         *q++ = *p++;
1832         newcount++;
1833         left--;
1834     }
1835     if (appData.debugMode) {
1836         fprintf(debugFP, ">ICS: ");
1837         show_bytes(debugFP, buf, newcount);
1838         fprintf(debugFP, "\n");
1839     }
1840     outcount = OutputToProcess(pr, buf, newcount, outError);
1841     if (outcount < newcount) return -1; /* to be sure */
1842     return count;
1843 }
1844
1845 void
1846 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1847 {
1848     int outError, outCount;
1849     static int gotEof = 0;
1850     static FILE *ini;
1851
1852     /* Pass data read from player on to ICS */
1853     if (count > 0) {
1854         gotEof = 0;
1855         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1856         if (outCount < count) {
1857             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1858         }
1859         if(have_sent_ICS_logon == 2) {
1860           if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1861             fprintf(ini, "%s", message);
1862             have_sent_ICS_logon = 3;
1863           } else
1864             have_sent_ICS_logon = 1;
1865         } else if(have_sent_ICS_logon == 3) {
1866             fprintf(ini, "%s", message);
1867             fclose(ini);
1868           have_sent_ICS_logon = 1;
1869         }
1870     } else if (count < 0) {
1871         RemoveInputSource(isr);
1872         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1873     } else if (gotEof++ > 0) {
1874         RemoveInputSource(isr);
1875         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1876     }
1877 }
1878
1879 void
1880 KeepAlive ()
1881 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1882     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1883     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1884     SendToICS("date\n");
1885     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1886 }
1887
1888 /* added routine for printf style output to ics */
1889 void
1890 ics_printf (char *format, ...)
1891 {
1892     char buffer[MSG_SIZ];
1893     va_list args;
1894
1895     va_start(args, format);
1896     vsnprintf(buffer, sizeof(buffer), format, args);
1897     buffer[sizeof(buffer)-1] = '\0';
1898     SendToICS(buffer);
1899     va_end(args);
1900 }
1901
1902 void
1903 SendToICS (char *s)
1904 {
1905     int count, outCount, outError;
1906
1907     if (icsPR == NoProc) return;
1908
1909     count = strlen(s);
1910     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1911     if (outCount < count) {
1912         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1913     }
1914 }
1915
1916 /* This is used for sending logon scripts to the ICS. Sending
1917    without a delay causes problems when using timestamp on ICC
1918    (at least on my machine). */
1919 void
1920 SendToICSDelayed (char *s, long msdelay)
1921 {
1922     int count, outCount, outError;
1923
1924     if (icsPR == NoProc) return;
1925
1926     count = strlen(s);
1927     if (appData.debugMode) {
1928         fprintf(debugFP, ">ICS: ");
1929         show_bytes(debugFP, s, count);
1930         fprintf(debugFP, "\n");
1931     }
1932     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1933                                       msdelay);
1934     if (outCount < count) {
1935         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1936     }
1937 }
1938
1939
1940 /* Remove all highlighting escape sequences in s
1941    Also deletes any suffix starting with '('
1942    */
1943 char *
1944 StripHighlightAndTitle (char *s)
1945 {
1946     static char retbuf[MSG_SIZ];
1947     char *p = retbuf;
1948
1949     while (*s != NULLCHAR) {
1950         while (*s == '\033') {
1951             while (*s != NULLCHAR && !isalpha(*s)) s++;
1952             if (*s != NULLCHAR) s++;
1953         }
1954         while (*s != NULLCHAR && *s != '\033') {
1955             if (*s == '(' || *s == '[') {
1956                 *p = NULLCHAR;
1957                 return retbuf;
1958             }
1959             *p++ = *s++;
1960         }
1961     }
1962     *p = NULLCHAR;
1963     return retbuf;
1964 }
1965
1966 /* Remove all highlighting escape sequences in s */
1967 char *
1968 StripHighlight (char *s)
1969 {
1970     static char retbuf[MSG_SIZ];
1971     char *p = retbuf;
1972
1973     while (*s != NULLCHAR) {
1974         while (*s == '\033') {
1975             while (*s != NULLCHAR && !isalpha(*s)) s++;
1976             if (*s != NULLCHAR) s++;
1977         }
1978         while (*s != NULLCHAR && *s != '\033') {
1979             *p++ = *s++;
1980         }
1981     }
1982     *p = NULLCHAR;
1983     return retbuf;
1984 }
1985
1986 char *variantNames[] = VARIANT_NAMES;
1987 char *
1988 VariantName (VariantClass v)
1989 {
1990     return variantNames[v];
1991 }
1992
1993
1994 /* Identify a variant from the strings the chess servers use or the
1995    PGN Variant tag names we use. */
1996 VariantClass
1997 StringToVariant (char *e)
1998 {
1999     char *p;
2000     int wnum = -1;
2001     VariantClass v = VariantNormal;
2002     int i, found = FALSE;
2003     char buf[MSG_SIZ];
2004     int len;
2005
2006     if (!e) return v;
2007
2008     /* [HGM] skip over optional board-size prefixes */
2009     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
2010         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
2011         while( *e++ != '_');
2012     }
2013
2014     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2015         v = VariantNormal;
2016         found = TRUE;
2017     } else
2018     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2019       if (StrCaseStr(e, variantNames[i])) {
2020         v = (VariantClass) i;
2021         found = TRUE;
2022         break;
2023       }
2024     }
2025
2026     if (!found) {
2027       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2028           || StrCaseStr(e, "wild/fr")
2029           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2030         v = VariantFischeRandom;
2031       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2032                  (i = 1, p = StrCaseStr(e, "w"))) {
2033         p += i;
2034         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2035         if (isdigit(*p)) {
2036           wnum = atoi(p);
2037         } else {
2038           wnum = -1;
2039         }
2040         switch (wnum) {
2041         case 0: /* FICS only, actually */
2042         case 1:
2043           /* Castling legal even if K starts on d-file */
2044           v = VariantWildCastle;
2045           break;
2046         case 2:
2047         case 3:
2048         case 4:
2049           /* Castling illegal even if K & R happen to start in
2050              normal positions. */
2051           v = VariantNoCastle;
2052           break;
2053         case 5:
2054         case 7:
2055         case 8:
2056         case 10:
2057         case 11:
2058         case 12:
2059         case 13:
2060         case 14:
2061         case 15:
2062         case 18:
2063         case 19:
2064           /* Castling legal iff K & R start in normal positions */
2065           v = VariantNormal;
2066           break;
2067         case 6:
2068         case 20:
2069         case 21:
2070           /* Special wilds for position setup; unclear what to do here */
2071           v = VariantLoadable;
2072           break;
2073         case 9:
2074           /* Bizarre ICC game */
2075           v = VariantTwoKings;
2076           break;
2077         case 16:
2078           v = VariantKriegspiel;
2079           break;
2080         case 17:
2081           v = VariantLosers;
2082           break;
2083         case 22:
2084           v = VariantFischeRandom;
2085           break;
2086         case 23:
2087           v = VariantCrazyhouse;
2088           break;
2089         case 24:
2090           v = VariantBughouse;
2091           break;
2092         case 25:
2093           v = Variant3Check;
2094           break;
2095         case 26:
2096           /* Not quite the same as FICS suicide! */
2097           v = VariantGiveaway;
2098           break;
2099         case 27:
2100           v = VariantAtomic;
2101           break;
2102         case 28:
2103           v = VariantShatranj;
2104           break;
2105
2106         /* Temporary names for future ICC types.  The name *will* change in
2107            the next xboard/WinBoard release after ICC defines it. */
2108         case 29:
2109           v = Variant29;
2110           break;
2111         case 30:
2112           v = Variant30;
2113           break;
2114         case 31:
2115           v = Variant31;
2116           break;
2117         case 32:
2118           v = Variant32;
2119           break;
2120         case 33:
2121           v = Variant33;
2122           break;
2123         case 34:
2124           v = Variant34;
2125           break;
2126         case 35:
2127           v = Variant35;
2128           break;
2129         case 36:
2130           v = Variant36;
2131           break;
2132         case 37:
2133           v = VariantShogi;
2134           break;
2135         case 38:
2136           v = VariantXiangqi;
2137           break;
2138         case 39:
2139           v = VariantCourier;
2140           break;
2141         case 40:
2142           v = VariantGothic;
2143           break;
2144         case 41:
2145           v = VariantCapablanca;
2146           break;
2147         case 42:
2148           v = VariantKnightmate;
2149           break;
2150         case 43:
2151           v = VariantFairy;
2152           break;
2153         case 44:
2154           v = VariantCylinder;
2155           break;
2156         case 45:
2157           v = VariantFalcon;
2158           break;
2159         case 46:
2160           v = VariantCapaRandom;
2161           break;
2162         case 47:
2163           v = VariantBerolina;
2164           break;
2165         case 48:
2166           v = VariantJanus;
2167           break;
2168         case 49:
2169           v = VariantSuper;
2170           break;
2171         case 50:
2172           v = VariantGreat;
2173           break;
2174         case -1:
2175           /* Found "wild" or "w" in the string but no number;
2176              must assume it's normal chess. */
2177           v = VariantNormal;
2178           break;
2179         default:
2180           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2181           if( (len >= MSG_SIZ) && appData.debugMode )
2182             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2183
2184           DisplayError(buf, 0);
2185           v = VariantUnknown;
2186           break;
2187         }
2188       }
2189     }
2190     if (appData.debugMode) {
2191       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2192               e, wnum, VariantName(v));
2193     }
2194     return v;
2195 }
2196
2197 static int leftover_start = 0, leftover_len = 0;
2198 char star_match[STAR_MATCH_N][MSG_SIZ];
2199
2200 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2201    advance *index beyond it, and set leftover_start to the new value of
2202    *index; else return FALSE.  If pattern contains the character '*', it
2203    matches any sequence of characters not containing '\r', '\n', or the
2204    character following the '*' (if any), and the matched sequence(s) are
2205    copied into star_match.
2206    */
2207 int
2208 looking_at ( char *buf, int *index, char *pattern)
2209 {
2210     char *bufp = &buf[*index], *patternp = pattern;
2211     int star_count = 0;
2212     char *matchp = star_match[0];
2213
2214     for (;;) {
2215         if (*patternp == NULLCHAR) {
2216             *index = leftover_start = bufp - buf;
2217             *matchp = NULLCHAR;
2218             return TRUE;
2219         }
2220         if (*bufp == NULLCHAR) return FALSE;
2221         if (*patternp == '*') {
2222             if (*bufp == *(patternp + 1)) {
2223                 *matchp = NULLCHAR;
2224                 matchp = star_match[++star_count];
2225                 patternp += 2;
2226                 bufp++;
2227                 continue;
2228             } else if (*bufp == '\n' || *bufp == '\r') {
2229                 patternp++;
2230                 if (*patternp == NULLCHAR)
2231                   continue;
2232                 else
2233                   return FALSE;
2234             } else {
2235                 *matchp++ = *bufp++;
2236                 continue;
2237             }
2238         }
2239         if (*patternp != *bufp) return FALSE;
2240         patternp++;
2241         bufp++;
2242     }
2243 }
2244
2245 void
2246 SendToPlayer (char *data, int length)
2247 {
2248     int error, outCount;
2249     outCount = OutputToProcess(NoProc, data, length, &error);
2250     if (outCount < length) {
2251         DisplayFatalError(_("Error writing to display"), error, 1);
2252     }
2253 }
2254
2255 void
2256 PackHolding (char packed[], char *holding)
2257 {
2258     char *p = holding;
2259     char *q = packed;
2260     int runlength = 0;
2261     int curr = 9999;
2262     do {
2263         if (*p == curr) {
2264             runlength++;
2265         } else {
2266             switch (runlength) {
2267               case 0:
2268                 break;
2269               case 1:
2270                 *q++ = curr;
2271                 break;
2272               case 2:
2273                 *q++ = curr;
2274                 *q++ = curr;
2275                 break;
2276               default:
2277                 sprintf(q, "%d", runlength);
2278                 while (*q) q++;
2279                 *q++ = curr;
2280                 break;
2281             }
2282             runlength = 1;
2283             curr = *p;
2284         }
2285     } while (*p++);
2286     *q = NULLCHAR;
2287 }
2288
2289 /* Telnet protocol requests from the front end */
2290 void
2291 TelnetRequest (unsigned char ddww, unsigned char option)
2292 {
2293     unsigned char msg[3];
2294     int outCount, outError;
2295
2296     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2297
2298     if (appData.debugMode) {
2299         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2300         switch (ddww) {
2301           case TN_DO:
2302             ddwwStr = "DO";
2303             break;
2304           case TN_DONT:
2305             ddwwStr = "DONT";
2306             break;
2307           case TN_WILL:
2308             ddwwStr = "WILL";
2309             break;
2310           case TN_WONT:
2311             ddwwStr = "WONT";
2312             break;
2313           default:
2314             ddwwStr = buf1;
2315             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2316             break;
2317         }
2318         switch (option) {
2319           case TN_ECHO:
2320             optionStr = "ECHO";
2321             break;
2322           default:
2323             optionStr = buf2;
2324             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2325             break;
2326         }
2327         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2328     }
2329     msg[0] = TN_IAC;
2330     msg[1] = ddww;
2331     msg[2] = option;
2332     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2333     if (outCount < 3) {
2334         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2335     }
2336 }
2337
2338 void
2339 DoEcho ()
2340 {
2341     if (!appData.icsActive) return;
2342     TelnetRequest(TN_DO, TN_ECHO);
2343 }
2344
2345 void
2346 DontEcho ()
2347 {
2348     if (!appData.icsActive) return;
2349     TelnetRequest(TN_DONT, TN_ECHO);
2350 }
2351
2352 void
2353 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2354 {
2355     /* put the holdings sent to us by the server on the board holdings area */
2356     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2357     char p;
2358     ChessSquare piece;
2359
2360     if(gameInfo.holdingsWidth < 2)  return;
2361     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2362         return; // prevent overwriting by pre-board holdings
2363
2364     if( (int)lowestPiece >= BlackPawn ) {
2365         holdingsColumn = 0;
2366         countsColumn = 1;
2367         holdingsStartRow = BOARD_HEIGHT-1;
2368         direction = -1;
2369     } else {
2370         holdingsColumn = BOARD_WIDTH-1;
2371         countsColumn = BOARD_WIDTH-2;
2372         holdingsStartRow = 0;
2373         direction = 1;
2374     }
2375
2376     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2377         board[i][holdingsColumn] = EmptySquare;
2378         board[i][countsColumn]   = (ChessSquare) 0;
2379     }
2380     while( (p=*holdings++) != NULLCHAR ) {
2381         piece = CharToPiece( ToUpper(p) );
2382         if(piece == EmptySquare) continue;
2383         /*j = (int) piece - (int) WhitePawn;*/
2384         j = PieceToNumber(piece);
2385         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2386         if(j < 0) continue;               /* should not happen */
2387         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2388         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2389         board[holdingsStartRow+j*direction][countsColumn]++;
2390     }
2391 }
2392
2393
2394 void
2395 VariantSwitch (Board board, VariantClass newVariant)
2396 {
2397    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2398    static Board oldBoard;
2399
2400    startedFromPositionFile = FALSE;
2401    if(gameInfo.variant == newVariant) return;
2402
2403    /* [HGM] This routine is called each time an assignment is made to
2404     * gameInfo.variant during a game, to make sure the board sizes
2405     * are set to match the new variant. If that means adding or deleting
2406     * holdings, we shift the playing board accordingly
2407     * This kludge is needed because in ICS observe mode, we get boards
2408     * of an ongoing game without knowing the variant, and learn about the
2409     * latter only later. This can be because of the move list we requested,
2410     * in which case the game history is refilled from the beginning anyway,
2411     * but also when receiving holdings of a crazyhouse game. In the latter
2412     * case we want to add those holdings to the already received position.
2413     */
2414
2415
2416    if (appData.debugMode) {
2417      fprintf(debugFP, "Switch board from %s to %s\n",
2418              VariantName(gameInfo.variant), VariantName(newVariant));
2419      setbuf(debugFP, NULL);
2420    }
2421    shuffleOpenings = 0;       /* [HGM] shuffle */
2422    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2423    switch(newVariant)
2424      {
2425      case VariantShogi:
2426        newWidth = 9;  newHeight = 9;
2427        gameInfo.holdingsSize = 7;
2428      case VariantBughouse:
2429      case VariantCrazyhouse:
2430        newHoldingsWidth = 2; break;
2431      case VariantGreat:
2432        newWidth = 10;
2433      case VariantSuper:
2434        newHoldingsWidth = 2;
2435        gameInfo.holdingsSize = 8;
2436        break;
2437      case VariantGothic:
2438      case VariantCapablanca:
2439      case VariantCapaRandom:
2440        newWidth = 10;
2441      default:
2442        newHoldingsWidth = gameInfo.holdingsSize = 0;
2443      };
2444
2445    if(newWidth  != gameInfo.boardWidth  ||
2446       newHeight != gameInfo.boardHeight ||
2447       newHoldingsWidth != gameInfo.holdingsWidth ) {
2448
2449      /* shift position to new playing area, if needed */
2450      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2451        for(i=0; i<BOARD_HEIGHT; i++)
2452          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2453            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2454              board[i][j];
2455        for(i=0; i<newHeight; i++) {
2456          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2457          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2458        }
2459      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2460        for(i=0; i<BOARD_HEIGHT; i++)
2461          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2462            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2463              board[i][j];
2464      }
2465      board[HOLDINGS_SET] = 0;
2466      gameInfo.boardWidth  = newWidth;
2467      gameInfo.boardHeight = newHeight;
2468      gameInfo.holdingsWidth = newHoldingsWidth;
2469      gameInfo.variant = newVariant;
2470      InitDrawingSizes(-2, 0);
2471    } else gameInfo.variant = newVariant;
2472    CopyBoard(oldBoard, board);   // remember correctly formatted board
2473      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2474    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2475 }
2476
2477 static int loggedOn = FALSE;
2478
2479 /*-- Game start info cache: --*/
2480 int gs_gamenum;
2481 char gs_kind[MSG_SIZ];
2482 static char player1Name[128] = "";
2483 static char player2Name[128] = "";
2484 static char cont_seq[] = "\n\\   ";
2485 static int player1Rating = -1;
2486 static int player2Rating = -1;
2487 /*----------------------------*/
2488
2489 ColorClass curColor = ColorNormal;
2490 int suppressKibitz = 0;
2491
2492 // [HGM] seekgraph
2493 Boolean soughtPending = FALSE;
2494 Boolean seekGraphUp;
2495 #define MAX_SEEK_ADS 200
2496 #define SQUARE 0x80
2497 char *seekAdList[MAX_SEEK_ADS];
2498 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2499 float tcList[MAX_SEEK_ADS];
2500 char colorList[MAX_SEEK_ADS];
2501 int nrOfSeekAds = 0;
2502 int minRating = 1010, maxRating = 2800;
2503 int hMargin = 10, vMargin = 20, h, w;
2504 extern int squareSize, lineGap;
2505
2506 void
2507 PlotSeekAd (int i)
2508 {
2509         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2510         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2511         if(r < minRating+100 && r >=0 ) r = minRating+100;
2512         if(r > maxRating) r = maxRating;
2513         if(tc < 1.f) tc = 1.f;
2514         if(tc > 95.f) tc = 95.f;
2515         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2516         y = ((double)r - minRating)/(maxRating - minRating)
2517             * (h-vMargin-squareSize/8-1) + vMargin;
2518         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2519         if(strstr(seekAdList[i], " u ")) color = 1;
2520         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2521            !strstr(seekAdList[i], "bullet") &&
2522            !strstr(seekAdList[i], "blitz") &&
2523            !strstr(seekAdList[i], "standard") ) color = 2;
2524         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2525         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2526 }
2527
2528 void
2529 PlotSingleSeekAd (int i)
2530 {
2531         PlotSeekAd(i);
2532 }
2533
2534 void
2535 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2536 {
2537         char buf[MSG_SIZ], *ext = "";
2538         VariantClass v = StringToVariant(type);
2539         if(strstr(type, "wild")) {
2540             ext = type + 4; // append wild number
2541             if(v == VariantFischeRandom) type = "chess960"; else
2542             if(v == VariantLoadable) type = "setup"; else
2543             type = VariantName(v);
2544         }
2545         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2546         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2547             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2548             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2549             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2550             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2551             seekNrList[nrOfSeekAds] = nr;
2552             zList[nrOfSeekAds] = 0;
2553             seekAdList[nrOfSeekAds++] = StrSave(buf);
2554             if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2555         }
2556 }
2557
2558 void
2559 EraseSeekDot (int i)
2560 {
2561     int x = xList[i], y = yList[i], d=squareSize/4, k;
2562     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2563     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2564     // now replot every dot that overlapped
2565     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2566         int xx = xList[k], yy = yList[k];
2567         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2568             DrawSeekDot(xx, yy, colorList[k]);
2569     }
2570 }
2571
2572 void
2573 RemoveSeekAd (int nr)
2574 {
2575         int i;
2576         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2577             EraseSeekDot(i);
2578             if(seekAdList[i]) free(seekAdList[i]);
2579             seekAdList[i] = seekAdList[--nrOfSeekAds];
2580             seekNrList[i] = seekNrList[nrOfSeekAds];
2581             ratingList[i] = ratingList[nrOfSeekAds];
2582             colorList[i]  = colorList[nrOfSeekAds];
2583             tcList[i] = tcList[nrOfSeekAds];
2584             xList[i]  = xList[nrOfSeekAds];
2585             yList[i]  = yList[nrOfSeekAds];
2586             zList[i]  = zList[nrOfSeekAds];
2587             seekAdList[nrOfSeekAds] = NULL;
2588             break;
2589         }
2590 }
2591
2592 Boolean
2593 MatchSoughtLine (char *line)
2594 {
2595     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2596     int nr, base, inc, u=0; char dummy;
2597
2598     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2599        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2600        (u=1) &&
2601        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2602         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2603         // match: compact and save the line
2604         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2605         return TRUE;
2606     }
2607     return FALSE;
2608 }
2609
2610 int
2611 DrawSeekGraph ()
2612 {
2613     int i;
2614     if(!seekGraphUp) return FALSE;
2615     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2616     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2617
2618     DrawSeekBackground(0, 0, w, h);
2619     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2620     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2621     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2622         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2623         yy = h-1-yy;
2624         DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2625         if(i%500 == 0) {
2626             char buf[MSG_SIZ];
2627             snprintf(buf, MSG_SIZ, "%d", i);
2628             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2629         }
2630     }
2631     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2632     for(i=1; i<100; i+=(i<10?1:5)) {
2633         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2634         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2635         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2636             char buf[MSG_SIZ];
2637             snprintf(buf, MSG_SIZ, "%d", i);
2638             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2639         }
2640     }
2641     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2642     return TRUE;
2643 }
2644
2645 int
2646 SeekGraphClick (ClickType click, int x, int y, int moving)
2647 {
2648     static int lastDown = 0, displayed = 0, lastSecond;
2649     if(y < 0) return FALSE;
2650     if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2651         (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2652         if(!seekGraphUp) return FALSE;
2653         seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2654         DrawPosition(TRUE, NULL);
2655         return TRUE;
2656     }
2657     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2658         if(click == Release || moving) return FALSE;
2659         nrOfSeekAds = 0;
2660         soughtPending = TRUE;
2661         SendToICS(ics_prefix);
2662         SendToICS("sought\n"); // should this be "sought all"?
2663     } else { // issue challenge based on clicked ad
2664         int dist = 10000; int i, closest = 0, second = 0;
2665         for(i=0; i<nrOfSeekAds; i++) {
2666             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2667             if(d < dist) { dist = d; closest = i; }
2668             second += (d - zList[i] < 120); // count in-range ads
2669             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2670         }
2671         if(dist < 120) {
2672             char buf[MSG_SIZ];
2673             second = (second > 1);
2674             if(displayed != closest || second != lastSecond) {
2675                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2676                 lastSecond = second; displayed = closest;
2677             }
2678             if(click == Press) {
2679                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2680                 lastDown = closest;
2681                 return TRUE;
2682             } // on press 'hit', only show info
2683             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2684             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2685             SendToICS(ics_prefix);
2686             SendToICS(buf);
2687             return TRUE; // let incoming board of started game pop down the graph
2688         } else if(click == Release) { // release 'miss' is ignored
2689             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2690             if(moving == 2) { // right up-click
2691                 nrOfSeekAds = 0; // refresh graph
2692                 soughtPending = TRUE;
2693                 SendToICS(ics_prefix);
2694                 SendToICS("sought\n"); // should this be "sought all"?
2695             }
2696             return TRUE;
2697         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2698         // press miss or release hit 'pop down' seek graph
2699         seekGraphUp = FALSE;
2700         DrawPosition(TRUE, NULL);
2701     }
2702     return TRUE;
2703 }
2704
2705 void
2706 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2707 {
2708 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2709 #define STARTED_NONE 0
2710 #define STARTED_MOVES 1
2711 #define STARTED_BOARD 2
2712 #define STARTED_OBSERVE 3
2713 #define STARTED_HOLDINGS 4
2714 #define STARTED_CHATTER 5
2715 #define STARTED_COMMENT 6
2716 #define STARTED_MOVES_NOHIDE 7
2717
2718     static int started = STARTED_NONE;
2719     static char parse[20000];
2720     static int parse_pos = 0;
2721     static char buf[BUF_SIZE + 1];
2722     static int firstTime = TRUE, intfSet = FALSE;
2723     static ColorClass prevColor = ColorNormal;
2724     static int savingComment = FALSE;
2725     static int cmatch = 0; // continuation sequence match
2726     char *bp;
2727     char str[MSG_SIZ];
2728     int i, oldi;
2729     int buf_len;
2730     int next_out;
2731     int tkind;
2732     int backup;    /* [DM] For zippy color lines */
2733     char *p;
2734     char talker[MSG_SIZ]; // [HGM] chat
2735     int channel;
2736
2737     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2738
2739     if (appData.debugMode) {
2740       if (!error) {
2741         fprintf(debugFP, "<ICS: ");
2742         show_bytes(debugFP, data, count);
2743         fprintf(debugFP, "\n");
2744       }
2745     }
2746
2747     if (appData.debugMode) { int f = forwardMostMove;
2748         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2749                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2750                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2751     }
2752     if (count > 0) {
2753         /* If last read ended with a partial line that we couldn't parse,
2754            prepend it to the new read and try again. */
2755         if (leftover_len > 0) {
2756             for (i=0; i<leftover_len; i++)
2757               buf[i] = buf[leftover_start + i];
2758         }
2759
2760     /* copy new characters into the buffer */
2761     bp = buf + leftover_len;
2762     buf_len=leftover_len;
2763     for (i=0; i<count; i++)
2764     {
2765         // ignore these
2766         if (data[i] == '\r')
2767             continue;
2768
2769         // join lines split by ICS?
2770         if (!appData.noJoin)
2771         {
2772             /*
2773                 Joining just consists of finding matches against the
2774                 continuation sequence, and discarding that sequence
2775                 if found instead of copying it.  So, until a match
2776                 fails, there's nothing to do since it might be the
2777                 complete sequence, and thus, something we don't want
2778                 copied.
2779             */
2780             if (data[i] == cont_seq[cmatch])
2781             {
2782                 cmatch++;
2783                 if (cmatch == strlen(cont_seq))
2784                 {
2785                     cmatch = 0; // complete match.  just reset the counter
2786
2787                     /*
2788                         it's possible for the ICS to not include the space
2789                         at the end of the last word, making our [correct]
2790                         join operation fuse two separate words.  the server
2791                         does this when the space occurs at the width setting.
2792                     */
2793                     if (!buf_len || buf[buf_len-1] != ' ')
2794                     {
2795                         *bp++ = ' ';
2796                         buf_len++;
2797                     }
2798                 }
2799                 continue;
2800             }
2801             else if (cmatch)
2802             {
2803                 /*
2804                     match failed, so we have to copy what matched before
2805                     falling through and copying this character.  In reality,
2806                     this will only ever be just the newline character, but
2807                     it doesn't hurt to be precise.
2808                 */
2809                 strncpy(bp, cont_seq, cmatch);
2810                 bp += cmatch;
2811                 buf_len += cmatch;
2812                 cmatch = 0;
2813             }
2814         }
2815
2816         // copy this char
2817         *bp++ = data[i];
2818         buf_len++;
2819     }
2820
2821         buf[buf_len] = NULLCHAR;
2822 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2823         next_out = 0;
2824         leftover_start = 0;
2825
2826         i = 0;
2827         while (i < buf_len) {
2828             /* Deal with part of the TELNET option negotiation
2829                protocol.  We refuse to do anything beyond the
2830                defaults, except that we allow the WILL ECHO option,
2831                which ICS uses to turn off password echoing when we are
2832                directly connected to it.  We reject this option
2833                if localLineEditing mode is on (always on in xboard)
2834                and we are talking to port 23, which might be a real
2835                telnet server that will try to keep WILL ECHO on permanently.
2836              */
2837             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2838                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2839                 unsigned char option;
2840                 oldi = i;
2841                 switch ((unsigned char) buf[++i]) {
2842                   case TN_WILL:
2843                     if (appData.debugMode)
2844                       fprintf(debugFP, "\n<WILL ");
2845                     switch (option = (unsigned char) buf[++i]) {
2846                       case TN_ECHO:
2847                         if (appData.debugMode)
2848                           fprintf(debugFP, "ECHO ");
2849                         /* Reply only if this is a change, according
2850                            to the protocol rules. */
2851                         if (remoteEchoOption) break;
2852                         if (appData.localLineEditing &&
2853                             atoi(appData.icsPort) == TN_PORT) {
2854                             TelnetRequest(TN_DONT, TN_ECHO);
2855                         } else {
2856                             EchoOff();
2857                             TelnetRequest(TN_DO, TN_ECHO);
2858                             remoteEchoOption = TRUE;
2859                         }
2860                         break;
2861                       default:
2862                         if (appData.debugMode)
2863                           fprintf(debugFP, "%d ", option);
2864                         /* Whatever this is, we don't want it. */
2865                         TelnetRequest(TN_DONT, option);
2866                         break;
2867                     }
2868                     break;
2869                   case TN_WONT:
2870                     if (appData.debugMode)
2871                       fprintf(debugFP, "\n<WONT ");
2872                     switch (option = (unsigned char) buf[++i]) {
2873                       case TN_ECHO:
2874                         if (appData.debugMode)
2875                           fprintf(debugFP, "ECHO ");
2876                         /* Reply only if this is a change, according
2877                            to the protocol rules. */
2878                         if (!remoteEchoOption) break;
2879                         EchoOn();
2880                         TelnetRequest(TN_DONT, TN_ECHO);
2881                         remoteEchoOption = FALSE;
2882                         break;
2883                       default:
2884                         if (appData.debugMode)
2885                           fprintf(debugFP, "%d ", (unsigned char) option);
2886                         /* Whatever this is, it must already be turned
2887                            off, because we never agree to turn on
2888                            anything non-default, so according to the
2889                            protocol rules, we don't reply. */
2890                         break;
2891                     }
2892                     break;
2893                   case TN_DO:
2894                     if (appData.debugMode)
2895                       fprintf(debugFP, "\n<DO ");
2896                     switch (option = (unsigned char) buf[++i]) {
2897                       default:
2898                         /* Whatever this is, we refuse to do it. */
2899                         if (appData.debugMode)
2900                           fprintf(debugFP, "%d ", option);
2901                         TelnetRequest(TN_WONT, option);
2902                         break;
2903                     }
2904                     break;
2905                   case TN_DONT:
2906                     if (appData.debugMode)
2907                       fprintf(debugFP, "\n<DONT ");
2908                     switch (option = (unsigned char) buf[++i]) {
2909                       default:
2910                         if (appData.debugMode)
2911                           fprintf(debugFP, "%d ", option);
2912                         /* Whatever this is, we are already not doing
2913                            it, because we never agree to do anything
2914                            non-default, so according to the protocol
2915                            rules, we don't reply. */
2916                         break;
2917                     }
2918                     break;
2919                   case TN_IAC:
2920                     if (appData.debugMode)
2921                       fprintf(debugFP, "\n<IAC ");
2922                     /* Doubled IAC; pass it through */
2923                     i--;
2924                     break;
2925                   default:
2926                     if (appData.debugMode)
2927                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2928                     /* Drop all other telnet commands on the floor */
2929                     break;
2930                 }
2931                 if (oldi > next_out)
2932                   SendToPlayer(&buf[next_out], oldi - next_out);
2933                 if (++i > next_out)
2934                   next_out = i;
2935                 continue;
2936             }
2937
2938             /* OK, this at least will *usually* work */
2939             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2940                 loggedOn = TRUE;
2941             }
2942
2943             if (loggedOn && !intfSet) {
2944                 if (ics_type == ICS_ICC) {
2945                   snprintf(str, MSG_SIZ,
2946                           "/set-quietly interface %s\n/set-quietly style 12\n",
2947                           programVersion);
2948                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2949                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2950                 } else if (ics_type == ICS_CHESSNET) {
2951                   snprintf(str, MSG_SIZ, "/style 12\n");
2952                 } else {
2953                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2954                   strcat(str, programVersion);
2955                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2956                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2957                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2958 #ifdef WIN32
2959                   strcat(str, "$iset nohighlight 1\n");
2960 #endif
2961                   strcat(str, "$iset lock 1\n$style 12\n");
2962                 }
2963                 SendToICS(str);
2964                 NotifyFrontendLogin();
2965                 intfSet = TRUE;
2966             }
2967
2968             if (started == STARTED_COMMENT) {
2969                 /* Accumulate characters in comment */
2970                 parse[parse_pos++] = buf[i];
2971                 if (buf[i] == '\n') {
2972                     parse[parse_pos] = NULLCHAR;
2973                     if(chattingPartner>=0) {
2974                         char mess[MSG_SIZ];
2975                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2976                         OutputChatMessage(chattingPartner, mess);
2977                         chattingPartner = -1;
2978                         next_out = i+1; // [HGM] suppress printing in ICS window
2979                     } else
2980                     if(!suppressKibitz) // [HGM] kibitz
2981                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2982                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2983                         int nrDigit = 0, nrAlph = 0, j;
2984                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2985                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2986                         parse[parse_pos] = NULLCHAR;
2987                         // try to be smart: if it does not look like search info, it should go to
2988                         // ICS interaction window after all, not to engine-output window.
2989                         for(j=0; j<parse_pos; j++) { // count letters and digits
2990                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2991                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2992                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2993                         }
2994                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2995                             int depth=0; float score;
2996                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2997                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2998                                 pvInfoList[forwardMostMove-1].depth = depth;
2999                                 pvInfoList[forwardMostMove-1].score = 100*score;
3000                             }
3001                             OutputKibitz(suppressKibitz, parse);
3002                         } else {
3003                             char tmp[MSG_SIZ];
3004                             if(gameMode == IcsObserving) // restore original ICS messages
3005                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3006                             else
3007                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3008                             SendToPlayer(tmp, strlen(tmp));
3009                         }
3010                         next_out = i+1; // [HGM] suppress printing in ICS window
3011                     }
3012                     started = STARTED_NONE;
3013                 } else {
3014                     /* Don't match patterns against characters in comment */
3015                     i++;
3016                     continue;
3017                 }
3018             }
3019             if (started == STARTED_CHATTER) {
3020                 if (buf[i] != '\n') {
3021                     /* Don't match patterns against characters in chatter */
3022                     i++;
3023                     continue;
3024                 }
3025                 started = STARTED_NONE;
3026                 if(suppressKibitz) next_out = i+1;
3027             }
3028
3029             /* Kludge to deal with rcmd protocol */
3030             if (firstTime && looking_at(buf, &i, "\001*")) {
3031                 DisplayFatalError(&buf[1], 0, 1);
3032                 continue;
3033             } else {
3034                 firstTime = FALSE;
3035             }
3036
3037             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3038                 ics_type = ICS_ICC;
3039                 ics_prefix = "/";
3040                 if (appData.debugMode)
3041                   fprintf(debugFP, "ics_type %d\n", ics_type);
3042                 continue;
3043             }
3044             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3045                 ics_type = ICS_FICS;
3046                 ics_prefix = "$";
3047                 if (appData.debugMode)
3048                   fprintf(debugFP, "ics_type %d\n", ics_type);
3049                 continue;
3050             }
3051             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3052                 ics_type = ICS_CHESSNET;
3053                 ics_prefix = "/";
3054                 if (appData.debugMode)
3055                   fprintf(debugFP, "ics_type %d\n", ics_type);
3056                 continue;
3057             }
3058
3059             if (!loggedOn &&
3060                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3061                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3062                  looking_at(buf, &i, "will be \"*\""))) {
3063               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3064               continue;
3065             }
3066
3067             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3068               char buf[MSG_SIZ];
3069               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3070               DisplayIcsInteractionTitle(buf);
3071               have_set_title = TRUE;
3072             }
3073
3074             /* skip finger notes */
3075             if (started == STARTED_NONE &&
3076                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3077                  (buf[i] == '1' && buf[i+1] == '0')) &&
3078                 buf[i+2] == ':' && buf[i+3] == ' ') {
3079               started = STARTED_CHATTER;
3080               i += 3;
3081               continue;
3082             }
3083
3084             oldi = i;
3085             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3086             if(appData.seekGraph) {
3087                 if(soughtPending && MatchSoughtLine(buf+i)) {
3088                     i = strstr(buf+i, "rated") - buf;
3089                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3090                     next_out = leftover_start = i;
3091                     started = STARTED_CHATTER;
3092                     suppressKibitz = TRUE;
3093                     continue;
3094                 }
3095                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3096                         && looking_at(buf, &i, "* ads displayed")) {
3097                     soughtPending = FALSE;
3098                     seekGraphUp = TRUE;
3099                     DrawSeekGraph();
3100                     continue;
3101                 }
3102                 if(appData.autoRefresh) {
3103                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3104                         int s = (ics_type == ICS_ICC); // ICC format differs
3105                         if(seekGraphUp)
3106                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3107                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3108                         looking_at(buf, &i, "*% "); // eat prompt
3109                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3110                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3111                         next_out = i; // suppress
3112                         continue;
3113                     }
3114                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3115                         char *p = star_match[0];
3116                         while(*p) {
3117                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3118                             while(*p && *p++ != ' '); // next
3119                         }
3120                         looking_at(buf, &i, "*% "); // eat prompt
3121                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3122                         next_out = i;
3123                         continue;
3124                     }
3125                 }
3126             }
3127
3128             /* skip formula vars */
3129             if (started == STARTED_NONE &&
3130                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3131               started = STARTED_CHATTER;
3132               i += 3;
3133               continue;
3134             }
3135
3136             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3137             if (appData.autoKibitz && started == STARTED_NONE &&
3138                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3139                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3140                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3141                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3142                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3143                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3144                         suppressKibitz = TRUE;
3145                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3146                         next_out = i;
3147                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3148                                 && (gameMode == IcsPlayingWhite)) ||
3149                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3150                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3151                             started = STARTED_CHATTER; // own kibitz we simply discard
3152                         else {
3153                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3154                             parse_pos = 0; parse[0] = NULLCHAR;
3155                             savingComment = TRUE;
3156                             suppressKibitz = gameMode != IcsObserving ? 2 :
3157                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3158                         }
3159                         continue;
3160                 } else
3161                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3162                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3163                          && atoi(star_match[0])) {
3164                     // suppress the acknowledgements of our own autoKibitz
3165                     char *p;
3166                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3167                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3168                     SendToPlayer(star_match[0], strlen(star_match[0]));
3169                     if(looking_at(buf, &i, "*% ")) // eat prompt
3170                         suppressKibitz = FALSE;
3171                     next_out = i;
3172                     continue;
3173                 }
3174             } // [HGM] kibitz: end of patch
3175
3176             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3177
3178             // [HGM] chat: intercept tells by users for which we have an open chat window
3179             channel = -1;
3180             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3181                                            looking_at(buf, &i, "* whispers:") ||
3182                                            looking_at(buf, &i, "* kibitzes:") ||
3183                                            looking_at(buf, &i, "* shouts:") ||
3184                                            looking_at(buf, &i, "* c-shouts:") ||
3185                                            looking_at(buf, &i, "--> * ") ||
3186                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3187                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3188                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3189                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3190                 int p;
3191                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3192                 chattingPartner = -1;
3193
3194                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3195                 for(p=0; p<MAX_CHAT; p++) {
3196                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3197                     talker[0] = '['; strcat(talker, "] ");
3198                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3199                     chattingPartner = p; break;
3200                     }
3201                 } else
3202                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3203                 for(p=0; p<MAX_CHAT; p++) {
3204                     if(!strcmp("kibitzes", chatPartner[p])) {
3205                         talker[0] = '['; strcat(talker, "] ");
3206                         chattingPartner = p; break;
3207                     }
3208                 } else
3209                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3210                 for(p=0; p<MAX_CHAT; p++) {
3211                     if(!strcmp("whispers", chatPartner[p])) {
3212                         talker[0] = '['; strcat(talker, "] ");
3213                         chattingPartner = p; break;
3214                     }
3215                 } else
3216                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3217                   if(buf[i-8] == '-' && buf[i-3] == 't')
3218                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3219                     if(!strcmp("c-shouts", chatPartner[p])) {
3220                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3221                         chattingPartner = p; break;
3222                     }
3223                   }
3224                   if(chattingPartner < 0)
3225                   for(p=0; p<MAX_CHAT; p++) {
3226                     if(!strcmp("shouts", chatPartner[p])) {
3227                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3228                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3229                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3230                         chattingPartner = p; break;
3231                     }
3232                   }
3233                 }
3234                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3235                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3236                     talker[0] = 0; Colorize(ColorTell, FALSE);
3237                     chattingPartner = p; break;
3238                 }
3239                 if(chattingPartner<0) i = oldi; else {
3240                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3241                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3242                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3243                     started = STARTED_COMMENT;
3244                     parse_pos = 0; parse[0] = NULLCHAR;
3245                     savingComment = 3 + chattingPartner; // counts as TRUE
3246                     suppressKibitz = TRUE;
3247                     continue;
3248                 }
3249             } // [HGM] chat: end of patch
3250
3251           backup = i;
3252             if (appData.zippyTalk || appData.zippyPlay) {
3253                 /* [DM] Backup address for color zippy lines */
3254 #if ZIPPY
3255                if (loggedOn == TRUE)
3256                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3257                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3258 #endif
3259             } // [DM] 'else { ' deleted
3260                 if (
3261                     /* Regular tells and says */
3262                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3263                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3264                     looking_at(buf, &i, "* says: ") ||
3265                     /* Don't color "message" or "messages" output */
3266                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3267                     looking_at(buf, &i, "*. * at *:*: ") ||
3268                     looking_at(buf, &i, "--* (*:*): ") ||
3269                     /* Message notifications (same color as tells) */
3270                     looking_at(buf, &i, "* has left a message ") ||
3271                     looking_at(buf, &i, "* just sent you a message:\n") ||
3272                     /* Whispers and kibitzes */
3273                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3274                     looking_at(buf, &i, "* kibitzes: ") ||
3275                     /* Channel tells */
3276                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3277
3278                   if (tkind == 1 && strchr(star_match[0], ':')) {
3279                       /* Avoid "tells you:" spoofs in channels */
3280                      tkind = 3;
3281                   }
3282                   if (star_match[0][0] == NULLCHAR ||
3283                       strchr(star_match[0], ' ') ||
3284                       (tkind == 3 && strchr(star_match[1], ' '))) {
3285                     /* Reject bogus matches */
3286                     i = oldi;
3287                   } else {
3288                     if (appData.colorize) {
3289                       if (oldi > next_out) {
3290                         SendToPlayer(&buf[next_out], oldi - next_out);
3291                         next_out = oldi;
3292                       }
3293                       switch (tkind) {
3294                       case 1:
3295                         Colorize(ColorTell, FALSE);
3296                         curColor = ColorTell;
3297                         break;
3298                       case 2:
3299                         Colorize(ColorKibitz, FALSE);
3300                         curColor = ColorKibitz;
3301                         break;
3302                       case 3:
3303                         p = strrchr(star_match[1], '(');
3304                         if (p == NULL) {
3305                           p = star_match[1];
3306                         } else {
3307                           p++;
3308                         }
3309                         if (atoi(p) == 1) {
3310                           Colorize(ColorChannel1, FALSE);
3311                           curColor = ColorChannel1;
3312                         } else {
3313                           Colorize(ColorChannel, FALSE);
3314                           curColor = ColorChannel;
3315                         }
3316                         break;
3317                       case 5:
3318                         curColor = ColorNormal;
3319                         break;
3320                       }
3321                     }
3322                     if (started == STARTED_NONE && appData.autoComment &&
3323                         (gameMode == IcsObserving ||
3324                          gameMode == IcsPlayingWhite ||
3325                          gameMode == IcsPlayingBlack)) {
3326                       parse_pos = i - oldi;
3327                       memcpy(parse, &buf[oldi], parse_pos);
3328                       parse[parse_pos] = NULLCHAR;
3329                       started = STARTED_COMMENT;
3330                       savingComment = TRUE;
3331                     } else {
3332                       started = STARTED_CHATTER;
3333                       savingComment = FALSE;
3334                     }
3335                     loggedOn = TRUE;
3336                     continue;
3337                   }
3338                 }
3339
3340                 if (looking_at(buf, &i, "* s-shouts: ") ||
3341                     looking_at(buf, &i, "* c-shouts: ")) {
3342                     if (appData.colorize) {
3343                         if (oldi > next_out) {
3344                             SendToPlayer(&buf[next_out], oldi - next_out);
3345                             next_out = oldi;
3346                         }
3347                         Colorize(ColorSShout, FALSE);
3348                         curColor = ColorSShout;
3349                     }
3350                     loggedOn = TRUE;
3351                     started = STARTED_CHATTER;
3352                     continue;
3353                 }
3354
3355                 if (looking_at(buf, &i, "--->")) {
3356                     loggedOn = TRUE;
3357                     continue;
3358                 }
3359
3360                 if (looking_at(buf, &i, "* shouts: ") ||
3361                     looking_at(buf, &i, "--> ")) {
3362                     if (appData.colorize) {
3363                         if (oldi > next_out) {
3364                             SendToPlayer(&buf[next_out], oldi - next_out);
3365                             next_out = oldi;
3366                         }
3367                         Colorize(ColorShout, FALSE);
3368                         curColor = ColorShout;
3369                     }
3370                     loggedOn = TRUE;
3371                     started = STARTED_CHATTER;
3372                     continue;
3373                 }
3374
3375                 if (looking_at( buf, &i, "Challenge:")) {
3376                     if (appData.colorize) {
3377                         if (oldi > next_out) {
3378                             SendToPlayer(&buf[next_out], oldi - next_out);
3379                             next_out = oldi;
3380                         }
3381                         Colorize(ColorChallenge, FALSE);
3382                         curColor = ColorChallenge;
3383                     }
3384                     loggedOn = TRUE;
3385                     continue;
3386                 }
3387
3388                 if (looking_at(buf, &i, "* offers you") ||
3389                     looking_at(buf, &i, "* offers to be") ||
3390                     looking_at(buf, &i, "* would like to") ||
3391                     looking_at(buf, &i, "* requests to") ||
3392                     looking_at(buf, &i, "Your opponent offers") ||
3393                     looking_at(buf, &i, "Your opponent requests")) {
3394
3395                     if (appData.colorize) {
3396                         if (oldi > next_out) {
3397                             SendToPlayer(&buf[next_out], oldi - next_out);
3398                             next_out = oldi;
3399                         }
3400                         Colorize(ColorRequest, FALSE);
3401                         curColor = ColorRequest;
3402                     }
3403                     continue;
3404                 }
3405
3406                 if (looking_at(buf, &i, "* (*) seeking")) {
3407                     if (appData.colorize) {
3408                         if (oldi > next_out) {
3409                             SendToPlayer(&buf[next_out], oldi - next_out);
3410                             next_out = oldi;
3411                         }
3412                         Colorize(ColorSeek, FALSE);
3413                         curColor = ColorSeek;
3414                     }
3415                     continue;
3416             }
3417
3418           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3419
3420             if (looking_at(buf, &i, "\\   ")) {
3421                 if (prevColor != ColorNormal) {
3422                     if (oldi > next_out) {
3423                         SendToPlayer(&buf[next_out], oldi - next_out);
3424                         next_out = oldi;
3425                     }
3426                     Colorize(prevColor, TRUE);
3427                     curColor = prevColor;
3428                 }
3429                 if (savingComment) {
3430                     parse_pos = i - oldi;
3431                     memcpy(parse, &buf[oldi], parse_pos);
3432                     parse[parse_pos] = NULLCHAR;
3433                     started = STARTED_COMMENT;
3434                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3435                         chattingPartner = savingComment - 3; // kludge to remember the box
3436                 } else {
3437                     started = STARTED_CHATTER;
3438                 }
3439                 continue;
3440             }
3441
3442             if (looking_at(buf, &i, "Black Strength :") ||
3443                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3444                 looking_at(buf, &i, "<10>") ||
3445                 looking_at(buf, &i, "#@#")) {
3446                 /* Wrong board style */
3447                 loggedOn = TRUE;
3448                 SendToICS(ics_prefix);
3449                 SendToICS("set style 12\n");
3450                 SendToICS(ics_prefix);
3451                 SendToICS("refresh\n");
3452                 continue;
3453             }
3454
3455             if (looking_at(buf, &i, "login:")) {
3456               if (!have_sent_ICS_logon) {
3457                 if(ICSInitScript())
3458                   have_sent_ICS_logon = 1;
3459                 else // no init script was found
3460                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3461               } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3462                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3463               }
3464                 continue;
3465             }
3466
3467             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3468                 (looking_at(buf, &i, "\n<12> ") ||
3469                  looking_at(buf, &i, "<12> "))) {
3470                 loggedOn = TRUE;
3471                 if (oldi > next_out) {
3472                     SendToPlayer(&buf[next_out], oldi - next_out);
3473                 }
3474                 next_out = i;
3475                 started = STARTED_BOARD;
3476                 parse_pos = 0;
3477                 continue;
3478             }
3479
3480             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3481                 looking_at(buf, &i, "<b1> ")) {
3482                 if (oldi > next_out) {
3483                     SendToPlayer(&buf[next_out], oldi - next_out);
3484                 }
3485                 next_out = i;
3486                 started = STARTED_HOLDINGS;
3487                 parse_pos = 0;
3488                 continue;
3489             }
3490
3491             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3492                 loggedOn = TRUE;
3493                 /* Header for a move list -- first line */
3494
3495                 switch (ics_getting_history) {
3496                   case H_FALSE:
3497                     switch (gameMode) {
3498                       case IcsIdle:
3499                       case BeginningOfGame:
3500                         /* User typed "moves" or "oldmoves" while we
3501                            were idle.  Pretend we asked for these
3502                            moves and soak them up so user can step
3503                            through them and/or save them.
3504                            */
3505                         Reset(FALSE, TRUE);
3506                         gameMode = IcsObserving;
3507                         ModeHighlight();
3508                         ics_gamenum = -1;
3509                         ics_getting_history = H_GOT_UNREQ_HEADER;
3510                         break;
3511                       case EditGame: /*?*/
3512                       case EditPosition: /*?*/
3513                         /* Should above feature work in these modes too? */
3514                         /* For now it doesn't */
3515                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3516                         break;
3517                       default:
3518                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3519                         break;
3520                     }
3521                     break;
3522                   case H_REQUESTED:
3523                     /* Is this the right one? */
3524                     if (gameInfo.white && gameInfo.black &&
3525                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3526                         strcmp(gameInfo.black, star_match[2]) == 0) {
3527                         /* All is well */
3528                         ics_getting_history = H_GOT_REQ_HEADER;
3529                     }
3530                     break;
3531                   case H_GOT_REQ_HEADER:
3532                   case H_GOT_UNREQ_HEADER:
3533                   case H_GOT_UNWANTED_HEADER:
3534                   case H_GETTING_MOVES:
3535                     /* Should not happen */
3536                     DisplayError(_("Error gathering move list: two headers"), 0);
3537                     ics_getting_history = H_FALSE;
3538                     break;
3539                 }
3540
3541                 /* Save player ratings into gameInfo if needed */
3542                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3543                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3544                     (gameInfo.whiteRating == -1 ||
3545                      gameInfo.blackRating == -1)) {
3546
3547                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3548                     gameInfo.blackRating = string_to_rating(star_match[3]);
3549                     if (appData.debugMode)
3550                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3551                               gameInfo.whiteRating, gameInfo.blackRating);
3552                 }
3553                 continue;
3554             }
3555
3556             if (looking_at(buf, &i,
3557               "* * match, initial time: * minute*, increment: * second")) {
3558                 /* Header for a move list -- second line */
3559                 /* Initial board will follow if this is a wild game */
3560                 if (gameInfo.event != NULL) free(gameInfo.event);
3561                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3562                 gameInfo.event = StrSave(str);
3563                 /* [HGM] we switched variant. Translate boards if needed. */
3564                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3565                 continue;
3566             }
3567
3568             if (looking_at(buf, &i, "Move  ")) {
3569                 /* Beginning of a move list */
3570                 switch (ics_getting_history) {
3571                   case H_FALSE:
3572                     /* Normally should not happen */
3573                     /* Maybe user hit reset while we were parsing */
3574                     break;
3575                   case H_REQUESTED:
3576                     /* Happens if we are ignoring a move list that is not
3577                      * the one we just requested.  Common if the user
3578                      * tries to observe two games without turning off
3579                      * getMoveList */
3580                     break;
3581                   case H_GETTING_MOVES:
3582                     /* Should not happen */
3583                     DisplayError(_("Error gathering move list: nested"), 0);
3584                     ics_getting_history = H_FALSE;
3585                     break;
3586                   case H_GOT_REQ_HEADER:
3587                     ics_getting_history = H_GETTING_MOVES;
3588                     started = STARTED_MOVES;
3589                     parse_pos = 0;
3590                     if (oldi > next_out) {
3591                         SendToPlayer(&buf[next_out], oldi - next_out);
3592                     }
3593                     break;
3594                   case H_GOT_UNREQ_HEADER:
3595                     ics_getting_history = H_GETTING_MOVES;
3596                     started = STARTED_MOVES_NOHIDE;
3597                     parse_pos = 0;
3598                     break;
3599                   case H_GOT_UNWANTED_HEADER:
3600                     ics_getting_history = H_FALSE;
3601                     break;
3602                 }
3603                 continue;
3604             }
3605
3606             if (looking_at(buf, &i, "% ") ||
3607                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3608                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3609                 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3610                     soughtPending = FALSE;
3611                     seekGraphUp = TRUE;
3612                     DrawSeekGraph();
3613                 }
3614                 if(suppressKibitz) next_out = i;
3615                 savingComment = FALSE;
3616                 suppressKibitz = 0;
3617                 switch (started) {
3618                   case STARTED_MOVES:
3619                   case STARTED_MOVES_NOHIDE:
3620                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3621                     parse[parse_pos + i - oldi] = NULLCHAR;
3622                     ParseGameHistory(parse);
3623 #if ZIPPY
3624                     if (appData.zippyPlay && first.initDone) {
3625                         FeedMovesToProgram(&first, forwardMostMove);
3626                         if (gameMode == IcsPlayingWhite) {
3627                             if (WhiteOnMove(forwardMostMove)) {
3628                                 if (first.sendTime) {
3629                                   if (first.useColors) {
3630                                     SendToProgram("black\n", &first);
3631                                   }
3632                                   SendTimeRemaining(&first, TRUE);
3633                                 }
3634                                 if (first.useColors) {
3635                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3636                                 }
3637                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3638                                 first.maybeThinking = TRUE;
3639                             } else {
3640                                 if (first.usePlayother) {
3641                                   if (first.sendTime) {
3642                                     SendTimeRemaining(&first, TRUE);
3643                                   }
3644                                   SendToProgram("playother\n", &first);
3645                                   firstMove = FALSE;
3646                                 } else {
3647                                   firstMove = TRUE;
3648                                 }
3649                             }
3650                         } else if (gameMode == IcsPlayingBlack) {
3651                             if (!WhiteOnMove(forwardMostMove)) {
3652                                 if (first.sendTime) {
3653                                   if (first.useColors) {
3654                                     SendToProgram("white\n", &first);
3655                                   }
3656                                   SendTimeRemaining(&first, FALSE);
3657                                 }
3658                                 if (first.useColors) {
3659                                   SendToProgram("black\n", &first);
3660                                 }
3661                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3662                                 first.maybeThinking = TRUE;
3663                             } else {
3664                                 if (first.usePlayother) {
3665                                   if (first.sendTime) {
3666                                     SendTimeRemaining(&first, FALSE);
3667                                   }
3668                                   SendToProgram("playother\n", &first);
3669                                   firstMove = FALSE;
3670                                 } else {
3671                                   firstMove = TRUE;
3672                                 }
3673                             }
3674                         }
3675                     }
3676 #endif
3677                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3678                         /* Moves came from oldmoves or moves command
3679                            while we weren't doing anything else.
3680                            */
3681                         currentMove = forwardMostMove;
3682                         ClearHighlights();/*!!could figure this out*/
3683                         flipView = appData.flipView;
3684                         DrawPosition(TRUE, boards[currentMove]);
3685                         DisplayBothClocks();
3686                         snprintf(str, MSG_SIZ, "%s %s %s",
3687                                 gameInfo.white, _("vs."),  gameInfo.black);
3688                         DisplayTitle(str);
3689                         gameMode = IcsIdle;
3690                     } else {
3691                         /* Moves were history of an active game */
3692                         if (gameInfo.resultDetails != NULL) {
3693                             free(gameInfo.resultDetails);
3694                             gameInfo.resultDetails = NULL;
3695                         }
3696                     }
3697                     HistorySet(parseList, backwardMostMove,
3698                                forwardMostMove, currentMove-1);
3699                     DisplayMove(currentMove - 1);
3700                     if (started == STARTED_MOVES) next_out = i;
3701                     started = STARTED_NONE;
3702                     ics_getting_history = H_FALSE;
3703                     break;
3704
3705                   case STARTED_OBSERVE:
3706                     started = STARTED_NONE;
3707                     SendToICS(ics_prefix);
3708                     SendToICS("refresh\n");
3709                     break;
3710
3711                   default:
3712                     break;
3713                 }
3714                 if(bookHit) { // [HGM] book: simulate book reply
3715                     static char bookMove[MSG_SIZ]; // a bit generous?
3716
3717                     programStats.nodes = programStats.depth = programStats.time =
3718                     programStats.score = programStats.got_only_move = 0;
3719                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3720
3721                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3722                     strcat(bookMove, bookHit);
3723                     HandleMachineMove(bookMove, &first);
3724                 }
3725                 continue;
3726             }
3727
3728             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3729                  started == STARTED_HOLDINGS ||
3730                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3731                 /* Accumulate characters in move list or board */
3732                 parse[parse_pos++] = buf[i];
3733             }
3734
3735             /* Start of game messages.  Mostly we detect start of game
3736                when the first board image arrives.  On some versions
3737                of the ICS, though, we need to do a "refresh" after starting
3738                to observe in order to get the current board right away. */
3739             if (looking_at(buf, &i, "Adding game * to observation list")) {
3740                 started = STARTED_OBSERVE;
3741                 continue;
3742             }
3743
3744             /* Handle auto-observe */
3745             if (appData.autoObserve &&
3746                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3747                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3748                 char *player;
3749                 /* Choose the player that was highlighted, if any. */
3750                 if (star_match[0][0] == '\033' ||
3751                     star_match[1][0] != '\033') {
3752                     player = star_match[0];
3753                 } else {
3754                     player = star_match[2];
3755                 }
3756                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3757                         ics_prefix, StripHighlightAndTitle(player));
3758                 SendToICS(str);
3759
3760                 /* Save ratings from notify string */
3761                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3762                 player1Rating = string_to_rating(star_match[1]);
3763                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3764                 player2Rating = string_to_rating(star_match[3]);
3765
3766                 if (appData.debugMode)
3767                   fprintf(debugFP,
3768                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3769                           player1Name, player1Rating,
3770                           player2Name, player2Rating);
3771
3772                 continue;
3773             }
3774
3775             /* Deal with automatic examine mode after a game,
3776                and with IcsObserving -> IcsExamining transition */
3777             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3778                 looking_at(buf, &i, "has made you an examiner of game *")) {
3779
3780                 int gamenum = atoi(star_match[0]);
3781                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3782                     gamenum == ics_gamenum) {
3783                     /* We were already playing or observing this game;
3784                        no need to refetch history */
3785                     gameMode = IcsExamining;
3786                     if (pausing) {
3787                         pauseExamForwardMostMove = forwardMostMove;
3788                     } else if (currentMove < forwardMostMove) {
3789                         ForwardInner(forwardMostMove);
3790                     }
3791                 } else {
3792                     /* I don't think this case really can happen */
3793                     SendToICS(ics_prefix);
3794                     SendToICS("refresh\n");
3795                 }
3796                 continue;
3797             }
3798
3799             /* Error messages */
3800 //          if (ics_user_moved) {
3801             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3802                 if (looking_at(buf, &i, "Illegal move") ||
3803                     looking_at(buf, &i, "Not a legal move") ||
3804                     looking_at(buf, &i, "Your king is in check") ||
3805                     looking_at(buf, &i, "It isn't your turn") ||
3806                     looking_at(buf, &i, "It is not your move")) {
3807                     /* Illegal move */
3808                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3809                         currentMove = forwardMostMove-1;
3810                         DisplayMove(currentMove - 1); /* before DMError */
3811                         DrawPosition(FALSE, boards[currentMove]);
3812                         SwitchClocks(forwardMostMove-1); // [HGM] race
3813                         DisplayBothClocks();
3814                     }
3815                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3816                     ics_user_moved = 0;
3817                     continue;
3818                 }
3819             }
3820
3821             if (looking_at(buf, &i, "still have time") ||
3822                 looking_at(buf, &i, "not out of time") ||
3823                 looking_at(buf, &i, "either player is out of time") ||
3824                 looking_at(buf, &i, "has timeseal; checking")) {
3825                 /* We must have called his flag a little too soon */
3826                 whiteFlag = blackFlag = FALSE;
3827                 continue;
3828             }
3829
3830             if (looking_at(buf, &i, "added * seconds to") ||
3831                 looking_at(buf, &i, "seconds were added to")) {
3832                 /* Update the clocks */
3833                 SendToICS(ics_prefix);
3834                 SendToICS("refresh\n");
3835                 continue;
3836             }
3837
3838             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3839                 ics_clock_paused = TRUE;
3840                 StopClocks();
3841                 continue;
3842             }
3843
3844             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3845                 ics_clock_paused = FALSE;
3846                 StartClocks();
3847                 continue;
3848             }
3849
3850             /* Grab player ratings from the Creating: message.
3851                Note we have to check for the special case when
3852                the ICS inserts things like [white] or [black]. */
3853             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3854                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3855                 /* star_matches:
3856                    0    player 1 name (not necessarily white)
3857                    1    player 1 rating
3858                    2    empty, white, or black (IGNORED)
3859                    3    player 2 name (not necessarily black)
3860                    4    player 2 rating
3861
3862                    The names/ratings are sorted out when the game
3863                    actually starts (below).
3864                 */
3865                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3866                 player1Rating = string_to_rating(star_match[1]);
3867                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3868                 player2Rating = string_to_rating(star_match[4]);
3869
3870                 if (appData.debugMode)
3871                   fprintf(debugFP,
3872                           "Ratings from 'Creating:' %s %d, %s %d\n",
3873                           player1Name, player1Rating,
3874                           player2Name, player2Rating);
3875
3876                 continue;
3877             }
3878
3879             /* Improved generic start/end-of-game messages */
3880             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3881                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3882                 /* If tkind == 0: */
3883                 /* star_match[0] is the game number */
3884                 /*           [1] is the white player's name */
3885                 /*           [2] is the black player's name */
3886                 /* For end-of-game: */
3887                 /*           [3] is the reason for the game end */
3888                 /*           [4] is a PGN end game-token, preceded by " " */
3889                 /* For start-of-game: */
3890                 /*           [3] begins with "Creating" or "Continuing" */
3891                 /*           [4] is " *" or empty (don't care). */
3892                 int gamenum = atoi(star_match[0]);
3893                 char *whitename, *blackname, *why, *endtoken;
3894                 ChessMove endtype = EndOfFile;
3895
3896                 if (tkind == 0) {
3897                   whitename = star_match[1];
3898                   blackname = star_match[2];
3899                   why = star_match[3];
3900                   endtoken = star_match[4];
3901                 } else {
3902                   whitename = star_match[1];
3903                   blackname = star_match[3];
3904                   why = star_match[5];
3905                   endtoken = star_match[6];
3906                 }
3907
3908                 /* Game start messages */
3909                 if (strncmp(why, "Creating ", 9) == 0 ||
3910                     strncmp(why, "Continuing ", 11) == 0) {
3911                     gs_gamenum = gamenum;
3912                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3913                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
3914                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3915 #if ZIPPY
3916                     if (appData.zippyPlay) {
3917                         ZippyGameStart(whitename, blackname);
3918                     }
3919 #endif /*ZIPPY*/
3920                     partnerBoardValid = FALSE; // [HGM] bughouse
3921                     continue;
3922                 }
3923
3924                 /* Game end messages */
3925                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3926                     ics_gamenum != gamenum) {
3927                     continue;
3928                 }
3929                 while (endtoken[0] == ' ') endtoken++;
3930                 switch (endtoken[0]) {
3931                   case '*':
3932                   default:
3933                     endtype = GameUnfinished;
3934                     break;
3935                   case '0':
3936                     endtype = BlackWins;
3937                     break;
3938                   case '1':
3939                     if (endtoken[1] == '/')
3940                       endtype = GameIsDrawn;
3941                     else
3942                       endtype = WhiteWins;
3943                     break;
3944                 }
3945                 GameEnds(endtype, why, GE_ICS);
3946 #if ZIPPY
3947                 if (appData.zippyPlay && first.initDone) {
3948                     ZippyGameEnd(endtype, why);
3949                     if (first.pr == NoProc) {
3950                       /* Start the next process early so that we'll
3951                          be ready for the next challenge */
3952                       StartChessProgram(&first);
3953                     }
3954                     /* Send "new" early, in case this command takes
3955                        a long time to finish, so that we'll be ready
3956                        for the next challenge. */
3957                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3958                     Reset(TRUE, TRUE);
3959                 }
3960 #endif /*ZIPPY*/
3961                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3962                 continue;
3963             }
3964
3965             if (looking_at(buf, &i, "Removing game * from observation") ||
3966                 looking_at(buf, &i, "no longer observing game *") ||
3967                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3968                 if (gameMode == IcsObserving &&
3969                     atoi(star_match[0]) == ics_gamenum)
3970                   {
3971                       /* icsEngineAnalyze */
3972                       if (appData.icsEngineAnalyze) {
3973                             ExitAnalyzeMode();
3974                             ModeHighlight();
3975                       }
3976                       StopClocks();
3977                       gameMode = IcsIdle;
3978                       ics_gamenum = -1;
3979                       ics_user_moved = FALSE;
3980                   }
3981                 continue;
3982             }
3983
3984             if (looking_at(buf, &i, "no longer examining game *")) {
3985                 if (gameMode == IcsExamining &&
3986                     atoi(star_match[0]) == ics_gamenum)
3987                   {
3988                       gameMode = IcsIdle;
3989                       ics_gamenum = -1;
3990                       ics_user_moved = FALSE;
3991                   }
3992                 continue;
3993             }
3994
3995             /* Advance leftover_start past any newlines we find,
3996                so only partial lines can get reparsed */
3997             if (looking_at(buf, &i, "\n")) {
3998                 prevColor = curColor;
3999                 if (curColor != ColorNormal) {
4000                     if (oldi > next_out) {
4001                         SendToPlayer(&buf[next_out], oldi - next_out);
4002                         next_out = oldi;
4003                     }
4004                     Colorize(ColorNormal, FALSE);
4005                     curColor = ColorNormal;
4006                 }
4007                 if (started == STARTED_BOARD) {
4008                     started = STARTED_NONE;
4009                     parse[parse_pos] = NULLCHAR;
4010                     ParseBoard12(parse);
4011                     ics_user_moved = 0;
4012
4013                     /* Send premove here */
4014                     if (appData.premove) {
4015                       char str[MSG_SIZ];
4016                       if (currentMove == 0 &&
4017                           gameMode == IcsPlayingWhite &&
4018                           appData.premoveWhite) {
4019                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
4020                         if (appData.debugMode)
4021                           fprintf(debugFP, "Sending premove:\n");
4022                         SendToICS(str);
4023                       } else if (currentMove == 1 &&
4024                                  gameMode == IcsPlayingBlack &&
4025                                  appData.premoveBlack) {
4026                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
4027                         if (appData.debugMode)
4028                           fprintf(debugFP, "Sending premove:\n");
4029                         SendToICS(str);
4030                       } else if (gotPremove) {
4031                         gotPremove = 0;
4032                         ClearPremoveHighlights();
4033                         if (appData.debugMode)
4034                           fprintf(debugFP, "Sending premove:\n");
4035                           UserMoveEvent(premoveFromX, premoveFromY,
4036                                         premoveToX, premoveToY,
4037                                         premovePromoChar);
4038                       }
4039                     }
4040
4041                     /* Usually suppress following prompt */
4042                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4043                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4044                         if (looking_at(buf, &i, "*% ")) {
4045                             savingComment = FALSE;
4046                             suppressKibitz = 0;
4047                         }
4048                     }
4049                     next_out = i;
4050                 } else if (started == STARTED_HOLDINGS) {
4051                     int gamenum;
4052                     char new_piece[MSG_SIZ];
4053                     started = STARTED_NONE;
4054                     parse[parse_pos] = NULLCHAR;
4055                     if (appData.debugMode)
4056                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4057                                                         parse, currentMove);
4058                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4059                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4060                         if (gameInfo.variant == VariantNormal) {
4061                           /* [HGM] We seem to switch variant during a game!
4062                            * Presumably no holdings were displayed, so we have
4063                            * to move the position two files to the right to
4064                            * create room for them!
4065                            */
4066                           VariantClass newVariant;
4067                           switch(gameInfo.boardWidth) { // base guess on board width
4068                                 case 9:  newVariant = VariantShogi; break;
4069                                 case 10: newVariant = VariantGreat; break;
4070                                 default: newVariant = VariantCrazyhouse; break;
4071                           }
4072                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4073                           /* Get a move list just to see the header, which
4074                              will tell us whether this is really bug or zh */
4075                           if (ics_getting_history == H_FALSE) {
4076                             ics_getting_history = H_REQUESTED;
4077                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4078                             SendToICS(str);
4079                           }
4080                         }
4081                         new_piece[0] = NULLCHAR;
4082                         sscanf(parse, "game %d white [%s black [%s <- %s",
4083                                &gamenum, white_holding, black_holding,
4084                                new_piece);
4085                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4086                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4087                         /* [HGM] copy holdings to board holdings area */
4088                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4089                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4090                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4091 #if ZIPPY
4092                         if (appData.zippyPlay && first.initDone) {
4093                             ZippyHoldings(white_holding, black_holding,
4094                                           new_piece);
4095                         }
4096 #endif /*ZIPPY*/
4097                         if (tinyLayout || smallLayout) {
4098                             char wh[16], bh[16];
4099                             PackHolding(wh, white_holding);
4100                             PackHolding(bh, black_holding);
4101                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4102                                     gameInfo.white, gameInfo.black);
4103                         } else {
4104                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4105                                     gameInfo.white, white_holding, _("vs."),
4106                                     gameInfo.black, black_holding);
4107                         }
4108                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4109                         DrawPosition(FALSE, boards[currentMove]);
4110                         DisplayTitle(str);
4111                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4112                         sscanf(parse, "game %d white [%s black [%s <- %s",
4113                                &gamenum, white_holding, black_holding,
4114                                new_piece);
4115                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4116                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4117                         /* [HGM] copy holdings to partner-board holdings area */
4118                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4119                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4120                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4121                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4122                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4123                       }
4124                     }
4125                     /* Suppress following prompt */
4126                     if (looking_at(buf, &i, "*% ")) {
4127                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4128                         savingComment = FALSE;
4129                         suppressKibitz = 0;
4130                     }
4131                     next_out = i;
4132                 }
4133                 continue;
4134             }
4135
4136             i++;                /* skip unparsed character and loop back */
4137         }
4138
4139         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4140 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4141 //          SendToPlayer(&buf[next_out], i - next_out);
4142             started != STARTED_HOLDINGS && leftover_start > next_out) {
4143             SendToPlayer(&buf[next_out], leftover_start - next_out);
4144             next_out = i;
4145         }
4146
4147         leftover_len = buf_len - leftover_start;
4148         /* if buffer ends with something we couldn't parse,
4149            reparse it after appending the next read */
4150
4151     } else if (count == 0) {
4152         RemoveInputSource(isr);
4153         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4154     } else {
4155         DisplayFatalError(_("Error reading from ICS"), error, 1);
4156     }
4157 }
4158
4159
4160 /* Board style 12 looks like this:
4161
4162    <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
4163
4164  * The "<12> " is stripped before it gets to this routine.  The two
4165  * trailing 0's (flip state and clock ticking) are later addition, and
4166  * some chess servers may not have them, or may have only the first.
4167  * Additional trailing fields may be added in the future.
4168  */
4169
4170 #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"
4171
4172 #define RELATION_OBSERVING_PLAYED    0
4173 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4174 #define RELATION_PLAYING_MYMOVE      1
4175 #define RELATION_PLAYING_NOTMYMOVE  -1
4176 #define RELATION_EXAMINING           2
4177 #define RELATION_ISOLATED_BOARD     -3
4178 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4179
4180 void
4181 ParseBoard12 (char *string)
4182 {
4183 #if ZIPPY
4184     int i, takeback;
4185     char *bookHit = NULL; // [HGM] book
4186 #endif
4187     GameMode newGameMode;
4188     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
4189     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
4190     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4191     char to_play, board_chars[200];
4192     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4193     char black[32], white[32];
4194     Board board;
4195     int prevMove = currentMove;
4196     int ticking = 2;
4197     ChessMove moveType;
4198     int fromX, fromY, toX, toY;
4199     char promoChar;
4200     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4201     Boolean weird = FALSE, reqFlag = FALSE;
4202
4203     fromX = fromY = toX = toY = -1;
4204
4205     newGame = FALSE;
4206
4207     if (appData.debugMode)
4208       fprintf(debugFP, _("Parsing board: %s\n"), string);
4209
4210     move_str[0] = NULLCHAR;
4211     elapsed_time[0] = NULLCHAR;
4212     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4213         int  i = 0, j;
4214         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4215             if(string[i] == ' ') { ranks++; files = 0; }
4216             else files++;
4217             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4218             i++;
4219         }
4220         for(j = 0; j <i; j++) board_chars[j] = string[j];
4221         board_chars[i] = '\0';
4222         string += i + 1;
4223     }
4224     n = sscanf(string, PATTERN, &to_play, &double_push,
4225                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4226                &gamenum, white, black, &relation, &basetime, &increment,
4227                &white_stren, &black_stren, &white_time, &black_time,
4228                &moveNum, str, elapsed_time, move_str, &ics_flip,
4229                &ticking);
4230
4231     if (n < 21) {
4232         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4233         DisplayError(str, 0);
4234         return;
4235     }
4236
4237     /* Convert the move number to internal form */
4238     moveNum = (moveNum - 1) * 2;
4239     if (to_play == 'B') moveNum++;
4240     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4241       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4242                         0, 1);
4243       return;
4244     }
4245
4246     switch (relation) {
4247       case RELATION_OBSERVING_PLAYED:
4248       case RELATION_OBSERVING_STATIC:
4249         if (gamenum == -1) {
4250             /* Old ICC buglet */
4251             relation = RELATION_OBSERVING_STATIC;
4252         }
4253         newGameMode = IcsObserving;
4254         break;
4255       case RELATION_PLAYING_MYMOVE:
4256       case RELATION_PLAYING_NOTMYMOVE:
4257         newGameMode =
4258           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4259             IcsPlayingWhite : IcsPlayingBlack;
4260         soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4261         break;
4262       case RELATION_EXAMINING:
4263         newGameMode = IcsExamining;
4264         break;
4265       case RELATION_ISOLATED_BOARD:
4266       default:
4267         /* Just display this board.  If user was doing something else,
4268            we will forget about it until the next board comes. */
4269         newGameMode = IcsIdle;
4270         break;
4271       case RELATION_STARTING_POSITION:
4272         newGameMode = gameMode;
4273         break;
4274     }
4275
4276     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4277         gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4278          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4279       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4280       int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4281       static int lastBgGame = -1;
4282       char *toSqr;
4283       for (k = 0; k < ranks; k++) {
4284         for (j = 0; j < files; j++)
4285           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4286         if(gameInfo.holdingsWidth > 1) {
4287              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4288              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4289         }
4290       }
4291       CopyBoard(partnerBoard, board);
4292       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4293         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4294         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4295       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4296       if(toSqr = strchr(str, '-')) {
4297         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4298         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4299       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4300       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4301       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4302       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4303       if(twoBoards) {
4304           DisplayWhiteClock(white_time*fac, to_play == 'W');
4305           DisplayBlackClock(black_time*fac, to_play != 'W');
4306           activePartner = to_play;
4307           if(gamenum != lastBgGame) {
4308               char buf[MSG_SIZ];
4309               snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4310               DisplayTitle(buf);
4311           }
4312           lastBgGame = gamenum;
4313           activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4314                       partnerUp = 0; flipView = !flipView; } // [HGM] dual
4315       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4316                  (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4317       DisplayMessage(partnerStatus, "");
4318         partnerBoardValid = TRUE;
4319       return;
4320     }
4321
4322     if(appData.dualBoard && appData.bgObserve) {
4323         if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4324             SendToICS(ics_prefix), SendToICS("pobserve\n");
4325         else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4326             char buf[MSG_SIZ];
4327             snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4328             SendToICS(buf);
4329         }
4330     }
4331
4332     /* Modify behavior for initial board display on move listing
4333        of wild games.
4334        */
4335     switch (ics_getting_history) {
4336       case H_FALSE:
4337       case H_REQUESTED:
4338         break;
4339       case H_GOT_REQ_HEADER:
4340       case H_GOT_UNREQ_HEADER:
4341         /* This is the initial position of the current game */
4342         gamenum = ics_gamenum;
4343         moveNum = 0;            /* old ICS bug workaround */
4344         if (to_play == 'B') {
4345           startedFromSetupPosition = TRUE;
4346           blackPlaysFirst = TRUE;
4347           moveNum = 1;
4348           if (forwardMostMove == 0) forwardMostMove = 1;
4349           if (backwardMostMove == 0) backwardMostMove = 1;
4350           if (currentMove == 0) currentMove = 1;
4351         }
4352         newGameMode = gameMode;
4353         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4354         break;
4355       case H_GOT_UNWANTED_HEADER:
4356         /* This is an initial board that we don't want */
4357         return;
4358       case H_GETTING_MOVES:
4359         /* Should not happen */
4360         DisplayError(_("Error gathering move list: extra board"), 0);
4361         ics_getting_history = H_FALSE;
4362         return;
4363     }
4364
4365    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4366                                         move_str[1] == '@' && !gameInfo.holdingsWidth ||
4367                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4368      /* [HGM] We seem to have switched variant unexpectedly
4369       * Try to guess new variant from board size
4370       */
4371           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4372           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4373           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4374           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4375           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4376           if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4377           if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4378           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4379           /* Get a move list just to see the header, which
4380              will tell us whether this is really bug or zh */
4381           if (ics_getting_history == H_FALSE) {
4382             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4383             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4384             SendToICS(str);
4385           }
4386     }
4387
4388     /* Take action if this is the first board of a new game, or of a
4389        different game than is currently being displayed.  */
4390     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4391         relation == RELATION_ISOLATED_BOARD) {
4392
4393         /* Forget the old game and get the history (if any) of the new one */
4394         if (gameMode != BeginningOfGame) {
4395           Reset(TRUE, TRUE);
4396         }
4397         newGame = TRUE;
4398         if (appData.autoRaiseBoard) BoardToTop();
4399         prevMove = -3;
4400         if (gamenum == -1) {
4401             newGameMode = IcsIdle;
4402         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4403                    appData.getMoveList && !reqFlag) {
4404             /* Need to get game history */
4405             ics_getting_history = H_REQUESTED;
4406             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4407             SendToICS(str);
4408         }
4409
4410         /* Initially flip the board to have black on the bottom if playing
4411            black or if the ICS flip flag is set, but let the user change
4412            it with the Flip View button. */
4413         flipView = appData.autoFlipView ?
4414           (newGameMode == IcsPlayingBlack) || ics_flip :
4415           appData.flipView;
4416
4417         /* Done with values from previous mode; copy in new ones */
4418         gameMode = newGameMode;
4419         ModeHighlight();
4420         ics_gamenum = gamenum;
4421         if (gamenum == gs_gamenum) {
4422             int klen = strlen(gs_kind);
4423             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4424             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4425             gameInfo.event = StrSave(str);
4426         } else {
4427             gameInfo.event = StrSave("ICS game");
4428         }
4429         gameInfo.site = StrSave(appData.icsHost);
4430         gameInfo.date = PGNDate();
4431         gameInfo.round = StrSave("-");
4432         gameInfo.white = StrSave(white);
4433         gameInfo.black = StrSave(black);
4434         timeControl = basetime * 60 * 1000;
4435         timeControl_2 = 0;
4436         timeIncrement = increment * 1000;
4437         movesPerSession = 0;
4438         gameInfo.timeControl = TimeControlTagValue();
4439         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4440   if (appData.debugMode) {
4441     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4442     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4443     setbuf(debugFP, NULL);
4444   }
4445
4446         gameInfo.outOfBook = NULL;
4447
4448         /* Do we have the ratings? */
4449         if (strcmp(player1Name, white) == 0 &&
4450             strcmp(player2Name, black) == 0) {
4451             if (appData.debugMode)
4452               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4453                       player1Rating, player2Rating);
4454             gameInfo.whiteRating = player1Rating;
4455             gameInfo.blackRating = player2Rating;
4456         } else if (strcmp(player2Name, white) == 0 &&
4457                    strcmp(player1Name, black) == 0) {
4458             if (appData.debugMode)
4459               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4460                       player2Rating, player1Rating);
4461             gameInfo.whiteRating = player2Rating;
4462             gameInfo.blackRating = player1Rating;
4463         }
4464         player1Name[0] = player2Name[0] = NULLCHAR;
4465
4466         /* Silence shouts if requested */
4467         if (appData.quietPlay &&
4468             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4469             SendToICS(ics_prefix);
4470             SendToICS("set shout 0\n");
4471         }
4472     }
4473
4474     /* Deal with midgame name changes */
4475     if (!newGame) {
4476         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4477             if (gameInfo.white) free(gameInfo.white);
4478             gameInfo.white = StrSave(white);
4479         }
4480         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4481             if (gameInfo.black) free(gameInfo.black);
4482             gameInfo.black = StrSave(black);
4483         }
4484     }
4485
4486     /* Throw away game result if anything actually changes in examine mode */
4487     if (gameMode == IcsExamining && !newGame) {
4488         gameInfo.result = GameUnfinished;
4489         if (gameInfo.resultDetails != NULL) {
4490             free(gameInfo.resultDetails);
4491             gameInfo.resultDetails = NULL;
4492         }
4493     }
4494
4495     /* In pausing && IcsExamining mode, we ignore boards coming
4496        in if they are in a different variation than we are. */
4497     if (pauseExamInvalid) return;
4498     if (pausing && gameMode == IcsExamining) {
4499         if (moveNum <= pauseExamForwardMostMove) {
4500             pauseExamInvalid = TRUE;
4501             forwardMostMove = pauseExamForwardMostMove;
4502             return;
4503         }
4504     }
4505
4506   if (appData.debugMode) {
4507     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4508   }
4509     /* Parse the board */
4510     for (k = 0; k < ranks; k++) {
4511       for (j = 0; j < files; j++)
4512         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4513       if(gameInfo.holdingsWidth > 1) {
4514            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4515            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4516       }
4517     }
4518     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4519       board[5][BOARD_RGHT+1] = WhiteAngel;
4520       board[6][BOARD_RGHT+1] = WhiteMarshall;
4521       board[1][0] = BlackMarshall;
4522       board[2][0] = BlackAngel;
4523       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4524     }
4525     CopyBoard(boards[moveNum], board);
4526     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4527     if (moveNum == 0) {
4528         startedFromSetupPosition =
4529           !CompareBoards(board, initialPosition);
4530         if(startedFromSetupPosition)
4531             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4532     }
4533
4534     /* [HGM] Set castling rights. Take the outermost Rooks,
4535        to make it also work for FRC opening positions. Note that board12
4536        is really defective for later FRC positions, as it has no way to
4537        indicate which Rook can castle if they are on the same side of King.
4538        For the initial position we grant rights to the outermost Rooks,
4539        and remember thos rights, and we then copy them on positions
4540        later in an FRC game. This means WB might not recognize castlings with
4541        Rooks that have moved back to their original position as illegal,
4542        but in ICS mode that is not its job anyway.
4543     */
4544     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4545     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4546
4547         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4548             if(board[0][i] == WhiteRook) j = i;
4549         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4550         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4551             if(board[0][i] == WhiteRook) j = i;
4552         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4553         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4554             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4555         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4556         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4557             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4558         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4559
4560         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4561         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4562         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4563             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4564         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4565             if(board[BOARD_HEIGHT-1][k] == bKing)
4566                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4567         if(gameInfo.variant == VariantTwoKings) {
4568             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4569             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4570             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4571         }
4572     } else { int r;
4573         r = boards[moveNum][CASTLING][0] = initialRights[0];
4574         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4575         r = boards[moveNum][CASTLING][1] = initialRights[1];
4576         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4577         r = boards[moveNum][CASTLING][3] = initialRights[3];
4578         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4579         r = boards[moveNum][CASTLING][4] = initialRights[4];
4580         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4581         /* wildcastle kludge: always assume King has rights */
4582         r = boards[moveNum][CASTLING][2] = initialRights[2];
4583         r = boards[moveNum][CASTLING][5] = initialRights[5];
4584     }
4585     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4586     boards[moveNum][EP_STATUS] = EP_NONE;
4587     if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4588     if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4589     if(double_push !=  -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4590
4591
4592     if (ics_getting_history == H_GOT_REQ_HEADER ||
4593         ics_getting_history == H_GOT_UNREQ_HEADER) {
4594         /* This was an initial position from a move list, not
4595            the current position */
4596         return;
4597     }
4598
4599     /* Update currentMove and known move number limits */
4600     newMove = newGame || moveNum > forwardMostMove;
4601
4602     if (newGame) {
4603         forwardMostMove = backwardMostMove = currentMove = moveNum;
4604         if (gameMode == IcsExamining && moveNum == 0) {
4605           /* Workaround for ICS limitation: we are not told the wild
4606              type when starting to examine a game.  But if we ask for
4607              the move list, the move list header will tell us */
4608             ics_getting_history = H_REQUESTED;
4609             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4610             SendToICS(str);
4611         }
4612     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4613                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4614 #if ZIPPY
4615         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4616         /* [HGM] applied this also to an engine that is silently watching        */
4617         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4618             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4619             gameInfo.variant == currentlyInitializedVariant) {
4620           takeback = forwardMostMove - moveNum;
4621           for (i = 0; i < takeback; i++) {
4622             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4623             SendToProgram("undo\n", &first);
4624           }
4625         }
4626 #endif
4627
4628         forwardMostMove = moveNum;
4629         if (!pausing || currentMove > forwardMostMove)
4630           currentMove = forwardMostMove;
4631     } else {
4632         /* New part of history that is not contiguous with old part */
4633         if (pausing && gameMode == IcsExamining) {
4634             pauseExamInvalid = TRUE;
4635             forwardMostMove = pauseExamForwardMostMove;
4636             return;
4637         }
4638         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4639 #if ZIPPY
4640             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4641                 // [HGM] when we will receive the move list we now request, it will be
4642                 // fed to the engine from the first move on. So if the engine is not
4643                 // in the initial position now, bring it there.
4644                 InitChessProgram(&first, 0);
4645             }
4646 #endif
4647             ics_getting_history = H_REQUESTED;
4648             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4649             SendToICS(str);
4650         }
4651         forwardMostMove = backwardMostMove = currentMove = moveNum;
4652     }
4653
4654     /* Update the clocks */
4655     if (strchr(elapsed_time, '.')) {
4656       /* Time is in ms */
4657       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4658       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4659     } else {
4660       /* Time is in seconds */
4661       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4662       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4663     }
4664
4665
4666 #if ZIPPY
4667     if (appData.zippyPlay && newGame &&
4668         gameMode != IcsObserving && gameMode != IcsIdle &&
4669         gameMode != IcsExamining)
4670       ZippyFirstBoard(moveNum, basetime, increment);
4671 #endif
4672
4673     /* Put the move on the move list, first converting
4674        to canonical algebraic form. */
4675     if (moveNum > 0) {
4676   if (appData.debugMode) {
4677     if (appData.debugMode) { int f = forwardMostMove;
4678         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4679                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4680                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4681     }
4682     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4683     fprintf(debugFP, "moveNum = %d\n", moveNum);
4684     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4685     setbuf(debugFP, NULL);
4686   }
4687         if (moveNum <= backwardMostMove) {
4688             /* We don't know what the board looked like before
4689                this move.  Punt. */
4690           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4691             strcat(parseList[moveNum - 1], " ");
4692             strcat(parseList[moveNum - 1], elapsed_time);
4693             moveList[moveNum - 1][0] = NULLCHAR;
4694         } else if (strcmp(move_str, "none") == 0) {
4695             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4696             /* Again, we don't know what the board looked like;
4697                this is really the start of the game. */
4698             parseList[moveNum - 1][0] = NULLCHAR;
4699             moveList[moveNum - 1][0] = NULLCHAR;
4700             backwardMostMove = moveNum;
4701             startedFromSetupPosition = TRUE;
4702             fromX = fromY = toX = toY = -1;
4703         } else {
4704           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4705           //                 So we parse the long-algebraic move string in stead of the SAN move
4706           int valid; char buf[MSG_SIZ], *prom;
4707
4708           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4709                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4710           // str looks something like "Q/a1-a2"; kill the slash
4711           if(str[1] == '/')
4712             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4713           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4714           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4715                 strcat(buf, prom); // long move lacks promo specification!
4716           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4717                 if(appData.debugMode)
4718                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4719                 safeStrCpy(move_str, buf, MSG_SIZ);
4720           }
4721           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4722                                 &fromX, &fromY, &toX, &toY, &promoChar)
4723                || ParseOneMove(buf, moveNum - 1, &moveType,
4724                                 &fromX, &fromY, &toX, &toY, &promoChar);
4725           // end of long SAN patch
4726           if (valid) {
4727             (void) CoordsToAlgebraic(boards[moveNum - 1],
4728                                      PosFlags(moveNum - 1),
4729                                      fromY, fromX, toY, toX, promoChar,
4730                                      parseList[moveNum-1]);
4731             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4732               case MT_NONE:
4733               case MT_STALEMATE:
4734               default:
4735                 break;
4736               case MT_CHECK:
4737                 if(gameInfo.variant != VariantShogi)
4738                     strcat(parseList[moveNum - 1], "+");
4739                 break;
4740               case MT_CHECKMATE:
4741               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4742                 strcat(parseList[moveNum - 1], "#");
4743                 break;
4744             }
4745             strcat(parseList[moveNum - 1], " ");
4746             strcat(parseList[moveNum - 1], elapsed_time);
4747             /* currentMoveString is set as a side-effect of ParseOneMove */
4748             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4749             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4750             strcat(moveList[moveNum - 1], "\n");
4751
4752             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4753                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4754               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4755                 ChessSquare old, new = boards[moveNum][k][j];
4756                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4757                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4758                   if(old == new) continue;
4759                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4760                   else if(new == WhiteWazir || new == BlackWazir) {
4761                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4762                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4763                       else boards[moveNum][k][j] = old; // preserve type of Gold
4764                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4765                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4766               }
4767           } else {
4768             /* Move from ICS was illegal!?  Punt. */
4769             if (appData.debugMode) {
4770               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4771               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4772             }
4773             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4774             strcat(parseList[moveNum - 1], " ");
4775             strcat(parseList[moveNum - 1], elapsed_time);
4776             moveList[moveNum - 1][0] = NULLCHAR;
4777             fromX = fromY = toX = toY = -1;
4778           }
4779         }
4780   if (appData.debugMode) {
4781     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4782     setbuf(debugFP, NULL);
4783   }
4784
4785 #if ZIPPY
4786         /* Send move to chess program (BEFORE animating it). */
4787         if (appData.zippyPlay && !newGame && newMove &&
4788            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4789
4790             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4791                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4792                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4793                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4794                             move_str);
4795                     DisplayError(str, 0);
4796                 } else {
4797                     if (first.sendTime) {
4798                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4799                     }
4800                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4801                     if (firstMove && !bookHit) {
4802                         firstMove = FALSE;
4803                         if (first.useColors) {
4804                           SendToProgram(gameMode == IcsPlayingWhite ?
4805                                         "white\ngo\n" :
4806                                         "black\ngo\n", &first);
4807                         } else {
4808                           SendToProgram("go\n", &first);
4809                         }
4810                         first.maybeThinking = TRUE;
4811                     }
4812                 }
4813             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4814               if (moveList[moveNum - 1][0] == NULLCHAR) {
4815                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4816                 DisplayError(str, 0);
4817               } else {
4818                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4819                 SendMoveToProgram(moveNum - 1, &first);
4820               }
4821             }
4822         }
4823 #endif
4824     }
4825
4826     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4827         /* If move comes from a remote source, animate it.  If it
4828            isn't remote, it will have already been animated. */
4829         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4830             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4831         }
4832         if (!pausing && appData.highlightLastMove) {
4833             SetHighlights(fromX, fromY, toX, toY);
4834         }
4835     }
4836
4837     /* Start the clocks */
4838     whiteFlag = blackFlag = FALSE;
4839     appData.clockMode = !(basetime == 0 && increment == 0);
4840     if (ticking == 0) {
4841       ics_clock_paused = TRUE;
4842       StopClocks();
4843     } else if (ticking == 1) {
4844       ics_clock_paused = FALSE;
4845     }
4846     if (gameMode == IcsIdle ||
4847         relation == RELATION_OBSERVING_STATIC ||
4848         relation == RELATION_EXAMINING ||
4849         ics_clock_paused)
4850       DisplayBothClocks();
4851     else
4852       StartClocks();
4853
4854     /* Display opponents and material strengths */
4855     if (gameInfo.variant != VariantBughouse &&
4856         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4857         if (tinyLayout || smallLayout) {
4858             if(gameInfo.variant == VariantNormal)
4859               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4860                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4861                     basetime, increment);
4862             else
4863               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4864                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4865                     basetime, increment, (int) gameInfo.variant);
4866         } else {
4867             if(gameInfo.variant == VariantNormal)
4868               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4869                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4870                     basetime, increment);
4871             else
4872               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
4873                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4874                     basetime, increment, VariantName(gameInfo.variant));
4875         }
4876         DisplayTitle(str);
4877   if (appData.debugMode) {
4878     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4879   }
4880     }
4881
4882
4883     /* Display the board */
4884     if (!pausing && !appData.noGUI) {
4885
4886       if (appData.premove)
4887           if (!gotPremove ||
4888              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4889              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4890               ClearPremoveHighlights();
4891
4892       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4893         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4894       DrawPosition(j, boards[currentMove]);
4895
4896       DisplayMove(moveNum - 1);
4897       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4898             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4899               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4900         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4901       }
4902     }
4903
4904     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4905 #if ZIPPY
4906     if(bookHit) { // [HGM] book: simulate book reply
4907         static char bookMove[MSG_SIZ]; // a bit generous?
4908
4909         programStats.nodes = programStats.depth = programStats.time =
4910         programStats.score = programStats.got_only_move = 0;
4911         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4912
4913         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4914         strcat(bookMove, bookHit);
4915         HandleMachineMove(bookMove, &first);
4916     }
4917 #endif
4918 }
4919
4920 void
4921 GetMoveListEvent ()
4922 {
4923     char buf[MSG_SIZ];
4924     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4925         ics_getting_history = H_REQUESTED;
4926         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4927         SendToICS(buf);
4928     }
4929 }
4930
4931 void
4932 SendToBoth (char *msg)
4933 {   // to make it easy to keep two engines in step in dual analysis
4934     SendToProgram(msg, &first);
4935     if(second.analyzing) SendToProgram(msg, &second);
4936 }
4937
4938 void
4939 AnalysisPeriodicEvent (int force)
4940 {
4941     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4942          && !force) || !appData.periodicUpdates)
4943       return;
4944
4945     /* Send . command to Crafty to collect stats */
4946     SendToBoth(".\n");
4947
4948     /* Don't send another until we get a response (this makes
4949        us stop sending to old Crafty's which don't understand
4950        the "." command (sending illegal cmds resets node count & time,
4951        which looks bad)) */
4952     programStats.ok_to_send = 0;
4953 }
4954
4955 void
4956 ics_update_width (int new_width)
4957 {
4958         ics_printf("set width %d\n", new_width);
4959 }
4960
4961 void
4962 SendMoveToProgram (int moveNum, ChessProgramState *cps)
4963 {
4964     char buf[MSG_SIZ];
4965
4966     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
4967         // null move in variant where engine does not understand it (for analysis purposes)
4968         SendBoard(cps, moveNum + 1); // send position after move in stead.
4969         return;
4970     }
4971     if (cps->useUsermove) {
4972       SendToProgram("usermove ", cps);
4973     }
4974     if (cps->useSAN) {
4975       char *space;
4976       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4977         int len = space - parseList[moveNum];
4978         memcpy(buf, parseList[moveNum], len);
4979         buf[len++] = '\n';
4980         buf[len] = NULLCHAR;
4981       } else {
4982         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4983       }
4984       SendToProgram(buf, cps);
4985     } else {
4986       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4987         AlphaRank(moveList[moveNum], 4);
4988         SendToProgram(moveList[moveNum], cps);
4989         AlphaRank(moveList[moveNum], 4); // and back
4990       } else
4991       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4992        * the engine. It would be nice to have a better way to identify castle
4993        * moves here. */
4994       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4995                                                                          && cps->useOOCastle) {
4996         int fromX = moveList[moveNum][0] - AAA;
4997         int fromY = moveList[moveNum][1] - ONE;
4998         int toX = moveList[moveNum][2] - AAA;
4999         int toY = moveList[moveNum][3] - ONE;
5000         if((boards[moveNum][fromY][fromX] == WhiteKing
5001             && boards[moveNum][toY][toX] == WhiteRook)
5002            || (boards[moveNum][fromY][fromX] == BlackKing
5003                && boards[moveNum][toY][toX] == BlackRook)) {
5004           if(toX > fromX) SendToProgram("O-O\n", cps);
5005           else SendToProgram("O-O-O\n", cps);
5006         }
5007         else SendToProgram(moveList[moveNum], cps);
5008       } else
5009       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5010         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5011           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5012           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5013                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5014         } else
5015           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5016                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5017         SendToProgram(buf, cps);
5018       }
5019       else SendToProgram(moveList[moveNum], cps);
5020       /* End of additions by Tord */
5021     }
5022
5023     /* [HGM] setting up the opening has brought engine in force mode! */
5024     /*       Send 'go' if we are in a mode where machine should play. */
5025     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5026         (gameMode == TwoMachinesPlay   ||
5027 #if ZIPPY
5028          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
5029 #endif
5030          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5031         SendToProgram("go\n", cps);
5032   if (appData.debugMode) {
5033     fprintf(debugFP, "(extra)\n");
5034   }
5035     }
5036     setboardSpoiledMachineBlack = 0;
5037 }
5038
5039 void
5040 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5041 {
5042     char user_move[MSG_SIZ];
5043     char suffix[4];
5044
5045     if(gameInfo.variant == VariantSChess && promoChar) {
5046         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5047         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5048     } else suffix[0] = NULLCHAR;
5049
5050     switch (moveType) {
5051       default:
5052         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5053                 (int)moveType, fromX, fromY, toX, toY);
5054         DisplayError(user_move + strlen("say "), 0);
5055         break;
5056       case WhiteKingSideCastle:
5057       case BlackKingSideCastle:
5058       case WhiteQueenSideCastleWild:
5059       case BlackQueenSideCastleWild:
5060       /* PUSH Fabien */
5061       case WhiteHSideCastleFR:
5062       case BlackHSideCastleFR:
5063       /* POP Fabien */
5064         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5065         break;
5066       case WhiteQueenSideCastle:
5067       case BlackQueenSideCastle:
5068       case WhiteKingSideCastleWild:
5069       case BlackKingSideCastleWild:
5070       /* PUSH Fabien */
5071       case WhiteASideCastleFR:
5072       case BlackASideCastleFR:
5073       /* POP Fabien */
5074         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5075         break;
5076       case WhiteNonPromotion:
5077       case BlackNonPromotion:
5078         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5079         break;
5080       case WhitePromotion:
5081       case BlackPromotion:
5082         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
5083           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5084                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5085                 PieceToChar(WhiteFerz));
5086         else if(gameInfo.variant == VariantGreat)
5087           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5088                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5089                 PieceToChar(WhiteMan));
5090         else
5091           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5092                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5093                 promoChar);
5094         break;
5095       case WhiteDrop:
5096       case BlackDrop:
5097       drop:
5098         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5099                  ToUpper(PieceToChar((ChessSquare) fromX)),
5100                  AAA + toX, ONE + toY);
5101         break;
5102       case IllegalMove:  /* could be a variant we don't quite understand */
5103         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5104       case NormalMove:
5105       case WhiteCapturesEnPassant:
5106       case BlackCapturesEnPassant:
5107         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5108                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5109         break;
5110     }
5111     SendToICS(user_move);
5112     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5113         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5114 }
5115
5116 void
5117 UploadGameEvent ()
5118 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5119     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5120     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5121     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5122       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5123       return;
5124     }
5125     if(gameMode != IcsExamining) { // is this ever not the case?
5126         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5127
5128         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5129           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5130         } else { // on FICS we must first go to general examine mode
5131           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5132         }
5133         if(gameInfo.variant != VariantNormal) {
5134             // try figure out wild number, as xboard names are not always valid on ICS
5135             for(i=1; i<=36; i++) {
5136               snprintf(buf, MSG_SIZ, "wild/%d", i);
5137                 if(StringToVariant(buf) == gameInfo.variant) break;
5138             }
5139             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5140             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5141             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5142         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5143         SendToICS(ics_prefix);
5144         SendToICS(buf);
5145         if(startedFromSetupPosition || backwardMostMove != 0) {
5146           fen = PositionToFEN(backwardMostMove, NULL);
5147           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5148             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5149             SendToICS(buf);
5150           } else { // FICS: everything has to set by separate bsetup commands
5151             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5152             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5153             SendToICS(buf);
5154             if(!WhiteOnMove(backwardMostMove)) {
5155                 SendToICS("bsetup tomove black\n");
5156             }
5157             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5158             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5159             SendToICS(buf);
5160             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5161             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5162             SendToICS(buf);
5163             i = boards[backwardMostMove][EP_STATUS];
5164             if(i >= 0) { // set e.p.
5165               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5166                 SendToICS(buf);
5167             }
5168             bsetup++;
5169           }
5170         }
5171       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5172             SendToICS("bsetup done\n"); // switch to normal examining.
5173     }
5174     for(i = backwardMostMove; i<last; i++) {
5175         char buf[20];
5176         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5177         if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5178             int len = strlen(moveList[i]);
5179             snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5180             if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5181         }
5182         SendToICS(buf);
5183     }
5184     SendToICS(ics_prefix);
5185     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5186 }
5187
5188 void
5189 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5190 {
5191     if (rf == DROP_RANK) {
5192       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5193       sprintf(move, "%c@%c%c\n",
5194                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5195     } else {
5196         if (promoChar == 'x' || promoChar == NULLCHAR) {
5197           sprintf(move, "%c%c%c%c\n",
5198                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5199         } else {
5200             sprintf(move, "%c%c%c%c%c\n",
5201                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5202         }
5203     }
5204 }
5205
5206 void
5207 ProcessICSInitScript (FILE *f)
5208 {
5209     char buf[MSG_SIZ];
5210
5211     while (fgets(buf, MSG_SIZ, f)) {
5212         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5213     }
5214
5215     fclose(f);
5216 }
5217
5218
5219 static int lastX, lastY, selectFlag, dragging;
5220
5221 void
5222 Sweep (int step)
5223 {
5224     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5225     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5226     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5227     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5228     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5229     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5230     do {
5231         promoSweep -= step;
5232         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5233         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5234         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5235         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5236         if(!step) step = -1;
5237     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5238             appData.testLegality && (promoSweep == king ||
5239             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5240     if(toX >= 0) {
5241         int victim = boards[currentMove][toY][toX];
5242         boards[currentMove][toY][toX] = promoSweep;
5243         DrawPosition(FALSE, boards[currentMove]);
5244         boards[currentMove][toY][toX] = victim;
5245     } else
5246     ChangeDragPiece(promoSweep);
5247 }
5248
5249 int
5250 PromoScroll (int x, int y)
5251 {
5252   int step = 0;
5253
5254   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5255   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5256   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5257   if(!step) return FALSE;
5258   lastX = x; lastY = y;
5259   if((promoSweep < BlackPawn) == flipView) step = -step;
5260   if(step > 0) selectFlag = 1;
5261   if(!selectFlag) Sweep(step);
5262   return FALSE;
5263 }
5264
5265 void
5266 NextPiece (int step)
5267 {
5268     ChessSquare piece = boards[currentMove][toY][toX];
5269     do {
5270         pieceSweep -= step;
5271         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5272         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5273         if(!step) step = -1;
5274     } while(PieceToChar(pieceSweep) == '.');
5275     boards[currentMove][toY][toX] = pieceSweep;
5276     DrawPosition(FALSE, boards[currentMove]);
5277     boards[currentMove][toY][toX] = piece;
5278 }
5279 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5280 void
5281 AlphaRank (char *move, int n)
5282 {
5283 //    char *p = move, c; int x, y;
5284
5285     if (appData.debugMode) {
5286         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5287     }
5288
5289     if(move[1]=='*' &&
5290        move[2]>='0' && move[2]<='9' &&
5291        move[3]>='a' && move[3]<='x'    ) {
5292         move[1] = '@';
5293         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5294         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5295     } else
5296     if(move[0]>='0' && move[0]<='9' &&
5297        move[1]>='a' && move[1]<='x' &&
5298        move[2]>='0' && move[2]<='9' &&
5299        move[3]>='a' && move[3]<='x'    ) {
5300         /* input move, Shogi -> normal */
5301         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5302         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5303         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5304         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5305     } else
5306     if(move[1]=='@' &&
5307        move[3]>='0' && move[3]<='9' &&
5308        move[2]>='a' && move[2]<='x'    ) {
5309         move[1] = '*';
5310         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5311         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5312     } else
5313     if(
5314        move[0]>='a' && move[0]<='x' &&
5315        move[3]>='0' && move[3]<='9' &&
5316        move[2]>='a' && move[2]<='x'    ) {
5317          /* output move, normal -> Shogi */
5318         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5319         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5320         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5321         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5322         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5323     }
5324     if (appData.debugMode) {
5325         fprintf(debugFP, "   out = '%s'\n", move);
5326     }
5327 }
5328
5329 char yy_textstr[8000];
5330
5331 /* Parser for moves from gnuchess, ICS, or user typein box */
5332 Boolean
5333 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5334 {
5335     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5336
5337     switch (*moveType) {
5338       case WhitePromotion:
5339       case BlackPromotion:
5340       case WhiteNonPromotion:
5341       case BlackNonPromotion:
5342       case NormalMove:
5343       case WhiteCapturesEnPassant:
5344       case BlackCapturesEnPassant:
5345       case WhiteKingSideCastle:
5346       case WhiteQueenSideCastle:
5347       case BlackKingSideCastle:
5348       case BlackQueenSideCastle:
5349       case WhiteKingSideCastleWild:
5350       case WhiteQueenSideCastleWild:
5351       case BlackKingSideCastleWild:
5352       case BlackQueenSideCastleWild:
5353       /* Code added by Tord: */
5354       case WhiteHSideCastleFR:
5355       case WhiteASideCastleFR:
5356       case BlackHSideCastleFR:
5357       case BlackASideCastleFR:
5358       /* End of code added by Tord */
5359       case IllegalMove:         /* bug or odd chess variant */
5360         *fromX = currentMoveString[0] - AAA;
5361         *fromY = currentMoveString[1] - ONE;
5362         *toX = currentMoveString[2] - AAA;
5363         *toY = currentMoveString[3] - ONE;
5364         *promoChar = currentMoveString[4];
5365         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5366             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5367     if (appData.debugMode) {
5368         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5369     }
5370             *fromX = *fromY = *toX = *toY = 0;
5371             return FALSE;
5372         }
5373         if (appData.testLegality) {
5374           return (*moveType != IllegalMove);
5375         } else {
5376           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5377                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5378         }
5379
5380       case WhiteDrop:
5381       case BlackDrop:
5382         *fromX = *moveType == WhiteDrop ?
5383           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5384           (int) CharToPiece(ToLower(currentMoveString[0]));
5385         *fromY = DROP_RANK;
5386         *toX = currentMoveString[2] - AAA;
5387         *toY = currentMoveString[3] - ONE;
5388         *promoChar = NULLCHAR;
5389         return TRUE;
5390
5391       case AmbiguousMove:
5392       case ImpossibleMove:
5393       case EndOfFile:
5394       case ElapsedTime:
5395       case Comment:
5396       case PGNTag:
5397       case NAG:
5398       case WhiteWins:
5399       case BlackWins:
5400       case GameIsDrawn:
5401       default:
5402     if (appData.debugMode) {
5403         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5404     }
5405         /* bug? */
5406         *fromX = *fromY = *toX = *toY = 0;
5407         *promoChar = NULLCHAR;
5408         return FALSE;
5409     }
5410 }
5411
5412 Boolean pushed = FALSE;
5413 char *lastParseAttempt;
5414
5415 void
5416 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5417 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5418   int fromX, fromY, toX, toY; char promoChar;
5419   ChessMove moveType;
5420   Boolean valid;
5421   int nr = 0;
5422
5423   lastParseAttempt = pv; if(!*pv) return;    // turns out we crash when we parse an empty PV
5424   if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5425     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5426     pushed = TRUE;
5427   }
5428   endPV = forwardMostMove;
5429   do {
5430     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5431     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5432     lastParseAttempt = pv;
5433     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5434     if(!valid && nr == 0 &&
5435        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5436         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5437         // Hande case where played move is different from leading PV move
5438         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5439         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5440         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5441         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5442           endPV += 2; // if position different, keep this
5443           moveList[endPV-1][0] = fromX + AAA;
5444           moveList[endPV-1][1] = fromY + ONE;
5445           moveList[endPV-1][2] = toX + AAA;
5446           moveList[endPV-1][3] = toY + ONE;
5447           parseList[endPV-1][0] = NULLCHAR;
5448           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5449         }
5450       }
5451     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5452     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5453     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5454     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5455         valid++; // allow comments in PV
5456         continue;
5457     }
5458     nr++;
5459     if(endPV+1 > framePtr) break; // no space, truncate
5460     if(!valid) break;
5461     endPV++;
5462     CopyBoard(boards[endPV], boards[endPV-1]);
5463     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5464     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5465     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5466     CoordsToAlgebraic(boards[endPV - 1],
5467                              PosFlags(endPV - 1),
5468                              fromY, fromX, toY, toX, promoChar,
5469                              parseList[endPV - 1]);
5470   } while(valid);
5471   if(atEnd == 2) return; // used hidden, for PV conversion
5472   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5473   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5474   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5475                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5476   DrawPosition(TRUE, boards[currentMove]);
5477 }
5478
5479 int
5480 MultiPV (ChessProgramState *cps)
5481 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5482         int i;
5483         for(i=0; i<cps->nrOptions; i++)
5484             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5485                 return i;
5486         return -1;
5487 }
5488
5489 Boolean
5490 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5491 {
5492         int startPV, multi, lineStart, origIndex = index;
5493         char *p, buf2[MSG_SIZ];
5494         ChessProgramState *cps = (pane ? &second : &first);
5495
5496         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5497         lastX = x; lastY = y;
5498         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5499         lineStart = startPV = index;
5500         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5501         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5502         index = startPV;
5503         do{ while(buf[index] && buf[index] != '\n') index++;
5504         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5505         buf[index] = 0;
5506         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(cps)) >= 0) {
5507                 int n = cps->option[multi].value;
5508                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5509                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5510                 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5511                 cps->option[multi].value = n;
5512                 *start = *end = 0;
5513                 return FALSE;
5514         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5515                 ExcludeClick(origIndex - lineStart);
5516                 return FALSE;
5517         }
5518         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5519         *start = startPV; *end = index-1;
5520         return TRUE;
5521 }
5522
5523 char *
5524 PvToSAN (char *pv)
5525 {
5526         static char buf[10*MSG_SIZ];
5527         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5528         *buf = NULLCHAR;
5529         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5530         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5531         for(i = forwardMostMove; i<endPV; i++){
5532             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5533             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5534             k += strlen(buf+k);
5535         }
5536         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5537         if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5538         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5539         endPV = savedEnd;
5540         return buf;
5541 }
5542
5543 Boolean
5544 LoadPV (int x, int y)
5545 { // called on right mouse click to load PV
5546   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5547   lastX = x; lastY = y;
5548   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5549   return TRUE;
5550 }
5551
5552 void
5553 UnLoadPV ()
5554 {
5555   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5556   if(endPV < 0) return;
5557   if(appData.autoCopyPV) CopyFENToClipboard();
5558   endPV = -1;
5559   if(gameMode == AnalyzeMode && currentMove > forwardMostMove) {
5560         Boolean saveAnimate = appData.animate;
5561         if(pushed) {
5562             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5563                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5564             } else storedGames--; // abandon shelved tail of original game
5565         }
5566         pushed = FALSE;
5567         forwardMostMove = currentMove;
5568         currentMove = oldFMM;
5569         appData.animate = FALSE;
5570         ToNrEvent(forwardMostMove);
5571         appData.animate = saveAnimate;
5572   }
5573   currentMove = forwardMostMove;
5574   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5575   ClearPremoveHighlights();
5576   DrawPosition(TRUE, boards[currentMove]);
5577 }
5578
5579 void
5580 MovePV (int x, int y, int h)
5581 { // step through PV based on mouse coordinates (called on mouse move)
5582   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5583
5584   // we must somehow check if right button is still down (might be released off board!)
5585   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5586   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5587   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5588   if(!step) return;
5589   lastX = x; lastY = y;
5590
5591   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5592   if(endPV < 0) return;
5593   if(y < margin) step = 1; else
5594   if(y > h - margin) step = -1;
5595   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5596   currentMove += step;
5597   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5598   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5599                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5600   DrawPosition(FALSE, boards[currentMove]);
5601 }
5602
5603
5604 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5605 // All positions will have equal probability, but the current method will not provide a unique
5606 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5607 #define DARK 1
5608 #define LITE 2
5609 #define ANY 3
5610
5611 int squaresLeft[4];
5612 int piecesLeft[(int)BlackPawn];
5613 int seed, nrOfShuffles;
5614
5615 void
5616 GetPositionNumber ()
5617 {       // sets global variable seed
5618         int i;
5619
5620         seed = appData.defaultFrcPosition;
5621         if(seed < 0) { // randomize based on time for negative FRC position numbers
5622                 for(i=0; i<50; i++) seed += random();
5623                 seed = random() ^ random() >> 8 ^ random() << 8;
5624                 if(seed<0) seed = -seed;
5625         }
5626 }
5627
5628 int
5629 put (Board board, int pieceType, int rank, int n, int shade)
5630 // put the piece on the (n-1)-th empty squares of the given shade
5631 {
5632         int i;
5633
5634         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5635                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5636                         board[rank][i] = (ChessSquare) pieceType;
5637                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5638                         squaresLeft[ANY]--;
5639                         piecesLeft[pieceType]--;
5640                         return i;
5641                 }
5642         }
5643         return -1;
5644 }
5645
5646
5647 void
5648 AddOnePiece (Board board, int pieceType, int rank, int shade)
5649 // calculate where the next piece goes, (any empty square), and put it there
5650 {
5651         int i;
5652
5653         i = seed % squaresLeft[shade];
5654         nrOfShuffles *= squaresLeft[shade];
5655         seed /= squaresLeft[shade];
5656         put(board, pieceType, rank, i, shade);
5657 }
5658
5659 void
5660 AddTwoPieces (Board board, int pieceType, int rank)
5661 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5662 {
5663         int i, n=squaresLeft[ANY], j=n-1, k;
5664
5665         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5666         i = seed % k;  // pick one
5667         nrOfShuffles *= k;
5668         seed /= k;
5669         while(i >= j) i -= j--;
5670         j = n - 1 - j; i += j;
5671         put(board, pieceType, rank, j, ANY);
5672         put(board, pieceType, rank, i, ANY);
5673 }
5674
5675 void
5676 SetUpShuffle (Board board, int number)
5677 {
5678         int i, p, first=1;
5679
5680         GetPositionNumber(); nrOfShuffles = 1;
5681
5682         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5683         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5684         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5685
5686         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5687
5688         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5689             p = (int) board[0][i];
5690             if(p < (int) BlackPawn) piecesLeft[p] ++;
5691             board[0][i] = EmptySquare;
5692         }
5693
5694         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5695             // shuffles restricted to allow normal castling put KRR first
5696             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5697                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5698             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5699                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5700             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5701                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5702             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5703                 put(board, WhiteRook, 0, 0, ANY);
5704             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5705         }
5706
5707         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5708             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5709             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5710                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5711                 while(piecesLeft[p] >= 2) {
5712                     AddOnePiece(board, p, 0, LITE);
5713                     AddOnePiece(board, p, 0, DARK);
5714                 }
5715                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5716             }
5717
5718         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5719             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5720             // but we leave King and Rooks for last, to possibly obey FRC restriction
5721             if(p == (int)WhiteRook) continue;
5722             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5723             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5724         }
5725
5726         // now everything is placed, except perhaps King (Unicorn) and Rooks
5727
5728         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5729             // Last King gets castling rights
5730             while(piecesLeft[(int)WhiteUnicorn]) {
5731                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5732                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5733             }
5734
5735             while(piecesLeft[(int)WhiteKing]) {
5736                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5737                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5738             }
5739
5740
5741         } else {
5742             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5743             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5744         }
5745
5746         // Only Rooks can be left; simply place them all
5747         while(piecesLeft[(int)WhiteRook]) {
5748                 i = put(board, WhiteRook, 0, 0, ANY);
5749                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5750                         if(first) {
5751                                 first=0;
5752                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5753                         }
5754                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5755                 }
5756         }
5757         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5758             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5759         }
5760
5761         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5762 }
5763
5764 int
5765 SetCharTable (char *table, const char * map)
5766 /* [HGM] moved here from winboard.c because of its general usefulness */
5767 /*       Basically a safe strcpy that uses the last character as King */
5768 {
5769     int result = FALSE; int NrPieces;
5770
5771     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5772                     && NrPieces >= 12 && !(NrPieces&1)) {
5773         int i; /* [HGM] Accept even length from 12 to 34 */
5774
5775         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5776         for( i=0; i<NrPieces/2-1; i++ ) {
5777             table[i] = map[i];
5778             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5779         }
5780         table[(int) WhiteKing]  = map[NrPieces/2-1];
5781         table[(int) BlackKing]  = map[NrPieces-1];
5782
5783         result = TRUE;
5784     }
5785
5786     return result;
5787 }
5788
5789 void
5790 Prelude (Board board)
5791 {       // [HGM] superchess: random selection of exo-pieces
5792         int i, j, k; ChessSquare p;
5793         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5794
5795         GetPositionNumber(); // use FRC position number
5796
5797         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5798             SetCharTable(pieceToChar, appData.pieceToCharTable);
5799             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5800                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5801         }
5802
5803         j = seed%4;                 seed /= 4;
5804         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5805         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5806         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5807         j = seed%3 + (seed%3 >= j); seed /= 3;
5808         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5809         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5810         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5811         j = seed%3;                 seed /= 3;
5812         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5813         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5814         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5815         j = seed%2 + (seed%2 >= j); seed /= 2;
5816         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5817         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5818         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5819         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5820         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5821         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5822         put(board, exoPieces[0],    0, 0, ANY);
5823         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5824 }
5825
5826 void
5827 InitPosition (int redraw)
5828 {
5829     ChessSquare (* pieces)[BOARD_FILES];
5830     int i, j, pawnRow, overrule,
5831     oldx = gameInfo.boardWidth,
5832     oldy = gameInfo.boardHeight,
5833     oldh = gameInfo.holdingsWidth;
5834     static int oldv;
5835
5836     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5837
5838     /* [AS] Initialize pv info list [HGM] and game status */
5839     {
5840         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5841             pvInfoList[i].depth = 0;
5842             boards[i][EP_STATUS] = EP_NONE;
5843             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5844         }
5845
5846         initialRulePlies = 0; /* 50-move counter start */
5847
5848         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5849         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5850     }
5851
5852
5853     /* [HGM] logic here is completely changed. In stead of full positions */
5854     /* the initialized data only consist of the two backranks. The switch */
5855     /* selects which one we will use, which is than copied to the Board   */
5856     /* initialPosition, which for the rest is initialized by Pawns and    */
5857     /* empty squares. This initial position is then copied to boards[0],  */
5858     /* possibly after shuffling, so that it remains available.            */
5859
5860     gameInfo.holdingsWidth = 0; /* default board sizes */
5861     gameInfo.boardWidth    = 8;
5862     gameInfo.boardHeight   = 8;
5863     gameInfo.holdingsSize  = 0;
5864     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5865     for(i=0; i<BOARD_FILES-2; i++)
5866       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5867     initialPosition[EP_STATUS] = EP_NONE;
5868     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5869     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5870          SetCharTable(pieceNickName, appData.pieceNickNames);
5871     else SetCharTable(pieceNickName, "............");
5872     pieces = FIDEArray;
5873
5874     switch (gameInfo.variant) {
5875     case VariantFischeRandom:
5876       shuffleOpenings = TRUE;
5877     default:
5878       break;
5879     case VariantShatranj:
5880       pieces = ShatranjArray;
5881       nrCastlingRights = 0;
5882       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5883       break;
5884     case VariantMakruk:
5885       pieces = makrukArray;
5886       nrCastlingRights = 0;
5887       startedFromSetupPosition = TRUE;
5888       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5889       break;
5890     case VariantTwoKings:
5891       pieces = twoKingsArray;
5892       break;
5893     case VariantGrand:
5894       pieces = GrandArray;
5895       nrCastlingRights = 0;
5896       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5897       gameInfo.boardWidth = 10;
5898       gameInfo.boardHeight = 10;
5899       gameInfo.holdingsSize = 7;
5900       break;
5901     case VariantCapaRandom:
5902       shuffleOpenings = TRUE;
5903     case VariantCapablanca:
5904       pieces = CapablancaArray;
5905       gameInfo.boardWidth = 10;
5906       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5907       break;
5908     case VariantGothic:
5909       pieces = GothicArray;
5910       gameInfo.boardWidth = 10;
5911       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5912       break;
5913     case VariantSChess:
5914       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5915       gameInfo.holdingsSize = 7;
5916       for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
5917       break;
5918     case VariantJanus:
5919       pieces = JanusArray;
5920       gameInfo.boardWidth = 10;
5921       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5922       nrCastlingRights = 6;
5923         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5924         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5925         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5926         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5927         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5928         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5929       break;
5930     case VariantFalcon:
5931       pieces = FalconArray;
5932       gameInfo.boardWidth = 10;
5933       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5934       break;
5935     case VariantXiangqi:
5936       pieces = XiangqiArray;
5937       gameInfo.boardWidth  = 9;
5938       gameInfo.boardHeight = 10;
5939       nrCastlingRights = 0;
5940       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5941       break;
5942     case VariantShogi:
5943       pieces = ShogiArray;
5944       gameInfo.boardWidth  = 9;
5945       gameInfo.boardHeight = 9;
5946       gameInfo.holdingsSize = 7;
5947       nrCastlingRights = 0;
5948       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5949       break;
5950     case VariantCourier:
5951       pieces = CourierArray;
5952       gameInfo.boardWidth  = 12;
5953       nrCastlingRights = 0;
5954       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5955       break;
5956     case VariantKnightmate:
5957       pieces = KnightmateArray;
5958       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5959       break;
5960     case VariantSpartan:
5961       pieces = SpartanArray;
5962       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5963       break;
5964     case VariantFairy:
5965       pieces = fairyArray;
5966       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5967       break;
5968     case VariantGreat:
5969       pieces = GreatArray;
5970       gameInfo.boardWidth = 10;
5971       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5972       gameInfo.holdingsSize = 8;
5973       break;
5974     case VariantSuper:
5975       pieces = FIDEArray;
5976       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5977       gameInfo.holdingsSize = 8;
5978       startedFromSetupPosition = TRUE;
5979       break;
5980     case VariantCrazyhouse:
5981     case VariantBughouse:
5982       pieces = FIDEArray;
5983       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5984       gameInfo.holdingsSize = 5;
5985       break;
5986     case VariantWildCastle:
5987       pieces = FIDEArray;
5988       /* !!?shuffle with kings guaranteed to be on d or e file */
5989       shuffleOpenings = 1;
5990       break;
5991     case VariantNoCastle:
5992       pieces = FIDEArray;
5993       nrCastlingRights = 0;
5994       /* !!?unconstrained back-rank shuffle */
5995       shuffleOpenings = 1;
5996       break;
5997     }
5998
5999     overrule = 0;
6000     if(appData.NrFiles >= 0) {
6001         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6002         gameInfo.boardWidth = appData.NrFiles;
6003     }
6004     if(appData.NrRanks >= 0) {
6005         gameInfo.boardHeight = appData.NrRanks;
6006     }
6007     if(appData.holdingsSize >= 0) {
6008         i = appData.holdingsSize;
6009         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6010         gameInfo.holdingsSize = i;
6011     }
6012     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6013     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6014         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6015
6016     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6017     if(pawnRow < 1) pawnRow = 1;
6018     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) pawnRow = 2;
6019
6020     /* User pieceToChar list overrules defaults */
6021     if(appData.pieceToCharTable != NULL)
6022         SetCharTable(pieceToChar, appData.pieceToCharTable);
6023
6024     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6025
6026         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6027             s = (ChessSquare) 0; /* account holding counts in guard band */
6028         for( i=0; i<BOARD_HEIGHT; i++ )
6029             initialPosition[i][j] = s;
6030
6031         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6032         initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
6033         initialPosition[pawnRow][j] = WhitePawn;
6034         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6035         if(gameInfo.variant == VariantXiangqi) {
6036             if(j&1) {
6037                 initialPosition[pawnRow][j] =
6038                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6039                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6040                    initialPosition[2][j] = WhiteCannon;
6041                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6042                 }
6043             }
6044         }
6045         if(gameInfo.variant == VariantGrand) {
6046             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6047                initialPosition[0][j] = WhiteRook;
6048                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6049             }
6050         }
6051         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] =  pieces[1][j-gameInfo.holdingsWidth];
6052     }
6053     if( (gameInfo.variant == VariantShogi) && !overrule ) {
6054
6055             j=BOARD_LEFT+1;
6056             initialPosition[1][j] = WhiteBishop;
6057             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6058             j=BOARD_RGHT-2;
6059             initialPosition[1][j] = WhiteRook;
6060             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6061     }
6062
6063     if( nrCastlingRights == -1) {
6064         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6065         /*       This sets default castling rights from none to normal corners   */
6066         /* Variants with other castling rights must set them themselves above    */
6067         nrCastlingRights = 6;
6068
6069         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6070         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6071         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6072         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6073         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6074         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6075      }
6076
6077      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6078      if(gameInfo.variant == VariantGreat) { // promotion commoners
6079         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6080         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6081         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6082         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6083      }
6084      if( gameInfo.variant == VariantSChess ) {
6085       initialPosition[1][0] = BlackMarshall;
6086       initialPosition[2][0] = BlackAngel;
6087       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6088       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6089       initialPosition[1][1] = initialPosition[2][1] =
6090       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6091      }
6092   if (appData.debugMode) {
6093     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6094   }
6095     if(shuffleOpenings) {
6096         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6097         startedFromSetupPosition = TRUE;
6098     }
6099     if(startedFromPositionFile) {
6100       /* [HGM] loadPos: use PositionFile for every new game */
6101       CopyBoard(initialPosition, filePosition);
6102       for(i=0; i<nrCastlingRights; i++)
6103           initialRights[i] = filePosition[CASTLING][i];
6104       startedFromSetupPosition = TRUE;
6105     }
6106
6107     CopyBoard(boards[0], initialPosition);
6108
6109     if(oldx != gameInfo.boardWidth ||
6110        oldy != gameInfo.boardHeight ||
6111        oldv != gameInfo.variant ||
6112        oldh != gameInfo.holdingsWidth
6113                                          )
6114             InitDrawingSizes(-2 ,0);
6115
6116     oldv = gameInfo.variant;
6117     if (redraw)
6118       DrawPosition(TRUE, boards[currentMove]);
6119 }
6120
6121 void
6122 SendBoard (ChessProgramState *cps, int moveNum)
6123 {
6124     char message[MSG_SIZ];
6125
6126     if (cps->useSetboard) {
6127       char* fen = PositionToFEN(moveNum, cps->fenOverride);
6128       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6129       SendToProgram(message, cps);
6130       free(fen);
6131
6132     } else {
6133       ChessSquare *bp;
6134       int i, j, left=0, right=BOARD_WIDTH;
6135       /* Kludge to set black to move, avoiding the troublesome and now
6136        * deprecated "black" command.
6137        */
6138       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6139         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6140
6141       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6142
6143       SendToProgram("edit\n", cps);
6144       SendToProgram("#\n", cps);
6145       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6146         bp = &boards[moveNum][i][left];
6147         for (j = left; j < right; j++, bp++) {
6148           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6149           if ((int) *bp < (int) BlackPawn) {
6150             if(j == BOARD_RGHT+1)
6151                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6152             else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6153             if(message[0] == '+' || message[0] == '~') {
6154               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6155                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6156                         AAA + j, ONE + i);
6157             }
6158             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6159                 message[1] = BOARD_RGHT   - 1 - j + '1';
6160                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6161             }
6162             SendToProgram(message, cps);
6163           }
6164         }
6165       }
6166
6167       SendToProgram("c\n", cps);
6168       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6169         bp = &boards[moveNum][i][left];
6170         for (j = left; j < right; j++, bp++) {
6171           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6172           if (((int) *bp != (int) EmptySquare)
6173               && ((int) *bp >= (int) BlackPawn)) {
6174             if(j == BOARD_LEFT-2)
6175                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6176             else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6177                     AAA + j, ONE + i);
6178             if(message[0] == '+' || message[0] == '~') {
6179               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6180                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6181                         AAA + j, ONE + i);
6182             }
6183             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6184                 message[1] = BOARD_RGHT   - 1 - j + '1';
6185                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6186             }
6187             SendToProgram(message, cps);
6188           }
6189         }
6190       }
6191
6192       SendToProgram(".\n", cps);
6193     }
6194     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6195 }
6196
6197 char exclusionHeader[MSG_SIZ];
6198 int exCnt, excludePtr;
6199 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6200 static Exclusion excluTab[200];
6201 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6202
6203 static void
6204 WriteMap (int s)
6205 {
6206     int j;
6207     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6208     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6209 }
6210
6211 static void
6212 ClearMap ()
6213 {
6214     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6215     excludePtr = 24; exCnt = 0;
6216     WriteMap(0);
6217 }
6218
6219 static void
6220 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6221 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6222     char buf[2*MOVE_LEN], *p;
6223     Exclusion *e = excluTab;
6224     int i;
6225     for(i=0; i<exCnt; i++)
6226         if(e[i].ff == fromX && e[i].fr == fromY &&
6227            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6228     if(i == exCnt) { // was not in exclude list; add it
6229         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6230         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6231             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6232             return; // abort
6233         }
6234         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6235         excludePtr++; e[i].mark = excludePtr++;
6236         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6237         exCnt++;
6238     }
6239     exclusionHeader[e[i].mark] = state;
6240 }
6241
6242 static int
6243 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6244 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6245     char buf[MSG_SIZ];
6246     int j, k;
6247     ChessMove moveType;
6248     if((signed char)promoChar == -1) { // kludge to indicate best move
6249         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6250             return 1; // if unparsable, abort
6251     }
6252     // update exclusion map (resolving toggle by consulting existing state)
6253     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6254     j = k%8; k >>= 3;
6255     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6256     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6257          excludeMap[k] |=   1<<j;
6258     else excludeMap[k] &= ~(1<<j);
6259     // update header
6260     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6261     // inform engine
6262     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6263     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6264     SendToBoth(buf);
6265     return (state == '+');
6266 }
6267
6268 static void
6269 ExcludeClick (int index)
6270 {
6271     int i, j;
6272     Exclusion *e = excluTab;
6273     if(index < 25) { // none, best or tail clicked
6274         if(index < 13) { // none: include all
6275             WriteMap(0); // clear map
6276             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6277             SendToBoth("include all\n"); // and inform engine
6278         } else if(index > 18) { // tail
6279             if(exclusionHeader[19] == '-') { // tail was excluded
6280                 SendToBoth("include all\n");
6281                 WriteMap(0); // clear map completely
6282                 // now re-exclude selected moves
6283                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6284                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6285             } else { // tail was included or in mixed state
6286                 SendToBoth("exclude all\n");
6287                 WriteMap(0xFF); // fill map completely
6288                 // now re-include selected moves
6289                 j = 0; // count them
6290                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6291                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6292                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6293             }
6294         } else { // best
6295             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6296         }
6297     } else {
6298         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6299             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6300             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6301             break;
6302         }
6303     }
6304 }
6305
6306 ChessSquare
6307 DefaultPromoChoice (int white)
6308 {
6309     ChessSquare result;
6310     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
6311         result = WhiteFerz; // no choice
6312     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6313         result= WhiteKing; // in Suicide Q is the last thing we want
6314     else if(gameInfo.variant == VariantSpartan)
6315         result = white ? WhiteQueen : WhiteAngel;
6316     else result = WhiteQueen;
6317     if(!white) result = WHITE_TO_BLACK result;
6318     return result;
6319 }
6320
6321 static int autoQueen; // [HGM] oneclick
6322
6323 int
6324 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6325 {
6326     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6327     /* [HGM] add Shogi promotions */
6328     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6329     ChessSquare piece;
6330     ChessMove moveType;
6331     Boolean premove;
6332
6333     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6334     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6335
6336     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6337       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6338         return FALSE;
6339
6340     piece = boards[currentMove][fromY][fromX];
6341     if(gameInfo.variant == VariantShogi) {
6342         promotionZoneSize = BOARD_HEIGHT/3;
6343         highestPromotingPiece = (int)WhiteFerz;
6344     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6345         promotionZoneSize = 3;
6346     }
6347
6348     // Treat Lance as Pawn when it is not representing Amazon
6349     if(gameInfo.variant != VariantSuper) {
6350         if(piece == WhiteLance) piece = WhitePawn; else
6351         if(piece == BlackLance) piece = BlackPawn;
6352     }
6353
6354     // next weed out all moves that do not touch the promotion zone at all
6355     if((int)piece >= BlackPawn) {
6356         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6357              return FALSE;
6358         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6359     } else {
6360         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6361            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6362     }
6363
6364     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6365
6366     // weed out mandatory Shogi promotions
6367     if(gameInfo.variant == VariantShogi) {
6368         if(piece >= BlackPawn) {
6369             if(toY == 0 && piece == BlackPawn ||
6370                toY == 0 && piece == BlackQueen ||
6371                toY <= 1 && piece == BlackKnight) {
6372                 *promoChoice = '+';
6373                 return FALSE;
6374             }
6375         } else {
6376             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6377                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6378                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6379                 *promoChoice = '+';
6380                 return FALSE;
6381             }
6382         }
6383     }
6384
6385     // weed out obviously illegal Pawn moves
6386     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6387         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6388         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6389         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6390         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6391         // note we are not allowed to test for valid (non-)capture, due to premove
6392     }
6393
6394     // we either have a choice what to promote to, or (in Shogi) whether to promote
6395     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6396         *promoChoice = PieceToChar(BlackFerz);  // no choice
6397         return FALSE;
6398     }
6399     // no sense asking what we must promote to if it is going to explode...
6400     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6401         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6402         return FALSE;
6403     }
6404     // give caller the default choice even if we will not make it
6405     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6406     if(gameInfo.variant == VariantShogi) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6407     if(        sweepSelect && gameInfo.variant != VariantGreat
6408                            && gameInfo.variant != VariantGrand
6409                            && gameInfo.variant != VariantSuper) return FALSE;
6410     if(autoQueen) return FALSE; // predetermined
6411
6412     // suppress promotion popup on illegal moves that are not premoves
6413     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6414               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6415     if(appData.testLegality && !premove) {
6416         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6417                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6418         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6419             return FALSE;
6420     }
6421
6422     return TRUE;
6423 }
6424
6425 int
6426 InPalace (int row, int column)
6427 {   /* [HGM] for Xiangqi */
6428     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6429          column < (BOARD_WIDTH + 4)/2 &&
6430          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6431     return FALSE;
6432 }
6433
6434 int
6435 PieceForSquare (int x, int y)
6436 {
6437   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6438      return -1;
6439   else
6440      return boards[currentMove][y][x];
6441 }
6442
6443 int
6444 OKToStartUserMove (int x, int y)
6445 {
6446     ChessSquare from_piece;
6447     int white_piece;
6448
6449     if (matchMode) return FALSE;
6450     if (gameMode == EditPosition) return TRUE;
6451
6452     if (x >= 0 && y >= 0)
6453       from_piece = boards[currentMove][y][x];
6454     else
6455       from_piece = EmptySquare;
6456
6457     if (from_piece == EmptySquare) return FALSE;
6458
6459     white_piece = (int)from_piece >= (int)WhitePawn &&
6460       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6461
6462     switch (gameMode) {
6463       case AnalyzeFile:
6464       case TwoMachinesPlay:
6465       case EndOfGame:
6466         return FALSE;
6467
6468       case IcsObserving:
6469       case IcsIdle:
6470         return FALSE;
6471
6472       case MachinePlaysWhite:
6473       case IcsPlayingBlack:
6474         if (appData.zippyPlay) return FALSE;
6475         if (white_piece) {
6476             DisplayMoveError(_("You are playing Black"));
6477             return FALSE;
6478         }
6479         break;
6480
6481       case MachinePlaysBlack:
6482       case IcsPlayingWhite:
6483         if (appData.zippyPlay) return FALSE;
6484         if (!white_piece) {
6485             DisplayMoveError(_("You are playing White"));
6486             return FALSE;
6487         }
6488         break;
6489
6490       case PlayFromGameFile:
6491             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6492       case EditGame:
6493         if (!white_piece && WhiteOnMove(currentMove)) {
6494             DisplayMoveError(_("It is White's turn"));
6495             return FALSE;
6496         }
6497         if (white_piece && !WhiteOnMove(currentMove)) {
6498             DisplayMoveError(_("It is Black's turn"));
6499             return FALSE;
6500         }
6501         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6502             /* Editing correspondence game history */
6503             /* Could disallow this or prompt for confirmation */
6504             cmailOldMove = -1;
6505         }
6506         break;
6507
6508       case BeginningOfGame:
6509         if (appData.icsActive) return FALSE;
6510         if (!appData.noChessProgram) {
6511             if (!white_piece) {
6512                 DisplayMoveError(_("You are playing White"));
6513                 return FALSE;
6514             }
6515         }
6516         break;
6517
6518       case Training:
6519         if (!white_piece && WhiteOnMove(currentMove)) {
6520             DisplayMoveError(_("It is White's turn"));
6521             return FALSE;
6522         }
6523         if (white_piece && !WhiteOnMove(currentMove)) {
6524             DisplayMoveError(_("It is Black's turn"));
6525             return FALSE;
6526         }
6527         break;
6528
6529       default:
6530       case IcsExamining:
6531         break;
6532     }
6533     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6534         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6535         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6536         && gameMode != AnalyzeFile && gameMode != Training) {
6537         DisplayMoveError(_("Displayed position is not current"));
6538         return FALSE;
6539     }
6540     return TRUE;
6541 }
6542
6543 Boolean
6544 OnlyMove (int *x, int *y, Boolean captures)
6545 {
6546     DisambiguateClosure cl;
6547     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6548     switch(gameMode) {
6549       case MachinePlaysBlack:
6550       case IcsPlayingWhite:
6551       case BeginningOfGame:
6552         if(!WhiteOnMove(currentMove)) return FALSE;
6553         break;
6554       case MachinePlaysWhite:
6555       case IcsPlayingBlack:
6556         if(WhiteOnMove(currentMove)) return FALSE;
6557         break;
6558       case EditGame:
6559         break;
6560       default:
6561         return FALSE;
6562     }
6563     cl.pieceIn = EmptySquare;
6564     cl.rfIn = *y;
6565     cl.ffIn = *x;
6566     cl.rtIn = -1;
6567     cl.ftIn = -1;
6568     cl.promoCharIn = NULLCHAR;
6569     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6570     if( cl.kind == NormalMove ||
6571         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6572         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6573         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6574       fromX = cl.ff;
6575       fromY = cl.rf;
6576       *x = cl.ft;
6577       *y = cl.rt;
6578       return TRUE;
6579     }
6580     if(cl.kind != ImpossibleMove) return FALSE;
6581     cl.pieceIn = EmptySquare;
6582     cl.rfIn = -1;
6583     cl.ffIn = -1;
6584     cl.rtIn = *y;
6585     cl.ftIn = *x;
6586     cl.promoCharIn = NULLCHAR;
6587     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6588     if( cl.kind == NormalMove ||
6589         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6590         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6591         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6592       fromX = cl.ff;
6593       fromY = cl.rf;
6594       *x = cl.ft;
6595       *y = cl.rt;
6596       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6597       return TRUE;
6598     }
6599     return FALSE;
6600 }
6601
6602 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6603 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6604 int lastLoadGameUseList = FALSE;
6605 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6606 ChessMove lastLoadGameStart = EndOfFile;
6607 int doubleClick;
6608
6609 void
6610 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6611 {
6612     ChessMove moveType;
6613     ChessSquare pup;
6614     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6615
6616     /* Check if the user is playing in turn.  This is complicated because we
6617        let the user "pick up" a piece before it is his turn.  So the piece he
6618        tried to pick up may have been captured by the time he puts it down!
6619        Therefore we use the color the user is supposed to be playing in this
6620        test, not the color of the piece that is currently on the starting
6621        square---except in EditGame mode, where the user is playing both
6622        sides; fortunately there the capture race can't happen.  (It can
6623        now happen in IcsExamining mode, but that's just too bad.  The user
6624        will get a somewhat confusing message in that case.)
6625        */
6626
6627     switch (gameMode) {
6628       case AnalyzeFile:
6629       case TwoMachinesPlay:
6630       case EndOfGame:
6631       case IcsObserving:
6632       case IcsIdle:
6633         /* We switched into a game mode where moves are not accepted,
6634            perhaps while the mouse button was down. */
6635         return;
6636
6637       case MachinePlaysWhite:
6638         /* User is moving for Black */
6639         if (WhiteOnMove(currentMove)) {
6640             DisplayMoveError(_("It is White's turn"));
6641             return;
6642         }
6643         break;
6644
6645       case MachinePlaysBlack:
6646         /* User is moving for White */
6647         if (!WhiteOnMove(currentMove)) {
6648             DisplayMoveError(_("It is Black's turn"));
6649             return;
6650         }
6651         break;
6652
6653       case PlayFromGameFile:
6654             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6655       case EditGame:
6656       case IcsExamining:
6657       case BeginningOfGame:
6658       case AnalyzeMode:
6659       case Training:
6660         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6661         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6662             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6663             /* User is moving for Black */
6664             if (WhiteOnMove(currentMove)) {
6665                 DisplayMoveError(_("It is White's turn"));
6666                 return;
6667             }
6668         } else {
6669             /* User is moving for White */
6670             if (!WhiteOnMove(currentMove)) {
6671                 DisplayMoveError(_("It is Black's turn"));
6672                 return;
6673             }
6674         }
6675         break;
6676
6677       case IcsPlayingBlack:
6678         /* User is moving for Black */
6679         if (WhiteOnMove(currentMove)) {
6680             if (!appData.premove) {
6681                 DisplayMoveError(_("It is White's turn"));
6682             } else if (toX >= 0 && toY >= 0) {
6683                 premoveToX = toX;
6684                 premoveToY = toY;
6685                 premoveFromX = fromX;
6686                 premoveFromY = fromY;
6687                 premovePromoChar = promoChar;
6688                 gotPremove = 1;
6689                 if (appData.debugMode)
6690                     fprintf(debugFP, "Got premove: fromX %d,"
6691                             "fromY %d, toX %d, toY %d\n",
6692                             fromX, fromY, toX, toY);
6693             }
6694             return;
6695         }
6696         break;
6697
6698       case IcsPlayingWhite:
6699         /* User is moving for White */
6700         if (!WhiteOnMove(currentMove)) {
6701             if (!appData.premove) {
6702                 DisplayMoveError(_("It is Black's turn"));
6703             } else if (toX >= 0 && toY >= 0) {
6704                 premoveToX = toX;
6705                 premoveToY = toY;
6706                 premoveFromX = fromX;
6707                 premoveFromY = fromY;
6708                 premovePromoChar = promoChar;
6709                 gotPremove = 1;
6710                 if (appData.debugMode)
6711                     fprintf(debugFP, "Got premove: fromX %d,"
6712                             "fromY %d, toX %d, toY %d\n",
6713                             fromX, fromY, toX, toY);
6714             }
6715             return;
6716         }
6717         break;
6718
6719       default:
6720         break;
6721
6722       case EditPosition:
6723         /* EditPosition, empty square, or different color piece;
6724            click-click move is possible */
6725         if (toX == -2 || toY == -2) {
6726             boards[0][fromY][fromX] = EmptySquare;
6727             DrawPosition(FALSE, boards[currentMove]);
6728             return;
6729         } else if (toX >= 0 && toY >= 0) {
6730             boards[0][toY][toX] = boards[0][fromY][fromX];
6731             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6732                 if(boards[0][fromY][0] != EmptySquare) {
6733                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6734                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6735                 }
6736             } else
6737             if(fromX == BOARD_RGHT+1) {
6738                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6739                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6740                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6741                 }
6742             } else
6743             boards[0][fromY][fromX] = gatingPiece;
6744             DrawPosition(FALSE, boards[currentMove]);
6745             return;
6746         }
6747         return;
6748     }
6749
6750     if(toX < 0 || toY < 0) return;
6751     pup = boards[currentMove][toY][toX];
6752
6753     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6754     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6755          if( pup != EmptySquare ) return;
6756          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6757            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6758                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6759            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6760            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6761            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6762            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6763          fromY = DROP_RANK;
6764     }
6765
6766     /* [HGM] always test for legality, to get promotion info */
6767     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6768                                          fromY, fromX, toY, toX, promoChar);
6769
6770     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6771
6772     /* [HGM] but possibly ignore an IllegalMove result */
6773     if (appData.testLegality) {
6774         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6775             DisplayMoveError(_("Illegal move"));
6776             return;
6777         }
6778     }
6779
6780     if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
6781         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
6782              ClearPremoveHighlights(); // was included
6783         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
6784         return;
6785     }
6786
6787     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6788 }
6789
6790 /* Common tail of UserMoveEvent and DropMenuEvent */
6791 int
6792 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
6793 {
6794     char *bookHit = 0;
6795
6796     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6797         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6798         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6799         if(WhiteOnMove(currentMove)) {
6800             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6801         } else {
6802             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6803         }
6804     }
6805
6806     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6807        move type in caller when we know the move is a legal promotion */
6808     if(moveType == NormalMove && promoChar)
6809         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6810
6811     /* [HGM] <popupFix> The following if has been moved here from
6812        UserMoveEvent(). Because it seemed to belong here (why not allow
6813        piece drops in training games?), and because it can only be
6814        performed after it is known to what we promote. */
6815     if (gameMode == Training) {
6816       /* compare the move played on the board to the next move in the
6817        * game. If they match, display the move and the opponent's response.
6818        * If they don't match, display an error message.
6819        */
6820       int saveAnimate;
6821       Board testBoard;
6822       CopyBoard(testBoard, boards[currentMove]);
6823       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6824
6825       if (CompareBoards(testBoard, boards[currentMove+1])) {
6826         ForwardInner(currentMove+1);
6827
6828         /* Autoplay the opponent's response.
6829          * if appData.animate was TRUE when Training mode was entered,
6830          * the response will be animated.
6831          */
6832         saveAnimate = appData.animate;
6833         appData.animate = animateTraining;
6834         ForwardInner(currentMove+1);
6835         appData.animate = saveAnimate;
6836
6837         /* check for the end of the game */
6838         if (currentMove >= forwardMostMove) {
6839           gameMode = PlayFromGameFile;
6840           ModeHighlight();
6841           SetTrainingModeOff();
6842           DisplayInformation(_("End of game"));
6843         }
6844       } else {
6845         DisplayError(_("Incorrect move"), 0);
6846       }
6847       return 1;
6848     }
6849
6850   /* Ok, now we know that the move is good, so we can kill
6851      the previous line in Analysis Mode */
6852   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6853                                 && currentMove < forwardMostMove) {
6854     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6855     else forwardMostMove = currentMove;
6856   }
6857
6858   ClearMap();
6859
6860   /* If we need the chess program but it's dead, restart it */
6861   ResurrectChessProgram();
6862
6863   /* A user move restarts a paused game*/
6864   if (pausing)
6865     PauseEvent();
6866
6867   thinkOutput[0] = NULLCHAR;
6868
6869   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6870
6871   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6872     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6873     return 1;
6874   }
6875
6876   if (gameMode == BeginningOfGame) {
6877     if (appData.noChessProgram) {
6878       gameMode = EditGame;
6879       SetGameInfo();
6880     } else {
6881       char buf[MSG_SIZ];
6882       gameMode = MachinePlaysBlack;
6883       StartClocks();
6884       SetGameInfo();
6885       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
6886       DisplayTitle(buf);
6887       if (first.sendName) {
6888         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6889         SendToProgram(buf, &first);
6890       }
6891       StartClocks();
6892     }
6893     ModeHighlight();
6894   }
6895
6896   /* Relay move to ICS or chess engine */
6897   if (appData.icsActive) {
6898     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6899         gameMode == IcsExamining) {
6900       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6901         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6902         SendToICS("draw ");
6903         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6904       }
6905       // also send plain move, in case ICS does not understand atomic claims
6906       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6907       ics_user_moved = 1;
6908     }
6909   } else {
6910     if (first.sendTime && (gameMode == BeginningOfGame ||
6911                            gameMode == MachinePlaysWhite ||
6912                            gameMode == MachinePlaysBlack)) {
6913       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6914     }
6915     if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
6916          // [HGM] book: if program might be playing, let it use book
6917         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6918         first.maybeThinking = TRUE;
6919     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
6920         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
6921         SendBoard(&first, currentMove+1);
6922         if(second.analyzing) {
6923             if(!second.useSetboard) SendToProgram("undo\n", &second);
6924             SendBoard(&second, currentMove+1);
6925         }
6926     } else {
6927         SendMoveToProgram(forwardMostMove-1, &first);
6928         if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
6929     }
6930     if (currentMove == cmailOldMove + 1) {
6931       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6932     }
6933   }
6934
6935   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6936
6937   switch (gameMode) {
6938   case EditGame:
6939     if(appData.testLegality)
6940     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6941     case MT_NONE:
6942     case MT_CHECK:
6943       break;
6944     case MT_CHECKMATE:
6945     case MT_STAINMATE:
6946       if (WhiteOnMove(currentMove)) {
6947         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6948       } else {
6949         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6950       }
6951       break;
6952     case MT_STALEMATE:
6953       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6954       break;
6955     }
6956     break;
6957
6958   case MachinePlaysBlack:
6959   case MachinePlaysWhite:
6960     /* disable certain menu options while machine is thinking */
6961     SetMachineThinkingEnables();
6962     break;
6963
6964   default:
6965     break;
6966   }
6967
6968   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6969   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6970
6971   if(bookHit) { // [HGM] book: simulate book reply
6972         static char bookMove[MSG_SIZ]; // a bit generous?
6973
6974         programStats.nodes = programStats.depth = programStats.time =
6975         programStats.score = programStats.got_only_move = 0;
6976         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6977
6978         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6979         strcat(bookMove, bookHit);
6980         HandleMachineMove(bookMove, &first);
6981   }
6982   return 1;
6983 }
6984
6985 void
6986 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
6987 {
6988     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6989     Markers *m = (Markers *) closure;
6990     if(rf == fromY && ff == fromX)
6991         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6992                          || kind == WhiteCapturesEnPassant
6993                          || kind == BlackCapturesEnPassant);
6994     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6995 }
6996
6997 void
6998 MarkTargetSquares (int clear)
6999 {
7000   int x, y;
7001   if(clear) // no reason to ever suppress clearing
7002     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
7003   if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7004      !appData.testLegality || gameMode == EditPosition) return;
7005   if(!clear) {
7006     int capt = 0;
7007     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7008     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7009       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7010       if(capt)
7011       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7012     }
7013   }
7014   DrawPosition(FALSE, NULL);
7015 }
7016
7017 int
7018 Explode (Board board, int fromX, int fromY, int toX, int toY)
7019 {
7020     if(gameInfo.variant == VariantAtomic &&
7021        (board[toY][toX] != EmptySquare ||                     // capture?
7022         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
7023                          board[fromY][fromX] == BlackPawn   )
7024       )) {
7025         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7026         return TRUE;
7027     }
7028     return FALSE;
7029 }
7030
7031 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7032
7033 int
7034 CanPromote (ChessSquare piece, int y)
7035 {
7036         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7037         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7038         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
7039            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7040            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7041                                                   gameInfo.variant == VariantMakruk) return FALSE;
7042         return (piece == BlackPawn && y == 1 ||
7043                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
7044                 piece == BlackLance && y == 1 ||
7045                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
7046 }
7047
7048 void
7049 LeftClick (ClickType clickType, int xPix, int yPix)
7050 {
7051     int x, y;
7052     Boolean saveAnimate;
7053     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7054     char promoChoice = NULLCHAR;
7055     ChessSquare piece;
7056     static TimeMark lastClickTime, prevClickTime;
7057
7058     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7059
7060     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7061
7062     if (clickType == Press) ErrorPopDown();
7063
7064     x = EventToSquare(xPix, BOARD_WIDTH);
7065     y = EventToSquare(yPix, BOARD_HEIGHT);
7066     if (!flipView && y >= 0) {
7067         y = BOARD_HEIGHT - 1 - y;
7068     }
7069     if (flipView && x >= 0) {
7070         x = BOARD_WIDTH - 1 - x;
7071     }
7072
7073     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7074         defaultPromoChoice = promoSweep;
7075         promoSweep = EmptySquare;   // terminate sweep
7076         promoDefaultAltered = TRUE;
7077         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7078     }
7079
7080     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7081         if(clickType == Release) return; // ignore upclick of click-click destination
7082         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7083         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7084         if(gameInfo.holdingsWidth &&
7085                 (WhiteOnMove(currentMove)
7086                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7087                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7088             // click in right holdings, for determining promotion piece
7089             ChessSquare p = boards[currentMove][y][x];
7090             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7091             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7092             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7093                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7094                 fromX = fromY = -1;
7095                 return;
7096             }
7097         }
7098         DrawPosition(FALSE, boards[currentMove]);
7099         return;
7100     }
7101
7102     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7103     if(clickType == Press
7104             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7105               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7106               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7107         return;
7108
7109     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7110         // could be static click on premove from-square: abort premove
7111         gotPremove = 0;
7112         ClearPremoveHighlights();
7113     }
7114
7115     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7116         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7117
7118     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7119         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7120                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7121         defaultPromoChoice = DefaultPromoChoice(side);
7122     }
7123
7124     autoQueen = appData.alwaysPromoteToQueen;
7125
7126     if (fromX == -1) {
7127       int originalY = y;
7128       gatingPiece = EmptySquare;
7129       if (clickType != Press) {
7130         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7131             DragPieceEnd(xPix, yPix); dragging = 0;
7132             DrawPosition(FALSE, NULL);
7133         }
7134         return;
7135       }
7136       doubleClick = FALSE;
7137       if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7138         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7139       }
7140       fromX = x; fromY = y; toX = toY = -1;
7141       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7142          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7143          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7144             /* First square */
7145             if (OKToStartUserMove(fromX, fromY)) {
7146                 second = 0;
7147                 MarkTargetSquares(0);
7148                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7149                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7150                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7151                     promoSweep = defaultPromoChoice;
7152                     selectFlag = 0; lastX = xPix; lastY = yPix;
7153                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7154                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7155                 }
7156                 if (appData.highlightDragging) {
7157                     SetHighlights(fromX, fromY, -1, -1);
7158                 } else {
7159                     ClearHighlights();
7160                 }
7161             } else fromX = fromY = -1;
7162             return;
7163         }
7164     }
7165
7166     /* fromX != -1 */
7167     if (clickType == Press && gameMode != EditPosition) {
7168         ChessSquare fromP;
7169         ChessSquare toP;
7170         int frc;
7171
7172         // ignore off-board to clicks
7173         if(y < 0 || x < 0) return;
7174
7175         /* Check if clicking again on the same color piece */
7176         fromP = boards[currentMove][fromY][fromX];
7177         toP = boards[currentMove][y][x];
7178         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
7179         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
7180              WhitePawn <= toP && toP <= WhiteKing &&
7181              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7182              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7183             (BlackPawn <= fromP && fromP <= BlackKing &&
7184              BlackPawn <= toP && toP <= BlackKing &&
7185              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7186              !(fromP == BlackKing && toP == BlackRook && frc))) {
7187             /* Clicked again on same color piece -- changed his mind */
7188             second = (x == fromX && y == fromY);
7189             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7190                 second = FALSE; // first double-click rather than scond click
7191                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7192             }
7193             promoDefaultAltered = FALSE;
7194             MarkTargetSquares(1);
7195            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
7196             if (appData.highlightDragging) {
7197                 SetHighlights(x, y, -1, -1);
7198             } else {
7199                 ClearHighlights();
7200             }
7201             if (OKToStartUserMove(x, y)) {
7202                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7203                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7204                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7205                  gatingPiece = boards[currentMove][fromY][fromX];
7206                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7207                 fromX = x;
7208                 fromY = y; dragging = 1;
7209                 MarkTargetSquares(0);
7210                 DragPieceBegin(xPix, yPix, FALSE);
7211                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7212                     promoSweep = defaultPromoChoice;
7213                     selectFlag = 0; lastX = xPix; lastY = yPix;
7214                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7215                 }
7216             }
7217            }
7218            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7219            second = FALSE;
7220         }
7221         // ignore clicks on holdings
7222         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7223     }
7224
7225     if (clickType == Release && x == fromX && y == fromY) {
7226         DragPieceEnd(xPix, yPix); dragging = 0;
7227         if(clearFlag) {
7228             // a deferred attempt to click-click move an empty square on top of a piece
7229             boards[currentMove][y][x] = EmptySquare;
7230             ClearHighlights();
7231             DrawPosition(FALSE, boards[currentMove]);
7232             fromX = fromY = -1; clearFlag = 0;
7233             return;
7234         }
7235         if (appData.animateDragging) {
7236             /* Undo animation damage if any */
7237             DrawPosition(FALSE, NULL);
7238         }
7239         if (second || sweepSelecting) {
7240             /* Second up/down in same square; just abort move */
7241             if(sweepSelecting) DrawPosition(FALSE, boards[currentMove]);
7242             second = sweepSelecting = 0;
7243             fromX = fromY = -1;
7244             gatingPiece = EmptySquare;
7245             ClearHighlights();
7246             gotPremove = 0;
7247             ClearPremoveHighlights();
7248         } else {
7249             /* First upclick in same square; start click-click mode */
7250             SetHighlights(x, y, -1, -1);
7251         }
7252         return;
7253     }
7254
7255     clearFlag = 0;
7256
7257     /* we now have a different from- and (possibly off-board) to-square */
7258     /* Completed move */
7259     if(!sweepSelecting) {
7260         toX = x;
7261         toY = y;
7262     } else sweepSelecting = 0; // this must be the up-click corresponding to the down-click that started the sweep
7263
7264     saveAnimate = appData.animate;
7265     if (clickType == Press) {
7266         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7267             // must be Edit Position mode with empty-square selected
7268             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7269             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7270             return;
7271         }
7272         if(HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7273           if(appData.sweepSelect) {
7274             ChessSquare piece = boards[currentMove][fromY][fromX];
7275             promoSweep = defaultPromoChoice;
7276             if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
7277             selectFlag = 0; lastX = xPix; lastY = yPix;
7278             Sweep(0); // Pawn that is going to promote: preview promotion piece
7279             sweepSelecting = 1;
7280             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7281             MarkTargetSquares(1);
7282           }
7283           return; // promo popup appears on up-click
7284         }
7285         /* Finish clickclick move */
7286         if (appData.animate || appData.highlightLastMove) {
7287             SetHighlights(fromX, fromY, toX, toY);
7288         } else {
7289             ClearHighlights();
7290         }
7291     } else {
7292 #if 0
7293 // [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
7294         /* Finish drag move */
7295         if (appData.highlightLastMove) {
7296             SetHighlights(fromX, fromY, toX, toY);
7297         } else {
7298             ClearHighlights();
7299         }
7300 #endif
7301         DragPieceEnd(xPix, yPix); dragging = 0;
7302         /* Don't animate move and drag both */
7303         appData.animate = FALSE;
7304     }
7305
7306     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7307     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7308         ChessSquare piece = boards[currentMove][fromY][fromX];
7309         if(gameMode == EditPosition && piece != EmptySquare &&
7310            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7311             int n;
7312
7313             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7314                 n = PieceToNumber(piece - (int)BlackPawn);
7315                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7316                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7317                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7318             } else
7319             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7320                 n = PieceToNumber(piece);
7321                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7322                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7323                 boards[currentMove][n][BOARD_WIDTH-2]++;
7324             }
7325             boards[currentMove][fromY][fromX] = EmptySquare;
7326         }
7327         ClearHighlights();
7328         fromX = fromY = -1;
7329         MarkTargetSquares(1);
7330         DrawPosition(TRUE, boards[currentMove]);
7331         return;
7332     }
7333
7334     // off-board moves should not be highlighted
7335     if(x < 0 || y < 0) ClearHighlights();
7336
7337     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7338
7339     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7340         SetHighlights(fromX, fromY, toX, toY);
7341         MarkTargetSquares(1);
7342         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7343             // [HGM] super: promotion to captured piece selected from holdings
7344             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7345             promotionChoice = TRUE;
7346             // kludge follows to temporarily execute move on display, without promoting yet
7347             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7348             boards[currentMove][toY][toX] = p;
7349             DrawPosition(FALSE, boards[currentMove]);
7350             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7351             boards[currentMove][toY][toX] = q;
7352             DisplayMessage("Click in holdings to choose piece", "");
7353             return;
7354         }
7355         PromotionPopUp();
7356     } else {
7357         int oldMove = currentMove;
7358         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7359         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7360         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7361         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7362            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7363             DrawPosition(TRUE, boards[currentMove]);
7364         MarkTargetSquares(1);
7365         fromX = fromY = -1;
7366     }
7367     appData.animate = saveAnimate;
7368     if (appData.animate || appData.animateDragging) {
7369         /* Undo animation damage if needed */
7370         DrawPosition(FALSE, NULL);
7371     }
7372 }
7373
7374 int
7375 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7376 {   // front-end-free part taken out of PieceMenuPopup
7377     int whichMenu; int xSqr, ySqr;
7378
7379     if(seekGraphUp) { // [HGM] seekgraph
7380         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7381         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7382         return -2;
7383     }
7384
7385     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7386          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7387         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7388         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7389         if(action == Press)   {
7390             originalFlip = flipView;
7391             flipView = !flipView; // temporarily flip board to see game from partners perspective
7392             DrawPosition(TRUE, partnerBoard);
7393             DisplayMessage(partnerStatus, "");
7394             partnerUp = TRUE;
7395         } else if(action == Release) {
7396             flipView = originalFlip;
7397             DrawPosition(TRUE, boards[currentMove]);
7398             partnerUp = FALSE;
7399         }
7400         return -2;
7401     }
7402
7403     xSqr = EventToSquare(x, BOARD_WIDTH);
7404     ySqr = EventToSquare(y, BOARD_HEIGHT);
7405     if (action == Release) {
7406         if(pieceSweep != EmptySquare) {
7407             EditPositionMenuEvent(pieceSweep, toX, toY);
7408             pieceSweep = EmptySquare;
7409         } else UnLoadPV(); // [HGM] pv
7410     }
7411     if (action != Press) return -2; // return code to be ignored
7412     switch (gameMode) {
7413       case IcsExamining:
7414         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7415       case EditPosition:
7416         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7417         if (xSqr < 0 || ySqr < 0) return -1;
7418         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7419         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7420         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7421         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7422         NextPiece(0);
7423         return 2; // grab
7424       case IcsObserving:
7425         if(!appData.icsEngineAnalyze) return -1;
7426       case IcsPlayingWhite:
7427       case IcsPlayingBlack:
7428         if(!appData.zippyPlay) goto noZip;
7429       case AnalyzeMode:
7430       case AnalyzeFile:
7431       case MachinePlaysWhite:
7432       case MachinePlaysBlack:
7433       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7434         if (!appData.dropMenu) {
7435           LoadPV(x, y);
7436           return 2; // flag front-end to grab mouse events
7437         }
7438         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7439            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7440       case EditGame:
7441       noZip:
7442         if (xSqr < 0 || ySqr < 0) return -1;
7443         if (!appData.dropMenu || appData.testLegality &&
7444             gameInfo.variant != VariantBughouse &&
7445             gameInfo.variant != VariantCrazyhouse) return -1;
7446         whichMenu = 1; // drop menu
7447         break;
7448       default:
7449         return -1;
7450     }
7451
7452     if (((*fromX = xSqr) < 0) ||
7453         ((*fromY = ySqr) < 0)) {
7454         *fromX = *fromY = -1;
7455         return -1;
7456     }
7457     if (flipView)
7458       *fromX = BOARD_WIDTH - 1 - *fromX;
7459     else
7460       *fromY = BOARD_HEIGHT - 1 - *fromY;
7461
7462     return whichMenu;
7463 }
7464
7465 void
7466 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7467 {
7468 //    char * hint = lastHint;
7469     FrontEndProgramStats stats;
7470
7471     stats.which = cps == &first ? 0 : 1;
7472     stats.depth = cpstats->depth;
7473     stats.nodes = cpstats->nodes;
7474     stats.score = cpstats->score;
7475     stats.time = cpstats->time;
7476     stats.pv = cpstats->movelist;
7477     stats.hint = lastHint;
7478     stats.an_move_index = 0;
7479     stats.an_move_count = 0;
7480
7481     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7482         stats.hint = cpstats->move_name;
7483         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7484         stats.an_move_count = cpstats->nr_moves;
7485     }
7486
7487     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
7488
7489     SetProgramStats( &stats );
7490 }
7491
7492 void
7493 ClearEngineOutputPane (int which)
7494 {
7495     static FrontEndProgramStats dummyStats;
7496     dummyStats.which = which;
7497     dummyStats.pv = "#";
7498     SetProgramStats( &dummyStats );
7499 }
7500
7501 #define MAXPLAYERS 500
7502
7503 char *
7504 TourneyStandings (int display)
7505 {
7506     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7507     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7508     char result, *p, *names[MAXPLAYERS];
7509
7510     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7511         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7512     names[0] = p = strdup(appData.participants);
7513     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7514
7515     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7516
7517     while(result = appData.results[nr]) {
7518         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7519         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7520         wScore = bScore = 0;
7521         switch(result) {
7522           case '+': wScore = 2; break;
7523           case '-': bScore = 2; break;
7524           case '=': wScore = bScore = 1; break;
7525           case ' ':
7526           case '*': return strdup("busy"); // tourney not finished
7527         }
7528         score[w] += wScore;
7529         score[b] += bScore;
7530         games[w]++;
7531         games[b]++;
7532         nr++;
7533     }
7534     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7535     for(w=0; w<nPlayers; w++) {
7536         bScore = -1;
7537         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7538         ranking[w] = b; points[w] = bScore; score[b] = -2;
7539     }
7540     p = malloc(nPlayers*34+1);
7541     for(w=0; w<nPlayers && w<display; w++)
7542         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7543     free(names[0]);
7544     return p;
7545 }
7546
7547 void
7548 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7549 {       // count all piece types
7550         int p, f, r;
7551         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7552         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7553         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7554                 p = board[r][f];
7555                 pCnt[p]++;
7556                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7557                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7558                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7559                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7560                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7561                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7562         }
7563 }
7564
7565 int
7566 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7567 {
7568         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7569         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7570
7571         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7572         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7573         if(myPawns == 2 && nMine == 3) // KPP
7574             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7575         if(myPawns == 1 && nMine == 2) // KP
7576             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7577         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7578             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7579         if(myPawns) return FALSE;
7580         if(pCnt[WhiteRook+side])
7581             return pCnt[BlackRook-side] ||
7582                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7583                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7584                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7585         if(pCnt[WhiteCannon+side]) {
7586             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7587             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7588         }
7589         if(pCnt[WhiteKnight+side])
7590             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7591         return FALSE;
7592 }
7593
7594 int
7595 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7596 {
7597         VariantClass v = gameInfo.variant;
7598
7599         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7600         if(v == VariantShatranj) return TRUE; // always winnable through baring
7601         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7602         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7603
7604         if(v == VariantXiangqi) {
7605                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7606
7607                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7608                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7609                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7610                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7611                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7612                 if(stale) // we have at least one last-rank P plus perhaps C
7613                     return majors // KPKX
7614                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7615                 else // KCA*E*
7616                     return pCnt[WhiteFerz+side] // KCAK
7617                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7618                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7619                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7620
7621         } else if(v == VariantKnightmate) {
7622                 if(nMine == 1) return FALSE;
7623                 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
7624         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7625                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7626
7627                 if(nMine == 1) return FALSE; // bare King
7628                 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
7629                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7630                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7631                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7632                 if(pCnt[WhiteKnight+side])
7633                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7634                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7635                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7636                 if(nBishops)
7637                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7638                 if(pCnt[WhiteAlfil+side])
7639                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7640                 if(pCnt[WhiteWazir+side])
7641                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7642         }
7643
7644         return TRUE;
7645 }
7646
7647 int
7648 CompareWithRights (Board b1, Board b2)
7649 {
7650     int rights = 0;
7651     if(!CompareBoards(b1, b2)) return FALSE;
7652     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7653     /* compare castling rights */
7654     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7655            rights++; /* King lost rights, while rook still had them */
7656     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7657         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7658            rights++; /* but at least one rook lost them */
7659     }
7660     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7661            rights++;
7662     if( b1[CASTLING][5] != NoRights ) {
7663         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7664            rights++;
7665     }
7666     return rights == 0;
7667 }
7668
7669 int
7670 Adjudicate (ChessProgramState *cps)
7671 {       // [HGM] some adjudications useful with buggy engines
7672         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7673         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7674         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7675         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7676         int k, drop, count = 0; static int bare = 1;
7677         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7678         Boolean canAdjudicate = !appData.icsActive;
7679
7680         // most tests only when we understand the game, i.e. legality-checking on
7681             if( appData.testLegality )
7682             {   /* [HGM] Some more adjudications for obstinate engines */
7683                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7684                 static int moveCount = 6;
7685                 ChessMove result;
7686                 char *reason = NULL;
7687
7688                 /* Count what is on board. */
7689                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7690
7691                 /* Some material-based adjudications that have to be made before stalemate test */
7692                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7693                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7694                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7695                      if(canAdjudicate && appData.checkMates) {
7696                          if(engineOpponent)
7697                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7698                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7699                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7700                          return 1;
7701                      }
7702                 }
7703
7704                 /* Bare King in Shatranj (loses) or Losers (wins) */
7705                 if( nrW == 1 || nrB == 1) {
7706                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7707                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7708                      if(canAdjudicate && appData.checkMates) {
7709                          if(engineOpponent)
7710                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7711                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7712                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7713                          return 1;
7714                      }
7715                   } else
7716                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7717                   {    /* bare King */
7718                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7719                         if(canAdjudicate && appData.checkMates) {
7720                             /* but only adjudicate if adjudication enabled */
7721                             if(engineOpponent)
7722                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7723                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7724                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7725                             return 1;
7726                         }
7727                   }
7728                 } else bare = 1;
7729
7730
7731             // don't wait for engine to announce game end if we can judge ourselves
7732             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7733               case MT_CHECK:
7734                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7735                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7736                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7737                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7738                             checkCnt++;
7739                         if(checkCnt >= 2) {
7740                             reason = "Xboard adjudication: 3rd check";
7741                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7742                             break;
7743                         }
7744                     }
7745                 }
7746               case MT_NONE:
7747               default:
7748                 break;
7749               case MT_STALEMATE:
7750               case MT_STAINMATE:
7751                 reason = "Xboard adjudication: Stalemate";
7752                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7753                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7754                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7755                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7756                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7757                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7758                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7759                                                                         EP_CHECKMATE : EP_WINS);
7760                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7761                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7762                 }
7763                 break;
7764               case MT_CHECKMATE:
7765                 reason = "Xboard adjudication: Checkmate";
7766                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7767                 break;
7768             }
7769
7770                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7771                     case EP_STALEMATE:
7772                         result = GameIsDrawn; break;
7773                     case EP_CHECKMATE:
7774                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7775                     case EP_WINS:
7776                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7777                     default:
7778                         result = EndOfFile;
7779                 }
7780                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7781                     if(engineOpponent)
7782                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7783                     GameEnds( result, reason, GE_XBOARD );
7784                     return 1;
7785                 }
7786
7787                 /* Next absolutely insufficient mating material. */
7788                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7789                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7790                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7791
7792                      /* always flag draws, for judging claims */
7793                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7794
7795                      if(canAdjudicate && appData.materialDraws) {
7796                          /* but only adjudicate them if adjudication enabled */
7797                          if(engineOpponent) {
7798                            SendToProgram("force\n", engineOpponent); // suppress reply
7799                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7800                          }
7801                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7802                          return 1;
7803                      }
7804                 }
7805
7806                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7807                 if(gameInfo.variant == VariantXiangqi ?
7808                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7809                  : nrW + nrB == 4 &&
7810                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7811                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7812                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7813                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7814                    ) ) {
7815                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7816                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7817                           if(engineOpponent) {
7818                             SendToProgram("force\n", engineOpponent); // suppress reply
7819                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7820                           }
7821                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7822                           return 1;
7823                      }
7824                 } else moveCount = 6;
7825             }
7826
7827         // Repetition draws and 50-move rule can be applied independently of legality testing
7828
7829                 /* Check for rep-draws */
7830                 count = 0;
7831                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
7832                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
7833                 for(k = forwardMostMove-2;
7834                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
7835                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7836                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
7837                     k-=2)
7838                 {   int rights=0;
7839                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7840                         /* compare castling rights */
7841                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7842                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7843                                 rights++; /* King lost rights, while rook still had them */
7844                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7845                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7846                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7847                                    rights++; /* but at least one rook lost them */
7848                         }
7849                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7850                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7851                                 rights++;
7852                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7853                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7854                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7855                                    rights++;
7856                         }
7857                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7858                             && appData.drawRepeats > 1) {
7859                              /* adjudicate after user-specified nr of repeats */
7860                              int result = GameIsDrawn;
7861                              char *details = "XBoard adjudication: repetition draw";
7862                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7863                                 // [HGM] xiangqi: check for forbidden perpetuals
7864                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7865                                 for(m=forwardMostMove; m>k; m-=2) {
7866                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7867                                         ourPerpetual = 0; // the current mover did not always check
7868                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7869                                         hisPerpetual = 0; // the opponent did not always check
7870                                 }
7871                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7872                                                                         ourPerpetual, hisPerpetual);
7873                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7874                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7875                                     details = "Xboard adjudication: perpetual checking";
7876                                 } else
7877                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7878                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7879                                 } else
7880                                 // Now check for perpetual chases
7881                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7882                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7883                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7884                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7885                                         static char resdet[MSG_SIZ];
7886                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7887                                         details = resdet;
7888                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
7889                                     } else
7890                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7891                                         break; // Abort repetition-checking loop.
7892                                 }
7893                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7894                              }
7895                              if(engineOpponent) {
7896                                SendToProgram("force\n", engineOpponent); // suppress reply
7897                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7898                              }
7899                              GameEnds( result, details, GE_XBOARD );
7900                              return 1;
7901                         }
7902                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7903                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7904                     }
7905                 }
7906
7907                 /* Now we test for 50-move draws. Determine ply count */
7908                 count = forwardMostMove;
7909                 /* look for last irreversble move */
7910                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7911                     count--;
7912                 /* if we hit starting position, add initial plies */
7913                 if( count == backwardMostMove )
7914                     count -= initialRulePlies;
7915                 count = forwardMostMove - count;
7916                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7917                         // adjust reversible move counter for checks in Xiangqi
7918                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7919                         if(i < backwardMostMove) i = backwardMostMove;
7920                         while(i <= forwardMostMove) {
7921                                 lastCheck = inCheck; // check evasion does not count
7922                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7923                                 if(inCheck || lastCheck) count--; // check does not count
7924                                 i++;
7925                         }
7926                 }
7927                 if( count >= 100)
7928                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7929                          /* this is used to judge if draw claims are legal */
7930                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7931                          if(engineOpponent) {
7932                            SendToProgram("force\n", engineOpponent); // suppress reply
7933                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7934                          }
7935                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7936                          return 1;
7937                 }
7938
7939                 /* if draw offer is pending, treat it as a draw claim
7940                  * when draw condition present, to allow engines a way to
7941                  * claim draws before making their move to avoid a race
7942                  * condition occurring after their move
7943                  */
7944                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7945                          char *p = NULL;
7946                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7947                              p = "Draw claim: 50-move rule";
7948                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7949                              p = "Draw claim: 3-fold repetition";
7950                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7951                              p = "Draw claim: insufficient mating material";
7952                          if( p != NULL && canAdjudicate) {
7953                              if(engineOpponent) {
7954                                SendToProgram("force\n", engineOpponent); // suppress reply
7955                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7956                              }
7957                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7958                              return 1;
7959                          }
7960                 }
7961
7962                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7963                     if(engineOpponent) {
7964                       SendToProgram("force\n", engineOpponent); // suppress reply
7965                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7966                     }
7967                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7968                     return 1;
7969                 }
7970         return 0;
7971 }
7972
7973 char *
7974 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
7975 {   // [HGM] book: this routine intercepts moves to simulate book replies
7976     char *bookHit = NULL;
7977
7978     //first determine if the incoming move brings opponent into his book
7979     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7980         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7981     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7982     if(bookHit != NULL && !cps->bookSuspend) {
7983         // make sure opponent is not going to reply after receiving move to book position
7984         SendToProgram("force\n", cps);
7985         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7986     }
7987     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7988     // now arrange restart after book miss
7989     if(bookHit) {
7990         // after a book hit we never send 'go', and the code after the call to this routine
7991         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7992         char buf[MSG_SIZ], *move = bookHit;
7993         if(cps->useSAN) {
7994             int fromX, fromY, toX, toY;
7995             char promoChar;
7996             ChessMove moveType;
7997             move = buf + 30;
7998             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
7999                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8000                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8001                                     PosFlags(forwardMostMove),
8002                                     fromY, fromX, toY, toX, promoChar, move);
8003             } else {
8004                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8005                 bookHit = NULL;
8006             }
8007         }
8008         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8009         SendToProgram(buf, cps);
8010         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8011     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8012         SendToProgram("go\n", cps);
8013         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8014     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8015         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8016             SendToProgram("go\n", cps);
8017         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8018     }
8019     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8020 }
8021
8022 int
8023 LoadError (char *errmess, ChessProgramState *cps)
8024 {   // unloads engine and switches back to -ncp mode if it was first
8025     if(cps->initDone) return FALSE;
8026     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8027     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8028     cps->pr = NoProc;
8029     if(cps == &first) {
8030         appData.noChessProgram = TRUE;
8031         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8032         gameMode = BeginningOfGame; ModeHighlight();
8033         SetNCPMode();
8034     }
8035     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8036     DisplayMessage("", ""); // erase waiting message
8037     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8038     return TRUE;
8039 }
8040
8041 char *savedMessage;
8042 ChessProgramState *savedState;
8043 void
8044 DeferredBookMove (void)
8045 {
8046         if(savedState->lastPing != savedState->lastPong)
8047                     ScheduleDelayedEvent(DeferredBookMove, 10);
8048         else
8049         HandleMachineMove(savedMessage, savedState);
8050 }
8051
8052 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8053 static ChessProgramState *stalledEngine;
8054 static char stashedInputMove[MSG_SIZ];
8055
8056 void
8057 HandleMachineMove (char *message, ChessProgramState *cps)
8058 {
8059     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8060     char realname[MSG_SIZ];
8061     int fromX, fromY, toX, toY;
8062     ChessMove moveType;
8063     char promoChar;
8064     char *p, *pv=buf1;
8065     int machineWhite, oldError;
8066     char *bookHit;
8067
8068     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8069         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8070         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8071             DisplayError(_("Invalid pairing from pairing engine"), 0);
8072             return;
8073         }
8074         pairingReceived = 1;
8075         NextMatchGame();
8076         return; // Skim the pairing messages here.
8077     }
8078
8079     oldError = cps->userError; cps->userError = 0;
8080
8081 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8082     /*
8083      * Kludge to ignore BEL characters
8084      */
8085     while (*message == '\007') message++;
8086
8087     /*
8088      * [HGM] engine debug message: ignore lines starting with '#' character
8089      */
8090     if(cps->debug && *message == '#') return;
8091
8092     /*
8093      * Look for book output
8094      */
8095     if (cps == &first && bookRequested) {
8096         if (message[0] == '\t' || message[0] == ' ') {
8097             /* Part of the book output is here; append it */
8098             strcat(bookOutput, message);
8099             strcat(bookOutput, "  \n");
8100             return;
8101         } else if (bookOutput[0] != NULLCHAR) {
8102             /* All of book output has arrived; display it */
8103             char *p = bookOutput;
8104             while (*p != NULLCHAR) {
8105                 if (*p == '\t') *p = ' ';
8106                 p++;
8107             }
8108             DisplayInformation(bookOutput);
8109             bookRequested = FALSE;
8110             /* Fall through to parse the current output */
8111         }
8112     }
8113
8114     /*
8115      * Look for machine move.
8116      */
8117     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8118         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8119     {
8120         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8121             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8122             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8123             stalledEngine = cps;
8124             if(appData.ponderNextMove) { // bring opponent out of ponder
8125                 if(gameMode == TwoMachinesPlay) {
8126                     if(cps->other->pause)
8127                         PauseEngine(cps->other);
8128                     else
8129                         SendToProgram("easy\n", cps->other);
8130                 }
8131             }
8132             StopClocks();
8133             return;
8134         }
8135
8136         /* This method is only useful on engines that support ping */
8137         if (cps->lastPing != cps->lastPong) {
8138           if (gameMode == BeginningOfGame) {
8139             /* Extra move from before last new; ignore */
8140             if (appData.debugMode) {
8141                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8142             }
8143           } else {
8144             if (appData.debugMode) {
8145                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8146                         cps->which, gameMode);
8147             }
8148
8149             SendToProgram("undo\n", cps);
8150           }
8151           return;
8152         }
8153
8154         switch (gameMode) {
8155           case BeginningOfGame:
8156             /* Extra move from before last reset; ignore */
8157             if (appData.debugMode) {
8158                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8159             }
8160             return;
8161
8162           case EndOfGame:
8163           case IcsIdle:
8164           default:
8165             /* Extra move after we tried to stop.  The mode test is
8166                not a reliable way of detecting this problem, but it's
8167                the best we can do on engines that don't support ping.
8168             */
8169             if (appData.debugMode) {
8170                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8171                         cps->which, gameMode);
8172             }
8173             SendToProgram("undo\n", cps);
8174             return;
8175
8176           case MachinePlaysWhite:
8177           case IcsPlayingWhite:
8178             machineWhite = TRUE;
8179             break;
8180
8181           case MachinePlaysBlack:
8182           case IcsPlayingBlack:
8183             machineWhite = FALSE;
8184             break;
8185
8186           case TwoMachinesPlay:
8187             machineWhite = (cps->twoMachinesColor[0] == 'w');
8188             break;
8189         }
8190         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8191             if (appData.debugMode) {
8192                 fprintf(debugFP,
8193                         "Ignoring move out of turn by %s, gameMode %d"
8194                         ", forwardMost %d\n",
8195                         cps->which, gameMode, forwardMostMove);
8196             }
8197             return;
8198         }
8199
8200         if(cps->alphaRank) AlphaRank(machineMove, 4);
8201         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8202                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8203             /* Machine move could not be parsed; ignore it. */
8204           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8205                     machineMove, _(cps->which));
8206             DisplayError(buf1, 0);
8207             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
8208                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
8209             if (gameMode == TwoMachinesPlay) {
8210               GameEnds(machineWhite ? BlackWins : WhiteWins,
8211                        buf1, GE_XBOARD);
8212             }
8213             return;
8214         }
8215
8216         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8217         /* So we have to redo legality test with true e.p. status here,  */
8218         /* to make sure an illegal e.p. capture does not slip through,   */
8219         /* to cause a forfeit on a justified illegal-move complaint      */
8220         /* of the opponent.                                              */
8221         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8222            ChessMove moveType;
8223            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8224                              fromY, fromX, toY, toX, promoChar);
8225             if(moveType == IllegalMove) {
8226               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8227                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8228                 GameEnds(machineWhite ? BlackWins : WhiteWins,
8229                            buf1, GE_XBOARD);
8230                 return;
8231            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
8232            /* [HGM] Kludge to handle engines that send FRC-style castling
8233               when they shouldn't (like TSCP-Gothic) */
8234            switch(moveType) {
8235              case WhiteASideCastleFR:
8236              case BlackASideCastleFR:
8237                toX+=2;
8238                currentMoveString[2]++;
8239                break;
8240              case WhiteHSideCastleFR:
8241              case BlackHSideCastleFR:
8242                toX--;
8243                currentMoveString[2]--;
8244                break;
8245              default: ; // nothing to do, but suppresses warning of pedantic compilers
8246            }
8247         }
8248         hintRequested = FALSE;
8249         lastHint[0] = NULLCHAR;
8250         bookRequested = FALSE;
8251         /* Program may be pondering now */
8252         cps->maybeThinking = TRUE;
8253         if (cps->sendTime == 2) cps->sendTime = 1;
8254         if (cps->offeredDraw) cps->offeredDraw--;
8255
8256         /* [AS] Save move info*/
8257         pvInfoList[ forwardMostMove ].score = programStats.score;
8258         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8259         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8260
8261         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8262
8263         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8264         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8265             int count = 0;
8266
8267             while( count < adjudicateLossPlies ) {
8268                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8269
8270                 if( count & 1 ) {
8271                     score = -score; /* Flip score for winning side */
8272                 }
8273
8274                 if( score > adjudicateLossThreshold ) {
8275                     break;
8276                 }
8277
8278                 count++;
8279             }
8280
8281             if( count >= adjudicateLossPlies ) {
8282                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8283
8284                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8285                     "Xboard adjudication",
8286                     GE_XBOARD );
8287
8288                 return;
8289             }
8290         }
8291
8292         if(Adjudicate(cps)) {
8293             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8294             return; // [HGM] adjudicate: for all automatic game ends
8295         }
8296
8297 #if ZIPPY
8298         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8299             first.initDone) {
8300           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8301                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8302                 SendToICS("draw ");
8303                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8304           }
8305           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8306           ics_user_moved = 1;
8307           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8308                 char buf[3*MSG_SIZ];
8309
8310                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8311                         programStats.score / 100.,
8312                         programStats.depth,
8313                         programStats.time / 100.,
8314                         (unsigned int)programStats.nodes,
8315                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8316                         programStats.movelist);
8317                 SendToICS(buf);
8318 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8319           }
8320         }
8321 #endif
8322
8323         /* [AS] Clear stats for next move */
8324         ClearProgramStats();
8325         thinkOutput[0] = NULLCHAR;
8326         hiddenThinkOutputState = 0;
8327
8328         bookHit = NULL;
8329         if (gameMode == TwoMachinesPlay) {
8330             /* [HGM] relaying draw offers moved to after reception of move */
8331             /* and interpreting offer as claim if it brings draw condition */
8332             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8333                 SendToProgram("draw\n", cps->other);
8334             }
8335             if (cps->other->sendTime) {
8336                 SendTimeRemaining(cps->other,
8337                                   cps->other->twoMachinesColor[0] == 'w');
8338             }
8339             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8340             if (firstMove && !bookHit) {
8341                 firstMove = FALSE;
8342                 if (cps->other->useColors) {
8343                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8344                 }
8345                 SendToProgram("go\n", cps->other);
8346             }
8347             cps->other->maybeThinking = TRUE;
8348         }
8349
8350         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8351
8352         if (!pausing && appData.ringBellAfterMoves) {
8353             RingBell();
8354         }
8355
8356         /*
8357          * Reenable menu items that were disabled while
8358          * machine was thinking
8359          */
8360         if (gameMode != TwoMachinesPlay)
8361             SetUserThinkingEnables();
8362
8363         // [HGM] book: after book hit opponent has received move and is now in force mode
8364         // force the book reply into it, and then fake that it outputted this move by jumping
8365         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8366         if(bookHit) {
8367                 static char bookMove[MSG_SIZ]; // a bit generous?
8368
8369                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8370                 strcat(bookMove, bookHit);
8371                 message = bookMove;
8372                 cps = cps->other;
8373                 programStats.nodes = programStats.depth = programStats.time =
8374                 programStats.score = programStats.got_only_move = 0;
8375                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8376
8377                 if(cps->lastPing != cps->lastPong) {
8378                     savedMessage = message; // args for deferred call
8379                     savedState = cps;
8380                     ScheduleDelayedEvent(DeferredBookMove, 10);
8381                     return;
8382                 }
8383                 goto FakeBookMove;
8384         }
8385
8386         return;
8387     }
8388
8389     /* Set special modes for chess engines.  Later something general
8390      *  could be added here; for now there is just one kludge feature,
8391      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8392      *  when "xboard" is given as an interactive command.
8393      */
8394     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8395         cps->useSigint = FALSE;
8396         cps->useSigterm = FALSE;
8397     }
8398     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8399       ParseFeatures(message+8, cps);
8400       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8401     }
8402
8403     if ((!appData.testLegality || gameInfo.variant == VariantFairy) &&
8404                                         !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
8405       int dummy, s=6; char buf[MSG_SIZ];
8406       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8407       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8408       if(startedFromSetupPosition) return;
8409       ParseFEN(boards[0], &dummy, message+s);
8410       DrawPosition(TRUE, boards[0]);
8411       startedFromSetupPosition = TRUE;
8412       return;
8413     }
8414     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8415      * want this, I was asked to put it in, and obliged.
8416      */
8417     if (!strncmp(message, "setboard ", 9)) {
8418         Board initial_position;
8419
8420         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8421
8422         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8423             DisplayError(_("Bad FEN received from engine"), 0);
8424             return ;
8425         } else {
8426            Reset(TRUE, FALSE);
8427            CopyBoard(boards[0], initial_position);
8428            initialRulePlies = FENrulePlies;
8429            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8430            else gameMode = MachinePlaysBlack;
8431            DrawPosition(FALSE, boards[currentMove]);
8432         }
8433         return;
8434     }
8435
8436     /*
8437      * Look for communication commands
8438      */
8439     if (!strncmp(message, "telluser ", 9)) {
8440         if(message[9] == '\\' && message[10] == '\\')
8441             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8442         PlayTellSound();
8443         DisplayNote(message + 9);
8444         return;
8445     }
8446     if (!strncmp(message, "tellusererror ", 14)) {
8447         cps->userError = 1;
8448         if(message[14] == '\\' && message[15] == '\\')
8449             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8450         PlayTellSound();
8451         DisplayError(message + 14, 0);
8452         return;
8453     }
8454     if (!strncmp(message, "tellopponent ", 13)) {
8455       if (appData.icsActive) {
8456         if (loggedOn) {
8457           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8458           SendToICS(buf1);
8459         }
8460       } else {
8461         DisplayNote(message + 13);
8462       }
8463       return;
8464     }
8465     if (!strncmp(message, "tellothers ", 11)) {
8466       if (appData.icsActive) {
8467         if (loggedOn) {
8468           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8469           SendToICS(buf1);
8470         }
8471       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
8472       return;
8473     }
8474     if (!strncmp(message, "tellall ", 8)) {
8475       if (appData.icsActive) {
8476         if (loggedOn) {
8477           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8478           SendToICS(buf1);
8479         }
8480       } else {
8481         DisplayNote(message + 8);
8482       }
8483       return;
8484     }
8485     if (strncmp(message, "warning", 7) == 0) {
8486         /* Undocumented feature, use tellusererror in new code */
8487         DisplayError(message, 0);
8488         return;
8489     }
8490     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8491         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8492         strcat(realname, " query");
8493         AskQuestion(realname, buf2, buf1, cps->pr);
8494         return;
8495     }
8496     /* Commands from the engine directly to ICS.  We don't allow these to be
8497      *  sent until we are logged on. Crafty kibitzes have been known to
8498      *  interfere with the login process.
8499      */
8500     if (loggedOn) {
8501         if (!strncmp(message, "tellics ", 8)) {
8502             SendToICS(message + 8);
8503             SendToICS("\n");
8504             return;
8505         }
8506         if (!strncmp(message, "tellicsnoalias ", 15)) {
8507             SendToICS(ics_prefix);
8508             SendToICS(message + 15);
8509             SendToICS("\n");
8510             return;
8511         }
8512         /* The following are for backward compatibility only */
8513         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8514             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8515             SendToICS(ics_prefix);
8516             SendToICS(message);
8517             SendToICS("\n");
8518             return;
8519         }
8520     }
8521     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8522         return;
8523     }
8524     /*
8525      * If the move is illegal, cancel it and redraw the board.
8526      * Also deal with other error cases.  Matching is rather loose
8527      * here to accommodate engines written before the spec.
8528      */
8529     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8530         strncmp(message, "Error", 5) == 0) {
8531         if (StrStr(message, "name") ||
8532             StrStr(message, "rating") || StrStr(message, "?") ||
8533             StrStr(message, "result") || StrStr(message, "board") ||
8534             StrStr(message, "bk") || StrStr(message, "computer") ||
8535             StrStr(message, "variant") || StrStr(message, "hint") ||
8536             StrStr(message, "random") || StrStr(message, "depth") ||
8537             StrStr(message, "accepted")) {
8538             return;
8539         }
8540         if (StrStr(message, "protover")) {
8541           /* Program is responding to input, so it's apparently done
8542              initializing, and this error message indicates it is
8543              protocol version 1.  So we don't need to wait any longer
8544              for it to initialize and send feature commands. */
8545           FeatureDone(cps, 1);
8546           cps->protocolVersion = 1;
8547           return;
8548         }
8549         cps->maybeThinking = FALSE;
8550
8551         if (StrStr(message, "draw")) {
8552             /* Program doesn't have "draw" command */
8553             cps->sendDrawOffers = 0;
8554             return;
8555         }
8556         if (cps->sendTime != 1 &&
8557             (StrStr(message, "time") || StrStr(message, "otim"))) {
8558           /* Program apparently doesn't have "time" or "otim" command */
8559           cps->sendTime = 0;
8560           return;
8561         }
8562         if (StrStr(message, "analyze")) {
8563             cps->analysisSupport = FALSE;
8564             cps->analyzing = FALSE;
8565 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8566             EditGameEvent(); // [HGM] try to preserve loaded game
8567             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8568             DisplayError(buf2, 0);
8569             return;
8570         }
8571         if (StrStr(message, "(no matching move)st")) {
8572           /* Special kludge for GNU Chess 4 only */
8573           cps->stKludge = TRUE;
8574           SendTimeControl(cps, movesPerSession, timeControl,
8575                           timeIncrement, appData.searchDepth,
8576                           searchTime);
8577           return;
8578         }
8579         if (StrStr(message, "(no matching move)sd")) {
8580           /* Special kludge for GNU Chess 4 only */
8581           cps->sdKludge = TRUE;
8582           SendTimeControl(cps, movesPerSession, timeControl,
8583                           timeIncrement, appData.searchDepth,
8584                           searchTime);
8585           return;
8586         }
8587         if (!StrStr(message, "llegal")) {
8588             return;
8589         }
8590         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8591             gameMode == IcsIdle) return;
8592         if (forwardMostMove <= backwardMostMove) return;
8593         if (pausing) PauseEvent();
8594       if(appData.forceIllegal) {
8595             // [HGM] illegal: machine refused move; force position after move into it
8596           SendToProgram("force\n", cps);
8597           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8598                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8599                 // when black is to move, while there might be nothing on a2 or black
8600                 // might already have the move. So send the board as if white has the move.
8601                 // But first we must change the stm of the engine, as it refused the last move
8602                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8603                 if(WhiteOnMove(forwardMostMove)) {
8604                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8605                     SendBoard(cps, forwardMostMove); // kludgeless board
8606                 } else {
8607                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8608                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8609                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8610                 }
8611           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8612             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8613                  gameMode == TwoMachinesPlay)
8614               SendToProgram("go\n", cps);
8615             return;
8616       } else
8617         if (gameMode == PlayFromGameFile) {
8618             /* Stop reading this game file */
8619             gameMode = EditGame;
8620             ModeHighlight();
8621         }
8622         /* [HGM] illegal-move claim should forfeit game when Xboard */
8623         /* only passes fully legal moves                            */
8624         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8625             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8626                                 "False illegal-move claim", GE_XBOARD );
8627             return; // do not take back move we tested as valid
8628         }
8629         currentMove = forwardMostMove-1;
8630         DisplayMove(currentMove-1); /* before DisplayMoveError */
8631         SwitchClocks(forwardMostMove-1); // [HGM] race
8632         DisplayBothClocks();
8633         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8634                 parseList[currentMove], _(cps->which));
8635         DisplayMoveError(buf1);
8636         DrawPosition(FALSE, boards[currentMove]);
8637
8638         SetUserThinkingEnables();
8639         return;
8640     }
8641     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8642         /* Program has a broken "time" command that
8643            outputs a string not ending in newline.
8644            Don't use it. */
8645         cps->sendTime = 0;
8646     }
8647
8648     /*
8649      * If chess program startup fails, exit with an error message.
8650      * Attempts to recover here are futile. [HGM] Well, we try anyway
8651      */
8652     if ((StrStr(message, "unknown host") != NULL)
8653         || (StrStr(message, "No remote directory") != NULL)
8654         || (StrStr(message, "not found") != NULL)
8655         || (StrStr(message, "No such file") != NULL)
8656         || (StrStr(message, "can't alloc") != NULL)
8657         || (StrStr(message, "Permission denied") != NULL)) {
8658
8659         cps->maybeThinking = FALSE;
8660         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8661                 _(cps->which), cps->program, cps->host, message);
8662         RemoveInputSource(cps->isr);
8663         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8664             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
8665             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
8666         }
8667         return;
8668     }
8669
8670     /*
8671      * Look for hint output
8672      */
8673     if (sscanf(message, "Hint: %s", buf1) == 1) {
8674         if (cps == &first && hintRequested) {
8675             hintRequested = FALSE;
8676             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8677                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8678                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8679                                     PosFlags(forwardMostMove),
8680                                     fromY, fromX, toY, toX, promoChar, buf1);
8681                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8682                 DisplayInformation(buf2);
8683             } else {
8684                 /* Hint move could not be parsed!? */
8685               snprintf(buf2, sizeof(buf2),
8686                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8687                         buf1, _(cps->which));
8688                 DisplayError(buf2, 0);
8689             }
8690         } else {
8691           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8692         }
8693         return;
8694     }
8695
8696     /*
8697      * Ignore other messages if game is not in progress
8698      */
8699     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8700         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8701
8702     /*
8703      * look for win, lose, draw, or draw offer
8704      */
8705     if (strncmp(message, "1-0", 3) == 0) {
8706         char *p, *q, *r = "";
8707         p = strchr(message, '{');
8708         if (p) {
8709             q = strchr(p, '}');
8710             if (q) {
8711                 *q = NULLCHAR;
8712                 r = p + 1;
8713             }
8714         }
8715         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8716         return;
8717     } else if (strncmp(message, "0-1", 3) == 0) {
8718         char *p, *q, *r = "";
8719         p = strchr(message, '{');
8720         if (p) {
8721             q = strchr(p, '}');
8722             if (q) {
8723                 *q = NULLCHAR;
8724                 r = p + 1;
8725             }
8726         }
8727         /* Kludge for Arasan 4.1 bug */
8728         if (strcmp(r, "Black resigns") == 0) {
8729             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8730             return;
8731         }
8732         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8733         return;
8734     } else if (strncmp(message, "1/2", 3) == 0) {
8735         char *p, *q, *r = "";
8736         p = strchr(message, '{');
8737         if (p) {
8738             q = strchr(p, '}');
8739             if (q) {
8740                 *q = NULLCHAR;
8741                 r = p + 1;
8742             }
8743         }
8744
8745         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8746         return;
8747
8748     } else if (strncmp(message, "White resign", 12) == 0) {
8749         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8750         return;
8751     } else if (strncmp(message, "Black resign", 12) == 0) {
8752         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8753         return;
8754     } else if (strncmp(message, "White matches", 13) == 0 ||
8755                strncmp(message, "Black matches", 13) == 0   ) {
8756         /* [HGM] ignore GNUShogi noises */
8757         return;
8758     } else if (strncmp(message, "White", 5) == 0 &&
8759                message[5] != '(' &&
8760                StrStr(message, "Black") == NULL) {
8761         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8762         return;
8763     } else if (strncmp(message, "Black", 5) == 0 &&
8764                message[5] != '(') {
8765         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8766         return;
8767     } else if (strcmp(message, "resign") == 0 ||
8768                strcmp(message, "computer resigns") == 0) {
8769         switch (gameMode) {
8770           case MachinePlaysBlack:
8771           case IcsPlayingBlack:
8772             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8773             break;
8774           case MachinePlaysWhite:
8775           case IcsPlayingWhite:
8776             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8777             break;
8778           case TwoMachinesPlay:
8779             if (cps->twoMachinesColor[0] == 'w')
8780               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8781             else
8782               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8783             break;
8784           default:
8785             /* can't happen */
8786             break;
8787         }
8788         return;
8789     } else if (strncmp(message, "opponent mates", 14) == 0) {
8790         switch (gameMode) {
8791           case MachinePlaysBlack:
8792           case IcsPlayingBlack:
8793             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8794             break;
8795           case MachinePlaysWhite:
8796           case IcsPlayingWhite:
8797             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8798             break;
8799           case TwoMachinesPlay:
8800             if (cps->twoMachinesColor[0] == 'w')
8801               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8802             else
8803               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8804             break;
8805           default:
8806             /* can't happen */
8807             break;
8808         }
8809         return;
8810     } else if (strncmp(message, "computer mates", 14) == 0) {
8811         switch (gameMode) {
8812           case MachinePlaysBlack:
8813           case IcsPlayingBlack:
8814             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8815             break;
8816           case MachinePlaysWhite:
8817           case IcsPlayingWhite:
8818             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8819             break;
8820           case TwoMachinesPlay:
8821             if (cps->twoMachinesColor[0] == 'w')
8822               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8823             else
8824               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8825             break;
8826           default:
8827             /* can't happen */
8828             break;
8829         }
8830         return;
8831     } else if (strncmp(message, "checkmate", 9) == 0) {
8832         if (WhiteOnMove(forwardMostMove)) {
8833             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8834         } else {
8835             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8836         }
8837         return;
8838     } else if (strstr(message, "Draw") != NULL ||
8839                strstr(message, "game is a draw") != NULL) {
8840         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8841         return;
8842     } else if (strstr(message, "offer") != NULL &&
8843                strstr(message, "draw") != NULL) {
8844 #if ZIPPY
8845         if (appData.zippyPlay && first.initDone) {
8846             /* Relay offer to ICS */
8847             SendToICS(ics_prefix);
8848             SendToICS("draw\n");
8849         }
8850 #endif
8851         cps->offeredDraw = 2; /* valid until this engine moves twice */
8852         if (gameMode == TwoMachinesPlay) {
8853             if (cps->other->offeredDraw) {
8854                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8855             /* [HGM] in two-machine mode we delay relaying draw offer      */
8856             /* until after we also have move, to see if it is really claim */
8857             }
8858         } else if (gameMode == MachinePlaysWhite ||
8859                    gameMode == MachinePlaysBlack) {
8860           if (userOfferedDraw) {
8861             DisplayInformation(_("Machine accepts your draw offer"));
8862             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8863           } else {
8864             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8865           }
8866         }
8867     }
8868
8869
8870     /*
8871      * Look for thinking output
8872      */
8873     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8874           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8875                                 ) {
8876         int plylev, mvleft, mvtot, curscore, time;
8877         char mvname[MOVE_LEN];
8878         u64 nodes; // [DM]
8879         char plyext;
8880         int ignore = FALSE;
8881         int prefixHint = FALSE;
8882         mvname[0] = NULLCHAR;
8883
8884         switch (gameMode) {
8885           case MachinePlaysBlack:
8886           case IcsPlayingBlack:
8887             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8888             break;
8889           case MachinePlaysWhite:
8890           case IcsPlayingWhite:
8891             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8892             break;
8893           case AnalyzeMode:
8894           case AnalyzeFile:
8895             break;
8896           case IcsObserving: /* [DM] icsEngineAnalyze */
8897             if (!appData.icsEngineAnalyze) ignore = TRUE;
8898             break;
8899           case TwoMachinesPlay:
8900             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8901                 ignore = TRUE;
8902             }
8903             break;
8904           default:
8905             ignore = TRUE;
8906             break;
8907         }
8908
8909         if (!ignore) {
8910             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8911             buf1[0] = NULLCHAR;
8912             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8913                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8914
8915                 if (plyext != ' ' && plyext != '\t') {
8916                     time *= 100;
8917                 }
8918
8919                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8920                 if( cps->scoreIsAbsolute &&
8921                     ( gameMode == MachinePlaysBlack ||
8922                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8923                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8924                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8925                      !WhiteOnMove(currentMove)
8926                     ) )
8927                 {
8928                     curscore = -curscore;
8929                 }
8930
8931                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
8932
8933                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
8934                         char buf[MSG_SIZ];
8935                         FILE *f;
8936                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
8937                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
8938                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
8939                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
8940                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
8941                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
8942                                 fclose(f);
8943                         } else DisplayError(_("failed writing PV"), 0);
8944                 }
8945
8946                 tempStats.depth = plylev;
8947                 tempStats.nodes = nodes;
8948                 tempStats.time = time;
8949                 tempStats.score = curscore;
8950                 tempStats.got_only_move = 0;
8951
8952                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8953                         int ticklen;
8954
8955                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8956                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8957                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8958                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8959                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8960                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8961                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8962                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8963                 }
8964
8965                 /* Buffer overflow protection */
8966                 if (pv[0] != NULLCHAR) {
8967                     if (strlen(pv) >= sizeof(tempStats.movelist)
8968                         && appData.debugMode) {
8969                         fprintf(debugFP,
8970                                 "PV is too long; using the first %u bytes.\n",
8971                                 (unsigned) sizeof(tempStats.movelist) - 1);
8972                     }
8973
8974                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8975                 } else {
8976                     sprintf(tempStats.movelist, " no PV\n");
8977                 }
8978
8979                 if (tempStats.seen_stat) {
8980                     tempStats.ok_to_send = 1;
8981                 }
8982
8983                 if (strchr(tempStats.movelist, '(') != NULL) {
8984                     tempStats.line_is_book = 1;
8985                     tempStats.nr_moves = 0;
8986                     tempStats.moves_left = 0;
8987                 } else {
8988                     tempStats.line_is_book = 0;
8989                 }
8990
8991                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8992                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8993
8994                 SendProgramStatsToFrontend( cps, &tempStats );
8995
8996                 /*
8997                     [AS] Protect the thinkOutput buffer from overflow... this
8998                     is only useful if buf1 hasn't overflowed first!
8999                 */
9000                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
9001                          plylev,
9002                          (gameMode == TwoMachinesPlay ?
9003                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9004                          ((double) curscore) / 100.0,
9005                          prefixHint ? lastHint : "",
9006                          prefixHint ? " " : "" );
9007
9008                 if( buf1[0] != NULLCHAR ) {
9009                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9010
9011                     if( strlen(pv) > max_len ) {
9012                         if( appData.debugMode) {
9013                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9014                         }
9015                         pv[max_len+1] = '\0';
9016                     }
9017
9018                     strcat( thinkOutput, pv);
9019                 }
9020
9021                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9022                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9023                     DisplayMove(currentMove - 1);
9024                 }
9025                 return;
9026
9027             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9028                 /* crafty (9.25+) says "(only move) <move>"
9029                  * if there is only 1 legal move
9030                  */
9031                 sscanf(p, "(only move) %s", buf1);
9032                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9033                 sprintf(programStats.movelist, "%s (only move)", buf1);
9034                 programStats.depth = 1;
9035                 programStats.nr_moves = 1;
9036                 programStats.moves_left = 1;
9037                 programStats.nodes = 1;
9038                 programStats.time = 1;
9039                 programStats.got_only_move = 1;
9040
9041                 /* Not really, but we also use this member to
9042                    mean "line isn't going to change" (Crafty
9043                    isn't searching, so stats won't change) */
9044                 programStats.line_is_book = 1;
9045
9046                 SendProgramStatsToFrontend( cps, &programStats );
9047
9048                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9049                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9050                     DisplayMove(currentMove - 1);
9051                 }
9052                 return;
9053             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9054                               &time, &nodes, &plylev, &mvleft,
9055                               &mvtot, mvname) >= 5) {
9056                 /* The stat01: line is from Crafty (9.29+) in response
9057                    to the "." command */
9058                 programStats.seen_stat = 1;
9059                 cps->maybeThinking = TRUE;
9060
9061                 if (programStats.got_only_move || !appData.periodicUpdates)
9062                   return;
9063
9064                 programStats.depth = plylev;
9065                 programStats.time = time;
9066                 programStats.nodes = nodes;
9067                 programStats.moves_left = mvleft;
9068                 programStats.nr_moves = mvtot;
9069                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9070                 programStats.ok_to_send = 1;
9071                 programStats.movelist[0] = '\0';
9072
9073                 SendProgramStatsToFrontend( cps, &programStats );
9074
9075                 return;
9076
9077             } else if (strncmp(message,"++",2) == 0) {
9078                 /* Crafty 9.29+ outputs this */
9079                 programStats.got_fail = 2;
9080                 return;
9081
9082             } else if (strncmp(message,"--",2) == 0) {
9083                 /* Crafty 9.29+ outputs this */
9084                 programStats.got_fail = 1;
9085                 return;
9086
9087             } else if (thinkOutput[0] != NULLCHAR &&
9088                        strncmp(message, "    ", 4) == 0) {
9089                 unsigned message_len;
9090
9091                 p = message;
9092                 while (*p && *p == ' ') p++;
9093
9094                 message_len = strlen( p );
9095
9096                 /* [AS] Avoid buffer overflow */
9097                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9098                     strcat(thinkOutput, " ");
9099                     strcat(thinkOutput, p);
9100                 }
9101
9102                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9103                     strcat(programStats.movelist, " ");
9104                     strcat(programStats.movelist, p);
9105                 }
9106
9107                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9108                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9109                     DisplayMove(currentMove - 1);
9110                 }
9111                 return;
9112             }
9113         }
9114         else {
9115             buf1[0] = NULLCHAR;
9116
9117             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9118                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9119             {
9120                 ChessProgramStats cpstats;
9121
9122                 if (plyext != ' ' && plyext != '\t') {
9123                     time *= 100;
9124                 }
9125
9126                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9127                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9128                     curscore = -curscore;
9129                 }
9130
9131                 cpstats.depth = plylev;
9132                 cpstats.nodes = nodes;
9133                 cpstats.time = time;
9134                 cpstats.score = curscore;
9135                 cpstats.got_only_move = 0;
9136                 cpstats.movelist[0] = '\0';
9137
9138                 if (buf1[0] != NULLCHAR) {
9139                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9140                 }
9141
9142                 cpstats.ok_to_send = 0;
9143                 cpstats.line_is_book = 0;
9144                 cpstats.nr_moves = 0;
9145                 cpstats.moves_left = 0;
9146
9147                 SendProgramStatsToFrontend( cps, &cpstats );
9148             }
9149         }
9150     }
9151 }
9152
9153
9154 /* Parse a game score from the character string "game", and
9155    record it as the history of the current game.  The game
9156    score is NOT assumed to start from the standard position.
9157    The display is not updated in any way.
9158    */
9159 void
9160 ParseGameHistory (char *game)
9161 {
9162     ChessMove moveType;
9163     int fromX, fromY, toX, toY, boardIndex;
9164     char promoChar;
9165     char *p, *q;
9166     char buf[MSG_SIZ];
9167
9168     if (appData.debugMode)
9169       fprintf(debugFP, "Parsing game history: %s\n", game);
9170
9171     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9172     gameInfo.site = StrSave(appData.icsHost);
9173     gameInfo.date = PGNDate();
9174     gameInfo.round = StrSave("-");
9175
9176     /* Parse out names of players */
9177     while (*game == ' ') game++;
9178     p = buf;
9179     while (*game != ' ') *p++ = *game++;
9180     *p = NULLCHAR;
9181     gameInfo.white = StrSave(buf);
9182     while (*game == ' ') game++;
9183     p = buf;
9184     while (*game != ' ' && *game != '\n') *p++ = *game++;
9185     *p = NULLCHAR;
9186     gameInfo.black = StrSave(buf);
9187
9188     /* Parse moves */
9189     boardIndex = blackPlaysFirst ? 1 : 0;
9190     yynewstr(game);
9191     for (;;) {
9192         yyboardindex = boardIndex;
9193         moveType = (ChessMove) Myylex();
9194         switch (moveType) {
9195           case IllegalMove:             /* maybe suicide chess, etc. */
9196   if (appData.debugMode) {
9197     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9198     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9199     setbuf(debugFP, NULL);
9200   }
9201           case WhitePromotion:
9202           case BlackPromotion:
9203           case WhiteNonPromotion:
9204           case BlackNonPromotion:
9205           case NormalMove:
9206           case WhiteCapturesEnPassant:
9207           case BlackCapturesEnPassant:
9208           case WhiteKingSideCastle:
9209           case WhiteQueenSideCastle:
9210           case BlackKingSideCastle:
9211           case BlackQueenSideCastle:
9212           case WhiteKingSideCastleWild:
9213           case WhiteQueenSideCastleWild:
9214           case BlackKingSideCastleWild:
9215           case BlackQueenSideCastleWild:
9216           /* PUSH Fabien */
9217           case WhiteHSideCastleFR:
9218           case WhiteASideCastleFR:
9219           case BlackHSideCastleFR:
9220           case BlackASideCastleFR:
9221           /* POP Fabien */
9222             fromX = currentMoveString[0] - AAA;
9223             fromY = currentMoveString[1] - ONE;
9224             toX = currentMoveString[2] - AAA;
9225             toY = currentMoveString[3] - ONE;
9226             promoChar = currentMoveString[4];
9227             break;
9228           case WhiteDrop:
9229           case BlackDrop:
9230             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9231             fromX = moveType == WhiteDrop ?
9232               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9233             (int) CharToPiece(ToLower(currentMoveString[0]));
9234             fromY = DROP_RANK;
9235             toX = currentMoveString[2] - AAA;
9236             toY = currentMoveString[3] - ONE;
9237             promoChar = NULLCHAR;
9238             break;
9239           case AmbiguousMove:
9240             /* bug? */
9241             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9242   if (appData.debugMode) {
9243     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9244     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9245     setbuf(debugFP, NULL);
9246   }
9247             DisplayError(buf, 0);
9248             return;
9249           case ImpossibleMove:
9250             /* bug? */
9251             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9252   if (appData.debugMode) {
9253     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9254     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9255     setbuf(debugFP, NULL);
9256   }
9257             DisplayError(buf, 0);
9258             return;
9259           case EndOfFile:
9260             if (boardIndex < backwardMostMove) {
9261                 /* Oops, gap.  How did that happen? */
9262                 DisplayError(_("Gap in move list"), 0);
9263                 return;
9264             }
9265             backwardMostMove =  blackPlaysFirst ? 1 : 0;
9266             if (boardIndex > forwardMostMove) {
9267                 forwardMostMove = boardIndex;
9268             }
9269             return;
9270           case ElapsedTime:
9271             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9272                 strcat(parseList[boardIndex-1], " ");
9273                 strcat(parseList[boardIndex-1], yy_text);
9274             }
9275             continue;
9276           case Comment:
9277           case PGNTag:
9278           case NAG:
9279           default:
9280             /* ignore */
9281             continue;
9282           case WhiteWins:
9283           case BlackWins:
9284           case GameIsDrawn:
9285           case GameUnfinished:
9286             if (gameMode == IcsExamining) {
9287                 if (boardIndex < backwardMostMove) {
9288                     /* Oops, gap.  How did that happen? */
9289                     return;
9290                 }
9291                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9292                 return;
9293             }
9294             gameInfo.result = moveType;
9295             p = strchr(yy_text, '{');
9296             if (p == NULL) p = strchr(yy_text, '(');
9297             if (p == NULL) {
9298                 p = yy_text;
9299                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9300             } else {
9301                 q = strchr(p, *p == '{' ? '}' : ')');
9302                 if (q != NULL) *q = NULLCHAR;
9303                 p++;
9304             }
9305             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9306             gameInfo.resultDetails = StrSave(p);
9307             continue;
9308         }
9309         if (boardIndex >= forwardMostMove &&
9310             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9311             backwardMostMove = blackPlaysFirst ? 1 : 0;
9312             return;
9313         }
9314         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9315                                  fromY, fromX, toY, toX, promoChar,
9316                                  parseList[boardIndex]);
9317         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9318         /* currentMoveString is set as a side-effect of yylex */
9319         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9320         strcat(moveList[boardIndex], "\n");
9321         boardIndex++;
9322         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9323         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9324           case MT_NONE:
9325           case MT_STALEMATE:
9326           default:
9327             break;
9328           case MT_CHECK:
9329             if(gameInfo.variant != VariantShogi)
9330                 strcat(parseList[boardIndex - 1], "+");
9331             break;
9332           case MT_CHECKMATE:
9333           case MT_STAINMATE:
9334             strcat(parseList[boardIndex - 1], "#");
9335             break;
9336         }
9337     }
9338 }
9339
9340
9341 /* Apply a move to the given board  */
9342 void
9343 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9344 {
9345   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9346   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9347
9348     /* [HGM] compute & store e.p. status and castling rights for new position */
9349     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9350
9351       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9352       oldEP = (signed char)board[EP_STATUS];
9353       board[EP_STATUS] = EP_NONE;
9354
9355   if (fromY == DROP_RANK) {
9356         /* must be first */
9357         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9358             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9359             return;
9360         }
9361         piece = board[toY][toX] = (ChessSquare) fromX;
9362   } else {
9363       int i;
9364
9365       if( board[toY][toX] != EmptySquare )
9366            board[EP_STATUS] = EP_CAPTURE;
9367
9368       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9369            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9370                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9371       } else
9372       if( board[fromY][fromX] == WhitePawn ) {
9373            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9374                board[EP_STATUS] = EP_PAWN_MOVE;
9375            if( toY-fromY==2) {
9376                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9377                         gameInfo.variant != VariantBerolina || toX < fromX)
9378                       board[EP_STATUS] = toX | berolina;
9379                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9380                         gameInfo.variant != VariantBerolina || toX > fromX)
9381                       board[EP_STATUS] = toX;
9382            }
9383       } else
9384       if( board[fromY][fromX] == BlackPawn ) {
9385            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9386                board[EP_STATUS] = EP_PAWN_MOVE;
9387            if( toY-fromY== -2) {
9388                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9389                         gameInfo.variant != VariantBerolina || toX < fromX)
9390                       board[EP_STATUS] = toX | berolina;
9391                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9392                         gameInfo.variant != VariantBerolina || toX > fromX)
9393                       board[EP_STATUS] = toX;
9394            }
9395        }
9396
9397        for(i=0; i<nrCastlingRights; i++) {
9398            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9399               board[CASTLING][i] == toX   && castlingRank[i] == toY
9400              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9401        }
9402
9403        if(gameInfo.variant == VariantSChess) { // update virginity
9404            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
9405            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
9406            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
9407            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
9408        }
9409
9410      if (fromX == toX && fromY == toY) return;
9411
9412      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9413      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9414      if(gameInfo.variant == VariantKnightmate)
9415          king += (int) WhiteUnicorn - (int) WhiteKing;
9416
9417     /* Code added by Tord: */
9418     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9419     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9420         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9421       board[fromY][fromX] = EmptySquare;
9422       board[toY][toX] = EmptySquare;
9423       if((toX > fromX) != (piece == WhiteRook)) {
9424         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9425       } else {
9426         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9427       }
9428     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9429                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9430       board[fromY][fromX] = EmptySquare;
9431       board[toY][toX] = EmptySquare;
9432       if((toX > fromX) != (piece == BlackRook)) {
9433         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9434       } else {
9435         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9436       }
9437     /* End of code added by Tord */
9438
9439     } else if (board[fromY][fromX] == king
9440         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9441         && toY == fromY && toX > fromX+1) {
9442         board[fromY][fromX] = EmptySquare;
9443         board[toY][toX] = king;
9444         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9445         board[fromY][BOARD_RGHT-1] = EmptySquare;
9446     } else if (board[fromY][fromX] == king
9447         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9448                && toY == fromY && toX < fromX-1) {
9449         board[fromY][fromX] = EmptySquare;
9450         board[toY][toX] = king;
9451         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9452         board[fromY][BOARD_LEFT] = EmptySquare;
9453     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9454                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9455                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9456                ) {
9457         /* white pawn promotion */
9458         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9459         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9460             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9461         board[fromY][fromX] = EmptySquare;
9462     } else if ((fromY >= BOARD_HEIGHT>>1)
9463                && (toX != fromX)
9464                && gameInfo.variant != VariantXiangqi
9465                && gameInfo.variant != VariantBerolina
9466                && (board[fromY][fromX] == WhitePawn)
9467                && (board[toY][toX] == EmptySquare)) {
9468         board[fromY][fromX] = EmptySquare;
9469         board[toY][toX] = WhitePawn;
9470         captured = board[toY - 1][toX];
9471         board[toY - 1][toX] = EmptySquare;
9472     } else if ((fromY == BOARD_HEIGHT-4)
9473                && (toX == fromX)
9474                && gameInfo.variant == VariantBerolina
9475                && (board[fromY][fromX] == WhitePawn)
9476                && (board[toY][toX] == EmptySquare)) {
9477         board[fromY][fromX] = EmptySquare;
9478         board[toY][toX] = WhitePawn;
9479         if(oldEP & EP_BEROLIN_A) {
9480                 captured = board[fromY][fromX-1];
9481                 board[fromY][fromX-1] = EmptySquare;
9482         }else{  captured = board[fromY][fromX+1];
9483                 board[fromY][fromX+1] = EmptySquare;
9484         }
9485     } else if (board[fromY][fromX] == king
9486         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9487                && toY == fromY && toX > fromX+1) {
9488         board[fromY][fromX] = EmptySquare;
9489         board[toY][toX] = king;
9490         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9491         board[fromY][BOARD_RGHT-1] = EmptySquare;
9492     } else if (board[fromY][fromX] == king
9493         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9494                && toY == fromY && toX < fromX-1) {
9495         board[fromY][fromX] = EmptySquare;
9496         board[toY][toX] = king;
9497         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9498         board[fromY][BOARD_LEFT] = EmptySquare;
9499     } else if (fromY == 7 && fromX == 3
9500                && board[fromY][fromX] == BlackKing
9501                && toY == 7 && toX == 5) {
9502         board[fromY][fromX] = EmptySquare;
9503         board[toY][toX] = BlackKing;
9504         board[fromY][7] = EmptySquare;
9505         board[toY][4] = BlackRook;
9506     } else if (fromY == 7 && fromX == 3
9507                && board[fromY][fromX] == BlackKing
9508                && toY == 7 && toX == 1) {
9509         board[fromY][fromX] = EmptySquare;
9510         board[toY][toX] = BlackKing;
9511         board[fromY][0] = EmptySquare;
9512         board[toY][2] = BlackRook;
9513     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9514                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9515                && toY < promoRank && promoChar
9516                ) {
9517         /* black pawn promotion */
9518         board[toY][toX] = CharToPiece(ToLower(promoChar));
9519         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9520             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9521         board[fromY][fromX] = EmptySquare;
9522     } else if ((fromY < BOARD_HEIGHT>>1)
9523                && (toX != fromX)
9524                && gameInfo.variant != VariantXiangqi
9525                && gameInfo.variant != VariantBerolina
9526                && (board[fromY][fromX] == BlackPawn)
9527                && (board[toY][toX] == EmptySquare)) {
9528         board[fromY][fromX] = EmptySquare;
9529         board[toY][toX] = BlackPawn;
9530         captured = board[toY + 1][toX];
9531         board[toY + 1][toX] = EmptySquare;
9532     } else if ((fromY == 3)
9533                && (toX == fromX)
9534                && gameInfo.variant == VariantBerolina
9535                && (board[fromY][fromX] == BlackPawn)
9536                && (board[toY][toX] == EmptySquare)) {
9537         board[fromY][fromX] = EmptySquare;
9538         board[toY][toX] = BlackPawn;
9539         if(oldEP & EP_BEROLIN_A) {
9540                 captured = board[fromY][fromX-1];
9541                 board[fromY][fromX-1] = EmptySquare;
9542         }else{  captured = board[fromY][fromX+1];
9543                 board[fromY][fromX+1] = EmptySquare;
9544         }
9545     } else {
9546         board[toY][toX] = board[fromY][fromX];
9547         board[fromY][fromX] = EmptySquare;
9548     }
9549   }
9550
9551     if (gameInfo.holdingsWidth != 0) {
9552
9553       /* !!A lot more code needs to be written to support holdings  */
9554       /* [HGM] OK, so I have written it. Holdings are stored in the */
9555       /* penultimate board files, so they are automaticlly stored   */
9556       /* in the game history.                                       */
9557       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9558                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9559         /* Delete from holdings, by decreasing count */
9560         /* and erasing image if necessary            */
9561         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9562         if(p < (int) BlackPawn) { /* white drop */
9563              p -= (int)WhitePawn;
9564                  p = PieceToNumber((ChessSquare)p);
9565              if(p >= gameInfo.holdingsSize) p = 0;
9566              if(--board[p][BOARD_WIDTH-2] <= 0)
9567                   board[p][BOARD_WIDTH-1] = EmptySquare;
9568              if((int)board[p][BOARD_WIDTH-2] < 0)
9569                         board[p][BOARD_WIDTH-2] = 0;
9570         } else {                  /* black drop */
9571              p -= (int)BlackPawn;
9572                  p = PieceToNumber((ChessSquare)p);
9573              if(p >= gameInfo.holdingsSize) p = 0;
9574              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9575                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9576              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9577                         board[BOARD_HEIGHT-1-p][1] = 0;
9578         }
9579       }
9580       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9581           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9582         /* [HGM] holdings: Add to holdings, if holdings exist */
9583         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9584                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9585                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9586         }
9587         p = (int) captured;
9588         if (p >= (int) BlackPawn) {
9589           p -= (int)BlackPawn;
9590           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9591                   /* in Shogi restore piece to its original  first */
9592                   captured = (ChessSquare) (DEMOTED captured);
9593                   p = DEMOTED p;
9594           }
9595           p = PieceToNumber((ChessSquare)p);
9596           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9597           board[p][BOARD_WIDTH-2]++;
9598           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9599         } else {
9600           p -= (int)WhitePawn;
9601           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9602                   captured = (ChessSquare) (DEMOTED captured);
9603                   p = DEMOTED p;
9604           }
9605           p = PieceToNumber((ChessSquare)p);
9606           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9607           board[BOARD_HEIGHT-1-p][1]++;
9608           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9609         }
9610       }
9611     } else if (gameInfo.variant == VariantAtomic) {
9612       if (captured != EmptySquare) {
9613         int y, x;
9614         for (y = toY-1; y <= toY+1; y++) {
9615           for (x = toX-1; x <= toX+1; x++) {
9616             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9617                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9618               board[y][x] = EmptySquare;
9619             }
9620           }
9621         }
9622         board[toY][toX] = EmptySquare;
9623       }
9624     }
9625     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9626         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9627     } else
9628     if(promoChar == '+') {
9629         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
9630         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9631     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9632         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9633         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
9634            && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
9635         board[toY][toX] = newPiece;
9636     }
9637     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9638                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9639         // [HGM] superchess: take promotion piece out of holdings
9640         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9641         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9642             if(!--board[k][BOARD_WIDTH-2])
9643                 board[k][BOARD_WIDTH-1] = EmptySquare;
9644         } else {
9645             if(!--board[BOARD_HEIGHT-1-k][1])
9646                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9647         }
9648     }
9649
9650 }
9651
9652 /* Updates forwardMostMove */
9653 void
9654 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
9655 {
9656 //    forwardMostMove++; // [HGM] bare: moved downstream
9657
9658     (void) CoordsToAlgebraic(boards[forwardMostMove],
9659                              PosFlags(forwardMostMove),
9660                              fromY, fromX, toY, toX, promoChar,
9661                              parseList[forwardMostMove]);
9662
9663     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9664         int timeLeft; static int lastLoadFlag=0; int king, piece;
9665         piece = boards[forwardMostMove][fromY][fromX];
9666         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9667         if(gameInfo.variant == VariantKnightmate)
9668             king += (int) WhiteUnicorn - (int) WhiteKing;
9669         if(forwardMostMove == 0) {
9670             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9671                 fprintf(serverMoves, "%s;", UserName());
9672             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9673                 fprintf(serverMoves, "%s;", second.tidy);
9674             fprintf(serverMoves, "%s;", first.tidy);
9675             if(gameMode == MachinePlaysWhite)
9676                 fprintf(serverMoves, "%s;", UserName());
9677             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9678                 fprintf(serverMoves, "%s;", second.tidy);
9679         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9680         lastLoadFlag = loadFlag;
9681         // print base move
9682         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9683         // print castling suffix
9684         if( toY == fromY && piece == king ) {
9685             if(toX-fromX > 1)
9686                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9687             if(fromX-toX >1)
9688                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9689         }
9690         // e.p. suffix
9691         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9692              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9693              boards[forwardMostMove][toY][toX] == EmptySquare
9694              && fromX != toX && fromY != toY)
9695                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9696         // promotion suffix
9697         if(promoChar != NULLCHAR) {
9698             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
9699                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
9700                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
9701             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
9702         }
9703         if(!loadFlag) {
9704                 char buf[MOVE_LEN*2], *p; int len;
9705             fprintf(serverMoves, "/%d/%d",
9706                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9707             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9708             else                      timeLeft = blackTimeRemaining/1000;
9709             fprintf(serverMoves, "/%d", timeLeft);
9710                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
9711                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
9712                 if(p = strchr(buf, '=')) *p = NULLCHAR;
9713                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
9714             fprintf(serverMoves, "/%s", buf);
9715         }
9716         fflush(serverMoves);
9717     }
9718
9719     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
9720         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
9721       return;
9722     }
9723     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9724     if (commentList[forwardMostMove+1] != NULL) {
9725         free(commentList[forwardMostMove+1]);
9726         commentList[forwardMostMove+1] = NULL;
9727     }
9728     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9729     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9730     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9731     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9732     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9733     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9734     adjustedClock = FALSE;
9735     gameInfo.result = GameUnfinished;
9736     if (gameInfo.resultDetails != NULL) {
9737         free(gameInfo.resultDetails);
9738         gameInfo.resultDetails = NULL;
9739     }
9740     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9741                               moveList[forwardMostMove - 1]);
9742     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9743       case MT_NONE:
9744       case MT_STALEMATE:
9745       default:
9746         break;
9747       case MT_CHECK:
9748         if(gameInfo.variant != VariantShogi)
9749             strcat(parseList[forwardMostMove - 1], "+");
9750         break;
9751       case MT_CHECKMATE:
9752       case MT_STAINMATE:
9753         strcat(parseList[forwardMostMove - 1], "#");
9754         break;
9755     }
9756
9757 }
9758
9759 /* Updates currentMove if not pausing */
9760 void
9761 ShowMove (int fromX, int fromY, int toX, int toY)
9762 {
9763     int instant = (gameMode == PlayFromGameFile) ?
9764         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9765     if(appData.noGUI) return;
9766     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9767         if (!instant) {
9768             if (forwardMostMove == currentMove + 1) {
9769                 AnimateMove(boards[forwardMostMove - 1],
9770                             fromX, fromY, toX, toY);
9771             }
9772         }
9773         currentMove = forwardMostMove;
9774     }
9775
9776     if (instant) return;
9777
9778     DisplayMove(currentMove - 1);
9779     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9780             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
9781                 SetHighlights(fromX, fromY, toX, toY);
9782             }
9783     }
9784     DrawPosition(FALSE, boards[currentMove]);
9785     DisplayBothClocks();
9786     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9787 }
9788
9789 void
9790 SendEgtPath (ChessProgramState *cps)
9791 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9792         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9793
9794         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9795
9796         while(*p) {
9797             char c, *q = name+1, *r, *s;
9798
9799             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9800             while(*p && *p != ',') *q++ = *p++;
9801             *q++ = ':'; *q = 0;
9802             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9803                 strcmp(name, ",nalimov:") == 0 ) {
9804                 // take nalimov path from the menu-changeable option first, if it is defined
9805               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9806                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9807             } else
9808             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9809                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9810                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9811                 s = r = StrStr(s, ":") + 1; // beginning of path info
9812                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9813                 c = *r; *r = 0;             // temporarily null-terminate path info
9814                     *--q = 0;               // strip of trailig ':' from name
9815                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9816                 *r = c;
9817                 SendToProgram(buf,cps);     // send egtbpath command for this format
9818             }
9819             if(*p == ',') p++; // read away comma to position for next format name
9820         }
9821 }
9822
9823 void
9824 InitChessProgram (ChessProgramState *cps, int setup)
9825 /* setup needed to setup FRC opening position */
9826 {
9827     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9828     if (appData.noChessProgram) return;
9829     hintRequested = FALSE;
9830     bookRequested = FALSE;
9831
9832     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
9833     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9834     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9835     if(cps->memSize) { /* [HGM] memory */
9836       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9837         SendToProgram(buf, cps);
9838     }
9839     SendEgtPath(cps); /* [HGM] EGT */
9840     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9841       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9842         SendToProgram(buf, cps);
9843     }
9844
9845     SendToProgram(cps->initString, cps);
9846     if (gameInfo.variant != VariantNormal &&
9847         gameInfo.variant != VariantLoadable
9848         /* [HGM] also send variant if board size non-standard */
9849         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9850                                             ) {
9851       char *v = VariantName(gameInfo.variant);
9852       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9853         /* [HGM] in protocol 1 we have to assume all variants valid */
9854         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9855         DisplayFatalError(buf, 0, 1);
9856         return;
9857       }
9858
9859       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9860       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9861       if( gameInfo.variant == VariantXiangqi )
9862            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9863       if( gameInfo.variant == VariantShogi )
9864            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9865       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9866            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9867       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9868           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9869            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9870       if( gameInfo.variant == VariantCourier )
9871            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9872       if( gameInfo.variant == VariantSuper )
9873            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9874       if( gameInfo.variant == VariantGreat )
9875            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9876       if( gameInfo.variant == VariantSChess )
9877            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9878       if( gameInfo.variant == VariantGrand )
9879            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
9880
9881       if(overruled) {
9882         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9883                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9884            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9885            if(StrStr(cps->variants, b) == NULL) {
9886                // specific sized variant not known, check if general sizing allowed
9887                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9888                    if(StrStr(cps->variants, "boardsize") == NULL) {
9889                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9890                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9891                        DisplayFatalError(buf, 0, 1);
9892                        return;
9893                    }
9894                    /* [HGM] here we really should compare with the maximum supported board size */
9895                }
9896            }
9897       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9898       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9899       SendToProgram(buf, cps);
9900     }
9901     currentlyInitializedVariant = gameInfo.variant;
9902
9903     /* [HGM] send opening position in FRC to first engine */
9904     if(setup) {
9905           SendToProgram("force\n", cps);
9906           SendBoard(cps, 0);
9907           /* engine is now in force mode! Set flag to wake it up after first move. */
9908           setboardSpoiledMachineBlack = 1;
9909     }
9910
9911     if (cps->sendICS) {
9912       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9913       SendToProgram(buf, cps);
9914     }
9915     cps->maybeThinking = FALSE;
9916     cps->offeredDraw = 0;
9917     if (!appData.icsActive) {
9918         SendTimeControl(cps, movesPerSession, timeControl,
9919                         timeIncrement, appData.searchDepth,
9920                         searchTime);
9921     }
9922     if (appData.showThinking
9923         // [HGM] thinking: four options require thinking output to be sent
9924         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9925                                 ) {
9926         SendToProgram("post\n", cps);
9927     }
9928     SendToProgram("hard\n", cps);
9929     if (!appData.ponderNextMove) {
9930         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9931            it without being sure what state we are in first.  "hard"
9932            is not a toggle, so that one is OK.
9933          */
9934         SendToProgram("easy\n", cps);
9935     }
9936     if (cps->usePing) {
9937       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9938       SendToProgram(buf, cps);
9939     }
9940     cps->initDone = TRUE;
9941     ClearEngineOutputPane(cps == &second);
9942 }
9943
9944
9945 void
9946 ResendOptions (ChessProgramState *cps)
9947 { // send the stored value of the options
9948   int i;
9949   char buf[MSG_SIZ];
9950   Option *opt = cps->option;
9951   for(i=0; i<cps->nrOptions; i++, opt++) {
9952       switch(opt->type) {
9953         case Spin:
9954         case Slider:
9955         case CheckBox:
9956             snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
9957           break;
9958         case ComboBox:
9959           snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
9960           break;
9961         default:
9962             snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
9963           break;
9964         case Button:
9965         case SaveButton:
9966           continue;
9967       }
9968       SendToProgram(buf, cps);
9969   }
9970 }
9971
9972 void
9973 StartChessProgram (ChessProgramState *cps)
9974 {
9975     char buf[MSG_SIZ];
9976     int err;
9977
9978     if (appData.noChessProgram) return;
9979     cps->initDone = FALSE;
9980
9981     if (strcmp(cps->host, "localhost") == 0) {
9982         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9983     } else if (*appData.remoteShell == NULLCHAR) {
9984         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9985     } else {
9986         if (*appData.remoteUser == NULLCHAR) {
9987           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9988                     cps->program);
9989         } else {
9990           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9991                     cps->host, appData.remoteUser, cps->program);
9992         }
9993         err = StartChildProcess(buf, "", &cps->pr);
9994     }
9995
9996     if (err != 0) {
9997       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9998         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
9999         if(cps != &first) return;
10000         appData.noChessProgram = TRUE;
10001         ThawUI();
10002         SetNCPMode();
10003 //      DisplayFatalError(buf, err, 1);
10004 //      cps->pr = NoProc;
10005 //      cps->isr = NULL;
10006         return;
10007     }
10008
10009     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10010     if (cps->protocolVersion > 1) {
10011       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10012       if(!cps->reload) { // do not clear options when reloading because of -xreuse
10013         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10014         cps->comboCnt = 0;  //                and values of combo boxes
10015       }
10016       SendToProgram(buf, cps);
10017       if(cps->reload) ResendOptions(cps);
10018     } else {
10019       SendToProgram("xboard\n", cps);
10020     }
10021 }
10022
10023 void
10024 TwoMachinesEventIfReady P((void))
10025 {
10026   static int curMess = 0;
10027   if (first.lastPing != first.lastPong) {
10028     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10029     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10030     return;
10031   }
10032   if (second.lastPing != second.lastPong) {
10033     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10034     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10035     return;
10036   }
10037   DisplayMessage("", ""); curMess = 0;
10038   ThawUI();
10039   TwoMachinesEvent();
10040 }
10041
10042 char *
10043 MakeName (char *template)
10044 {
10045     time_t clock;
10046     struct tm *tm;
10047     static char buf[MSG_SIZ];
10048     char *p = buf;
10049     int i;
10050
10051     clock = time((time_t *)NULL);
10052     tm = localtime(&clock);
10053
10054     while(*p++ = *template++) if(p[-1] == '%') {
10055         switch(*template++) {
10056           case 0:   *p = 0; return buf;
10057           case 'Y': i = tm->tm_year+1900; break;
10058           case 'y': i = tm->tm_year-100; break;
10059           case 'M': i = tm->tm_mon+1; break;
10060           case 'd': i = tm->tm_mday; break;
10061           case 'h': i = tm->tm_hour; break;
10062           case 'm': i = tm->tm_min; break;
10063           case 's': i = tm->tm_sec; break;
10064           default:  i = 0;
10065         }
10066         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10067     }
10068     return buf;
10069 }
10070
10071 int
10072 CountPlayers (char *p)
10073 {
10074     int n = 0;
10075     while(p = strchr(p, '\n')) p++, n++; // count participants
10076     return n;
10077 }
10078
10079 FILE *
10080 WriteTourneyFile (char *results, FILE *f)
10081 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10082     if(f == NULL) f = fopen(appData.tourneyFile, "w");
10083     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10084         // create a file with tournament description
10085         fprintf(f, "-participants {%s}\n", appData.participants);
10086         fprintf(f, "-seedBase %d\n", appData.seedBase);
10087         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10088         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10089         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10090         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10091         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10092         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10093         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10094         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10095         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10096         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10097         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10098         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10099         fprintf(f, "-polyglotBook %s\n", appData.polyglotBook);
10100         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10101         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10102         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10103         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10104         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10105         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10106         fprintf(f, "-smpCores %d\n", appData.smpCores);
10107         if(searchTime > 0)
10108                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10109         else {
10110                 fprintf(f, "-mps %d\n", appData.movesPerSession);
10111                 fprintf(f, "-tc %s\n", appData.timeControl);
10112                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10113         }
10114         fprintf(f, "-results \"%s\"\n", results);
10115     }
10116     return f;
10117 }
10118
10119 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10120
10121 void
10122 Substitute (char *participants, int expunge)
10123 {
10124     int i, changed, changes=0, nPlayers=0;
10125     char *p, *q, *r, buf[MSG_SIZ];
10126     if(participants == NULL) return;
10127     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10128     r = p = participants; q = appData.participants;
10129     while(*p && *p == *q) {
10130         if(*p == '\n') r = p+1, nPlayers++;
10131         p++; q++;
10132     }
10133     if(*p) { // difference
10134         while(*p && *p++ != '\n');
10135         while(*q && *q++ != '\n');
10136       changed = nPlayers;
10137         changes = 1 + (strcmp(p, q) != 0);
10138     }
10139     if(changes == 1) { // a single engine mnemonic was changed
10140         q = r; while(*q) nPlayers += (*q++ == '\n');
10141         p = buf; while(*r && (*p = *r++) != '\n') p++;
10142         *p = NULLCHAR;
10143         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10144         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10145         if(mnemonic[i]) { // The substitute is valid
10146             FILE *f;
10147             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10148                 flock(fileno(f), LOCK_EX);
10149                 ParseArgsFromFile(f);
10150                 fseek(f, 0, SEEK_SET);
10151                 FREE(appData.participants); appData.participants = participants;
10152                 if(expunge) { // erase results of replaced engine
10153                     int len = strlen(appData.results), w, b, dummy;
10154                     for(i=0; i<len; i++) {
10155                         Pairing(i, nPlayers, &w, &b, &dummy);
10156                         if((w == changed || b == changed) && appData.results[i] == '*') {
10157                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10158                             fclose(f);
10159                             return;
10160                         }
10161                     }
10162                     for(i=0; i<len; i++) {
10163                         Pairing(i, nPlayers, &w, &b, &dummy);
10164                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10165                     }
10166                 }
10167                 WriteTourneyFile(appData.results, f);
10168                 fclose(f); // release lock
10169                 return;
10170             }
10171         } else DisplayError(_("No engine with the name you gave is installed"), 0);
10172     }
10173     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10174     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
10175     free(participants);
10176     return;
10177 }
10178
10179 int
10180 CheckPlayers (char *participants)
10181 {
10182         int i;
10183         char buf[MSG_SIZ], *p;
10184         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10185         while(p = strchr(participants, '\n')) {
10186             *p = NULLCHAR;
10187             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10188             if(!mnemonic[i]) {
10189                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10190                 *p = '\n';
10191                 DisplayError(buf, 0);
10192                 return 1;
10193             }
10194             *p = '\n';
10195             participants = p + 1;
10196         }
10197         return 0;
10198 }
10199
10200 int
10201 CreateTourney (char *name)
10202 {
10203         FILE *f;
10204         if(matchMode && strcmp(name, appData.tourneyFile)) {
10205              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10206         }
10207         if(name[0] == NULLCHAR) {
10208             if(appData.participants[0])
10209                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10210             return 0;
10211         }
10212         f = fopen(name, "r");
10213         if(f) { // file exists
10214             ASSIGN(appData.tourneyFile, name);
10215             ParseArgsFromFile(f); // parse it
10216         } else {
10217             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10218             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10219                 DisplayError(_("Not enough participants"), 0);
10220                 return 0;
10221             }
10222             if(CheckPlayers(appData.participants)) return 0;
10223             ASSIGN(appData.tourneyFile, name);
10224             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10225             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10226         }
10227         fclose(f);
10228         appData.noChessProgram = FALSE;
10229         appData.clockMode = TRUE;
10230         SetGNUMode();
10231         return 1;
10232 }
10233
10234 int
10235 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10236 {
10237     char buf[MSG_SIZ], *p, *q;
10238     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10239     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10240     skip = !all && group[0]; // if group requested, we start in skip mode
10241     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10242         p = names; q = buf; header = 0;
10243         while(*p && *p != '\n') *q++ = *p++;
10244         *q = 0;
10245         if(*p == '\n') p++;
10246         if(buf[0] == '#') {
10247             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10248             depth++; // we must be entering a new group
10249             if(all) continue; // suppress printing group headers when complete list requested
10250             header = 1;
10251             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10252         }
10253         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10254         if(engineList[i]) free(engineList[i]);
10255         engineList[i] = strdup(buf);
10256         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10257         if(engineMnemonic[i]) free(engineMnemonic[i]);
10258         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10259             strcat(buf, " (");
10260             sscanf(q + 8, "%s", buf + strlen(buf));
10261             strcat(buf, ")");
10262         }
10263         engineMnemonic[i] = strdup(buf);
10264         i++;
10265     }
10266     engineList[i] = engineMnemonic[i] = NULL;
10267     return i;
10268 }
10269
10270 // following implemented as macro to avoid type limitations
10271 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10272
10273 void
10274 SwapEngines (int n)
10275 {   // swap settings for first engine and other engine (so far only some selected options)
10276     int h;
10277     char *p;
10278     if(n == 0) return;
10279     SWAP(directory, p)
10280     SWAP(chessProgram, p)
10281     SWAP(isUCI, h)
10282     SWAP(hasOwnBookUCI, h)
10283     SWAP(protocolVersion, h)
10284     SWAP(reuse, h)
10285     SWAP(scoreIsAbsolute, h)
10286     SWAP(timeOdds, h)
10287     SWAP(logo, p)
10288     SWAP(pgnName, p)
10289     SWAP(pvSAN, h)
10290     SWAP(engOptions, p)
10291     SWAP(engInitString, p)
10292     SWAP(computerString, p)
10293     SWAP(features, p)
10294     SWAP(fenOverride, p)
10295     SWAP(NPS, h)
10296     SWAP(accumulateTC, h)
10297     SWAP(host, p)
10298 }
10299
10300 int
10301 GetEngineLine (char *s, int n)
10302 {
10303     int i;
10304     char buf[MSG_SIZ];
10305     extern char *icsNames;
10306     if(!s || !*s) return 0;
10307     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
10308     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
10309     if(!mnemonic[i]) return 0;
10310     if(n == 11) return 1; // just testing if there was a match
10311     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
10312     if(n == 1) SwapEngines(n);
10313     ParseArgsFromString(buf);
10314     if(n == 1) SwapEngines(n);
10315     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
10316         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
10317         ParseArgsFromString(buf);
10318     }
10319     return 1;
10320 }
10321
10322 int
10323 SetPlayer (int player, char *p)
10324 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
10325     int i;
10326     char buf[MSG_SIZ], *engineName;
10327     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10328     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10329     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10330     if(mnemonic[i]) {
10331         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10332         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10333         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10334         ParseArgsFromString(buf);
10335     }
10336     free(engineName);
10337     return i;
10338 }
10339
10340 char *recentEngines;
10341
10342 void
10343 RecentEngineEvent (int nr)
10344 {
10345     int n;
10346 //    SwapEngines(1); // bump first to second
10347 //    ReplaceEngine(&second, 1); // and load it there
10348     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10349     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10350     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10351         ReplaceEngine(&first, 0);
10352         FloatToFront(&appData.recentEngineList, command[n]);
10353     }
10354 }
10355
10356 int
10357 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10358 {   // determine players from game number
10359     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10360
10361     if(appData.tourneyType == 0) {
10362         roundsPerCycle = (nPlayers - 1) | 1;
10363         pairingsPerRound = nPlayers / 2;
10364     } else if(appData.tourneyType > 0) {
10365         roundsPerCycle = nPlayers - appData.tourneyType;
10366         pairingsPerRound = appData.tourneyType;
10367     }
10368     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10369     gamesPerCycle = gamesPerRound * roundsPerCycle;
10370     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10371     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10372     curRound = nr / gamesPerRound; nr %= gamesPerRound;
10373     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10374     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10375     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10376
10377     if(appData.cycleSync) *syncInterval = gamesPerCycle;
10378     if(appData.roundSync) *syncInterval = gamesPerRound;
10379
10380     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10381
10382     if(appData.tourneyType == 0) {
10383         if(curPairing == (nPlayers-1)/2 ) {
10384             *whitePlayer = curRound;
10385             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10386         } else {
10387             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10388             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10389             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10390             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10391         }
10392     } else if(appData.tourneyType > 1) {
10393         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10394         *whitePlayer = curRound + appData.tourneyType;
10395     } else if(appData.tourneyType > 0) {
10396         *whitePlayer = curPairing;
10397         *blackPlayer = curRound + appData.tourneyType;
10398     }
10399
10400     // take care of white/black alternation per round.
10401     // For cycles and games this is already taken care of by default, derived from matchGame!
10402     return curRound & 1;
10403 }
10404
10405 int
10406 NextTourneyGame (int nr, int *swapColors)
10407 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10408     char *p, *q;
10409     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers;
10410     FILE *tf;
10411     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10412     tf = fopen(appData.tourneyFile, "r");
10413     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10414     ParseArgsFromFile(tf); fclose(tf);
10415     InitTimeControls(); // TC might be altered from tourney file
10416
10417     nPlayers = CountPlayers(appData.participants); // count participants
10418     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10419     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10420
10421     if(syncInterval) {
10422         p = q = appData.results;
10423         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10424         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10425             DisplayMessage(_("Waiting for other game(s)"),"");
10426             waitingForGame = TRUE;
10427             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10428             return 0;
10429         }
10430         waitingForGame = FALSE;
10431     }
10432
10433     if(appData.tourneyType < 0) {
10434         if(nr>=0 && !pairingReceived) {
10435             char buf[1<<16];
10436             if(pairing.pr == NoProc) {
10437                 if(!appData.pairingEngine[0]) {
10438                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10439                     return 0;
10440                 }
10441                 StartChessProgram(&pairing); // starts the pairing engine
10442             }
10443             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10444             SendToProgram(buf, &pairing);
10445             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10446             SendToProgram(buf, &pairing);
10447             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10448         }
10449         pairingReceived = 0;                              // ... so we continue here
10450         *swapColors = 0;
10451         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10452         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10453         matchGame = 1; roundNr = nr / syncInterval + 1;
10454     }
10455
10456     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10457
10458     // redefine engines, engine dir, etc.
10459     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10460     if(first.pr == NoProc) {
10461       SetPlayer(whitePlayer, appData.participants); // find white player amongst it, and parse its engine line
10462       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10463     }
10464     if(second.pr == NoProc) {
10465       SwapEngines(1);
10466       SetPlayer(blackPlayer, appData.participants); // find black player amongst it, and parse its engine line
10467       SwapEngines(1);         // and make that valid for second engine by swapping
10468       InitEngine(&second, 1);
10469     }
10470     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10471     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10472     return 1;
10473 }
10474
10475 void
10476 NextMatchGame ()
10477 {   // performs game initialization that does not invoke engines, and then tries to start the game
10478     int res, firstWhite, swapColors = 0;
10479     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10480     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
10481         char buf[MSG_SIZ];
10482         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
10483         if(strcmp(buf, currentDebugFile)) { // name has changed
10484             FILE *f = fopen(buf, "w");
10485             if(f) { // if opening the new file failed, just keep using the old one
10486                 ASSIGN(currentDebugFile, buf);
10487                 fclose(debugFP);
10488                 debugFP = f;
10489             }
10490             if(appData.serverFileName) {
10491                 if(serverFP) fclose(serverFP);
10492                 serverFP = fopen(appData.serverFileName, "w");
10493                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
10494                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
10495             }
10496         }
10497     }
10498     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10499     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10500     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
10501     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10502     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10503     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10504     Reset(FALSE, first.pr != NoProc);
10505     res = LoadGameOrPosition(matchGame); // setup game
10506     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10507     if(!res) return; // abort when bad game/pos file
10508     TwoMachinesEvent();
10509 }
10510
10511 void
10512 UserAdjudicationEvent (int result)
10513 {
10514     ChessMove gameResult = GameIsDrawn;
10515
10516     if( result > 0 ) {
10517         gameResult = WhiteWins;
10518     }
10519     else if( result < 0 ) {
10520         gameResult = BlackWins;
10521     }
10522
10523     if( gameMode == TwoMachinesPlay ) {
10524         GameEnds( gameResult, "User adjudication", GE_XBOARD );
10525     }
10526 }
10527
10528
10529 // [HGM] save: calculate checksum of game to make games easily identifiable
10530 int
10531 StringCheckSum (char *s)
10532 {
10533         int i = 0;
10534         if(s==NULL) return 0;
10535         while(*s) i = i*259 + *s++;
10536         return i;
10537 }
10538
10539 int
10540 GameCheckSum ()
10541 {
10542         int i, sum=0;
10543         for(i=backwardMostMove; i<forwardMostMove; i++) {
10544                 sum += pvInfoList[i].depth;
10545                 sum += StringCheckSum(parseList[i]);
10546                 sum += StringCheckSum(commentList[i]);
10547                 sum *= 261;
10548         }
10549         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10550         return sum + StringCheckSum(commentList[i]);
10551 } // end of save patch
10552
10553 void
10554 GameEnds (ChessMove result, char *resultDetails, int whosays)
10555 {
10556     GameMode nextGameMode;
10557     int isIcsGame;
10558     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10559
10560     if(endingGame) return; /* [HGM] crash: forbid recursion */
10561     endingGame = 1;
10562     if(twoBoards) { // [HGM] dual: switch back to one board
10563         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10564         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10565     }
10566     if (appData.debugMode) {
10567       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10568               result, resultDetails ? resultDetails : "(null)", whosays);
10569     }
10570
10571     fromX = fromY = -1; // [HGM] abort any move the user is entering.
10572
10573     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
10574
10575     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10576         /* If we are playing on ICS, the server decides when the
10577            game is over, but the engine can offer to draw, claim
10578            a draw, or resign.
10579          */
10580 #if ZIPPY
10581         if (appData.zippyPlay && first.initDone) {
10582             if (result == GameIsDrawn) {
10583                 /* In case draw still needs to be claimed */
10584                 SendToICS(ics_prefix);
10585                 SendToICS("draw\n");
10586             } else if (StrCaseStr(resultDetails, "resign")) {
10587                 SendToICS(ics_prefix);
10588                 SendToICS("resign\n");
10589             }
10590         }
10591 #endif
10592         endingGame = 0; /* [HGM] crash */
10593         return;
10594     }
10595
10596     /* If we're loading the game from a file, stop */
10597     if (whosays == GE_FILE) {
10598       (void) StopLoadGameTimer();
10599       gameFileFP = NULL;
10600     }
10601
10602     /* Cancel draw offers */
10603     first.offeredDraw = second.offeredDraw = 0;
10604
10605     /* If this is an ICS game, only ICS can really say it's done;
10606        if not, anyone can. */
10607     isIcsGame = (gameMode == IcsPlayingWhite ||
10608                  gameMode == IcsPlayingBlack ||
10609                  gameMode == IcsObserving    ||
10610                  gameMode == IcsExamining);
10611
10612     if (!isIcsGame || whosays == GE_ICS) {
10613         /* OK -- not an ICS game, or ICS said it was done */
10614         StopClocks();
10615         if (!isIcsGame && !appData.noChessProgram)
10616           SetUserThinkingEnables();
10617
10618         /* [HGM] if a machine claims the game end we verify this claim */
10619         if(gameMode == TwoMachinesPlay && appData.testClaims) {
10620             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10621                 char claimer;
10622                 ChessMove trueResult = (ChessMove) -1;
10623
10624                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
10625                                             first.twoMachinesColor[0] :
10626                                             second.twoMachinesColor[0] ;
10627
10628                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10629                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10630                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10631                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10632                 } else
10633                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10634                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10635                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10636                 } else
10637                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10638                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10639                 }
10640
10641                 // now verify win claims, but not in drop games, as we don't understand those yet
10642                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10643                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10644                     (result == WhiteWins && claimer == 'w' ||
10645                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
10646                       if (appData.debugMode) {
10647                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
10648                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10649                       }
10650                       if(result != trueResult) {
10651                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10652                               result = claimer == 'w' ? BlackWins : WhiteWins;
10653                               resultDetails = buf;
10654                       }
10655                 } else
10656                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10657                     && (forwardMostMove <= backwardMostMove ||
10658                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10659                         (claimer=='b')==(forwardMostMove&1))
10660                                                                                   ) {
10661                       /* [HGM] verify: draws that were not flagged are false claims */
10662                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10663                       result = claimer == 'w' ? BlackWins : WhiteWins;
10664                       resultDetails = buf;
10665                 }
10666                 /* (Claiming a loss is accepted no questions asked!) */
10667             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
10668                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
10669                 result = GameUnfinished;
10670                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
10671             }
10672             /* [HGM] bare: don't allow bare King to win */
10673             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10674                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10675                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10676                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10677                && result != GameIsDrawn)
10678             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10679                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10680                         int p = (signed char)boards[forwardMostMove][i][j] - color;
10681                         if(p >= 0 && p <= (int)WhiteKing) k++;
10682                 }
10683                 if (appData.debugMode) {
10684                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10685                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10686                 }
10687                 if(k <= 1) {
10688                         result = GameIsDrawn;
10689                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10690                         resultDetails = buf;
10691                 }
10692             }
10693         }
10694
10695
10696         if(serverMoves != NULL && !loadFlag) { char c = '=';
10697             if(result==WhiteWins) c = '+';
10698             if(result==BlackWins) c = '-';
10699             if(resultDetails != NULL)
10700                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
10701         }
10702         if (resultDetails != NULL) {
10703             gameInfo.result = result;
10704             gameInfo.resultDetails = StrSave(resultDetails);
10705
10706             /* display last move only if game was not loaded from file */
10707             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10708                 DisplayMove(currentMove - 1);
10709
10710             if (forwardMostMove != 0) {
10711                 if (gameMode != PlayFromGameFile && gameMode != EditGame
10712                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10713                                                                 ) {
10714                     if (*appData.saveGameFile != NULLCHAR) {
10715                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
10716                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
10717                         else
10718                         SaveGameToFile(appData.saveGameFile, TRUE);
10719                     } else if (appData.autoSaveGames) {
10720                         AutoSaveGame();
10721                     }
10722                     if (*appData.savePositionFile != NULLCHAR) {
10723                         SavePositionToFile(appData.savePositionFile);
10724                     }
10725                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
10726                 }
10727             }
10728
10729             /* Tell program how game ended in case it is learning */
10730             /* [HGM] Moved this to after saving the PGN, just in case */
10731             /* engine died and we got here through time loss. In that */
10732             /* case we will get a fatal error writing the pipe, which */
10733             /* would otherwise lose us the PGN.                       */
10734             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
10735             /* output during GameEnds should never be fatal anymore   */
10736             if (gameMode == MachinePlaysWhite ||
10737                 gameMode == MachinePlaysBlack ||
10738                 gameMode == TwoMachinesPlay ||
10739                 gameMode == IcsPlayingWhite ||
10740                 gameMode == IcsPlayingBlack ||
10741                 gameMode == BeginningOfGame) {
10742                 char buf[MSG_SIZ];
10743                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10744                         resultDetails);
10745                 if (first.pr != NoProc) {
10746                     SendToProgram(buf, &first);
10747                 }
10748                 if (second.pr != NoProc &&
10749                     gameMode == TwoMachinesPlay) {
10750                     SendToProgram(buf, &second);
10751                 }
10752             }
10753         }
10754
10755         if (appData.icsActive) {
10756             if (appData.quietPlay &&
10757                 (gameMode == IcsPlayingWhite ||
10758                  gameMode == IcsPlayingBlack)) {
10759                 SendToICS(ics_prefix);
10760                 SendToICS("set shout 1\n");
10761             }
10762             nextGameMode = IcsIdle;
10763             ics_user_moved = FALSE;
10764             /* clean up premove.  It's ugly when the game has ended and the
10765              * premove highlights are still on the board.
10766              */
10767             if (gotPremove) {
10768               gotPremove = FALSE;
10769               ClearPremoveHighlights();
10770               DrawPosition(FALSE, boards[currentMove]);
10771             }
10772             if (whosays == GE_ICS) {
10773                 switch (result) {
10774                 case WhiteWins:
10775                     if (gameMode == IcsPlayingWhite)
10776                         PlayIcsWinSound();
10777                     else if(gameMode == IcsPlayingBlack)
10778                         PlayIcsLossSound();
10779                     break;
10780                 case BlackWins:
10781                     if (gameMode == IcsPlayingBlack)
10782                         PlayIcsWinSound();
10783                     else if(gameMode == IcsPlayingWhite)
10784                         PlayIcsLossSound();
10785                     break;
10786                 case GameIsDrawn:
10787                     PlayIcsDrawSound();
10788                     break;
10789                 default:
10790                     PlayIcsUnfinishedSound();
10791                 }
10792             }
10793         } else if (gameMode == EditGame ||
10794                    gameMode == PlayFromGameFile ||
10795                    gameMode == AnalyzeMode ||
10796                    gameMode == AnalyzeFile) {
10797             nextGameMode = gameMode;
10798         } else {
10799             nextGameMode = EndOfGame;
10800         }
10801         pausing = FALSE;
10802         ModeHighlight();
10803     } else {
10804         nextGameMode = gameMode;
10805     }
10806
10807     if (appData.noChessProgram) {
10808         gameMode = nextGameMode;
10809         ModeHighlight();
10810         endingGame = 0; /* [HGM] crash */
10811         return;
10812     }
10813
10814     if (first.reuse) {
10815         /* Put first chess program into idle state */
10816         if (first.pr != NoProc &&
10817             (gameMode == MachinePlaysWhite ||
10818              gameMode == MachinePlaysBlack ||
10819              gameMode == TwoMachinesPlay ||
10820              gameMode == IcsPlayingWhite ||
10821              gameMode == IcsPlayingBlack ||
10822              gameMode == BeginningOfGame)) {
10823             SendToProgram("force\n", &first);
10824             if (first.usePing) {
10825               char buf[MSG_SIZ];
10826               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10827               SendToProgram(buf, &first);
10828             }
10829         }
10830     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10831         /* Kill off first chess program */
10832         if (first.isr != NULL)
10833           RemoveInputSource(first.isr);
10834         first.isr = NULL;
10835
10836         if (first.pr != NoProc) {
10837             ExitAnalyzeMode();
10838             DoSleep( appData.delayBeforeQuit );
10839             SendToProgram("quit\n", &first);
10840             DoSleep( appData.delayAfterQuit );
10841             DestroyChildProcess(first.pr, first.useSigterm);
10842             first.reload = TRUE;
10843         }
10844         first.pr = NoProc;
10845     }
10846     if (second.reuse) {
10847         /* Put second chess program into idle state */
10848         if (second.pr != NoProc &&
10849             gameMode == TwoMachinesPlay) {
10850             SendToProgram("force\n", &second);
10851             if (second.usePing) {
10852               char buf[MSG_SIZ];
10853               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10854               SendToProgram(buf, &second);
10855             }
10856         }
10857     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10858         /* Kill off second chess program */
10859         if (second.isr != NULL)
10860           RemoveInputSource(second.isr);
10861         second.isr = NULL;
10862
10863         if (second.pr != NoProc) {
10864             DoSleep( appData.delayBeforeQuit );
10865             SendToProgram("quit\n", &second);
10866             DoSleep( appData.delayAfterQuit );
10867             DestroyChildProcess(second.pr, second.useSigterm);
10868             second.reload = TRUE;
10869         }
10870         second.pr = NoProc;
10871     }
10872
10873     if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10874         char resChar = '=';
10875         switch (result) {
10876         case WhiteWins:
10877           resChar = '+';
10878           if (first.twoMachinesColor[0] == 'w') {
10879             first.matchWins++;
10880           } else {
10881             second.matchWins++;
10882           }
10883           break;
10884         case BlackWins:
10885           resChar = '-';
10886           if (first.twoMachinesColor[0] == 'b') {
10887             first.matchWins++;
10888           } else {
10889             second.matchWins++;
10890           }
10891           break;
10892         case GameUnfinished:
10893           resChar = ' ';
10894         default:
10895           break;
10896         }
10897
10898         if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10899         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10900             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
10901             ReserveGame(nextGame, resChar); // sets nextGame
10902             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10903             else ranking = strdup("busy"); //suppress popup when aborted but not finished
10904         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10905
10906         if (nextGame <= appData.matchGames && !abortMatch) {
10907             gameMode = nextGameMode;
10908             matchGame = nextGame; // this will be overruled in tourney mode!
10909             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10910             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10911             endingGame = 0; /* [HGM] crash */
10912             return;
10913         } else {
10914             gameMode = nextGameMode;
10915             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10916                      first.tidy, second.tidy,
10917                      first.matchWins, second.matchWins,
10918                      appData.matchGames - (first.matchWins + second.matchWins));
10919             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
10920             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
10921             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10922             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10923                 first.twoMachinesColor = "black\n";
10924                 second.twoMachinesColor = "white\n";
10925             } else {
10926                 first.twoMachinesColor = "white\n";
10927                 second.twoMachinesColor = "black\n";
10928             }
10929         }
10930     }
10931     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10932         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10933       ExitAnalyzeMode();
10934     gameMode = nextGameMode;
10935     ModeHighlight();
10936     endingGame = 0;  /* [HGM] crash */
10937     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10938         if(matchMode == TRUE) { // match through command line: exit with or without popup
10939             if(ranking) {
10940                 ToNrEvent(forwardMostMove);
10941                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10942                 else ExitEvent(0);
10943             } else DisplayFatalError(buf, 0, 0);
10944         } else { // match through menu; just stop, with or without popup
10945             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10946             ModeHighlight();
10947             if(ranking){
10948                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10949             } else DisplayNote(buf);
10950       }
10951       if(ranking) free(ranking);
10952     }
10953 }
10954
10955 /* Assumes program was just initialized (initString sent).
10956    Leaves program in force mode. */
10957 void
10958 FeedMovesToProgram (ChessProgramState *cps, int upto)
10959 {
10960     int i;
10961
10962     if (appData.debugMode)
10963       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10964               startedFromSetupPosition ? "position and " : "",
10965               backwardMostMove, upto, cps->which);
10966     if(currentlyInitializedVariant != gameInfo.variant) {
10967       char buf[MSG_SIZ];
10968         // [HGM] variantswitch: make engine aware of new variant
10969         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10970                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10971         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10972         SendToProgram(buf, cps);
10973         currentlyInitializedVariant = gameInfo.variant;
10974     }
10975     SendToProgram("force\n", cps);
10976     if (startedFromSetupPosition) {
10977         SendBoard(cps, backwardMostMove);
10978     if (appData.debugMode) {
10979         fprintf(debugFP, "feedMoves\n");
10980     }
10981     }
10982     for (i = backwardMostMove; i < upto; i++) {
10983         SendMoveToProgram(i, cps);
10984     }
10985 }
10986
10987
10988 int
10989 ResurrectChessProgram ()
10990 {
10991      /* The chess program may have exited.
10992         If so, restart it and feed it all the moves made so far. */
10993     static int doInit = 0;
10994
10995     if (appData.noChessProgram) return 1;
10996
10997     if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10998         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10999         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11000         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11001     } else {
11002         if (first.pr != NoProc) return 1;
11003         StartChessProgram(&first);
11004     }
11005     InitChessProgram(&first, FALSE);
11006     FeedMovesToProgram(&first, currentMove);
11007
11008     if (!first.sendTime) {
11009         /* can't tell gnuchess what its clock should read,
11010            so we bow to its notion. */
11011         ResetClocks();
11012         timeRemaining[0][currentMove] = whiteTimeRemaining;
11013         timeRemaining[1][currentMove] = blackTimeRemaining;
11014     }
11015
11016     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11017                 appData.icsEngineAnalyze) && first.analysisSupport) {
11018       SendToProgram("analyze\n", &first);
11019       first.analyzing = TRUE;
11020     }
11021     return 1;
11022 }
11023
11024 /*
11025  * Button procedures
11026  */
11027 void
11028 Reset (int redraw, int init)
11029 {
11030     int i;
11031
11032     if (appData.debugMode) {
11033         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11034                 redraw, init, gameMode);
11035     }
11036     CleanupTail(); // [HGM] vari: delete any stored variations
11037     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11038     pausing = pauseExamInvalid = FALSE;
11039     startedFromSetupPosition = blackPlaysFirst = FALSE;
11040     firstMove = TRUE;
11041     whiteFlag = blackFlag = FALSE;
11042     userOfferedDraw = FALSE;
11043     hintRequested = bookRequested = FALSE;
11044     first.maybeThinking = FALSE;
11045     second.maybeThinking = FALSE;
11046     first.bookSuspend = FALSE; // [HGM] book
11047     second.bookSuspend = FALSE;
11048     thinkOutput[0] = NULLCHAR;
11049     lastHint[0] = NULLCHAR;
11050     ClearGameInfo(&gameInfo);
11051     gameInfo.variant = StringToVariant(appData.variant);
11052     ics_user_moved = ics_clock_paused = FALSE;
11053     ics_getting_history = H_FALSE;
11054     ics_gamenum = -1;
11055     white_holding[0] = black_holding[0] = NULLCHAR;
11056     ClearProgramStats();
11057     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11058
11059     ResetFrontEnd();
11060     ClearHighlights();
11061     flipView = appData.flipView;
11062     ClearPremoveHighlights();
11063     gotPremove = FALSE;
11064     alarmSounded = FALSE;
11065
11066     GameEnds(EndOfFile, NULL, GE_PLAYER);
11067     if(appData.serverMovesName != NULL) {
11068         /* [HGM] prepare to make moves file for broadcasting */
11069         clock_t t = clock();
11070         if(serverMoves != NULL) fclose(serverMoves);
11071         serverMoves = fopen(appData.serverMovesName, "r");
11072         if(serverMoves != NULL) {
11073             fclose(serverMoves);
11074             /* delay 15 sec before overwriting, so all clients can see end */
11075             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11076         }
11077         serverMoves = fopen(appData.serverMovesName, "w");
11078     }
11079
11080     ExitAnalyzeMode();
11081     gameMode = BeginningOfGame;
11082     ModeHighlight();
11083     if(appData.icsActive) gameInfo.variant = VariantNormal;
11084     currentMove = forwardMostMove = backwardMostMove = 0;
11085     MarkTargetSquares(1);
11086     InitPosition(redraw);
11087     for (i = 0; i < MAX_MOVES; i++) {
11088         if (commentList[i] != NULL) {
11089             free(commentList[i]);
11090             commentList[i] = NULL;
11091         }
11092     }
11093     ResetClocks();
11094     timeRemaining[0][0] = whiteTimeRemaining;
11095     timeRemaining[1][0] = blackTimeRemaining;
11096
11097     if (first.pr == NoProc) {
11098         StartChessProgram(&first);
11099     }
11100     if (init) {
11101             InitChessProgram(&first, startedFromSetupPosition);
11102     }
11103     DisplayTitle("");
11104     DisplayMessage("", "");
11105     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11106     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11107     ClearMap();        // [HGM] exclude: invalidate map
11108 }
11109
11110 void
11111 AutoPlayGameLoop ()
11112 {
11113     for (;;) {
11114         if (!AutoPlayOneMove())
11115           return;
11116         if (matchMode || appData.timeDelay == 0)
11117           continue;
11118         if (appData.timeDelay < 0)
11119           return;
11120         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11121         break;
11122     }
11123 }
11124
11125 void
11126 AnalyzeNextGame()
11127 {
11128     ReloadGame(1); // next game
11129 }
11130
11131 int
11132 AutoPlayOneMove ()
11133 {
11134     int fromX, fromY, toX, toY;
11135
11136     if (appData.debugMode) {
11137       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11138     }
11139
11140     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11141       return FALSE;
11142
11143     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
11144       pvInfoList[currentMove].depth = programStats.depth;
11145       pvInfoList[currentMove].score = programStats.score;
11146       pvInfoList[currentMove].time  = 0;
11147       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11148     }
11149
11150     if (currentMove >= forwardMostMove) {
11151       if(gameMode == AnalyzeFile) {
11152           if(appData.loadGameIndex == -1) {
11153             GameEnds(EndOfFile, NULL, GE_FILE);
11154           ScheduleDelayedEvent(AnalyzeNextGame, 10);
11155           } else {
11156           ExitAnalyzeMode(); SendToProgram("force\n", &first);
11157         }
11158       }
11159 //      gameMode = EndOfGame;
11160 //      ModeHighlight();
11161
11162       /* [AS] Clear current move marker at the end of a game */
11163       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11164
11165       return FALSE;
11166     }
11167
11168     toX = moveList[currentMove][2] - AAA;
11169     toY = moveList[currentMove][3] - ONE;
11170
11171     if (moveList[currentMove][1] == '@') {
11172         if (appData.highlightLastMove) {
11173             SetHighlights(-1, -1, toX, toY);
11174         }
11175     } else {
11176         fromX = moveList[currentMove][0] - AAA;
11177         fromY = moveList[currentMove][1] - ONE;
11178
11179         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11180
11181         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11182
11183         if (appData.highlightLastMove) {
11184             SetHighlights(fromX, fromY, toX, toY);
11185         }
11186     }
11187     DisplayMove(currentMove);
11188     SendMoveToProgram(currentMove++, &first);
11189     DisplayBothClocks();
11190     DrawPosition(FALSE, boards[currentMove]);
11191     // [HGM] PV info: always display, routine tests if empty
11192     DisplayComment(currentMove - 1, commentList[currentMove]);
11193     return TRUE;
11194 }
11195
11196
11197 int
11198 LoadGameOneMove (ChessMove readAhead)
11199 {
11200     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11201     char promoChar = NULLCHAR;
11202     ChessMove moveType;
11203     char move[MSG_SIZ];
11204     char *p, *q;
11205
11206     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11207         gameMode != AnalyzeMode && gameMode != Training) {
11208         gameFileFP = NULL;
11209         return FALSE;
11210     }
11211
11212     yyboardindex = forwardMostMove;
11213     if (readAhead != EndOfFile) {
11214       moveType = readAhead;
11215     } else {
11216       if (gameFileFP == NULL)
11217           return FALSE;
11218       moveType = (ChessMove) Myylex();
11219     }
11220
11221     done = FALSE;
11222     switch (moveType) {
11223       case Comment:
11224         if (appData.debugMode)
11225           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11226         p = yy_text;
11227
11228         /* append the comment but don't display it */
11229         AppendComment(currentMove, p, FALSE);
11230         return TRUE;
11231
11232       case WhiteCapturesEnPassant:
11233       case BlackCapturesEnPassant:
11234       case WhitePromotion:
11235       case BlackPromotion:
11236       case WhiteNonPromotion:
11237       case BlackNonPromotion:
11238       case NormalMove:
11239       case WhiteKingSideCastle:
11240       case WhiteQueenSideCastle:
11241       case BlackKingSideCastle:
11242       case BlackQueenSideCastle:
11243       case WhiteKingSideCastleWild:
11244       case WhiteQueenSideCastleWild:
11245       case BlackKingSideCastleWild:
11246       case BlackQueenSideCastleWild:
11247       /* PUSH Fabien */
11248       case WhiteHSideCastleFR:
11249       case WhiteASideCastleFR:
11250       case BlackHSideCastleFR:
11251       case BlackASideCastleFR:
11252       /* POP Fabien */
11253         if (appData.debugMode)
11254           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11255         fromX = currentMoveString[0] - AAA;
11256         fromY = currentMoveString[1] - ONE;
11257         toX = currentMoveString[2] - AAA;
11258         toY = currentMoveString[3] - ONE;
11259         promoChar = currentMoveString[4];
11260         break;
11261
11262       case WhiteDrop:
11263       case BlackDrop:
11264         if (appData.debugMode)
11265           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11266         fromX = moveType == WhiteDrop ?
11267           (int) CharToPiece(ToUpper(currentMoveString[0])) :
11268         (int) CharToPiece(ToLower(currentMoveString[0]));
11269         fromY = DROP_RANK;
11270         toX = currentMoveString[2] - AAA;
11271         toY = currentMoveString[3] - ONE;
11272         break;
11273
11274       case WhiteWins:
11275       case BlackWins:
11276       case GameIsDrawn:
11277       case GameUnfinished:
11278         if (appData.debugMode)
11279           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11280         p = strchr(yy_text, '{');
11281         if (p == NULL) p = strchr(yy_text, '(');
11282         if (p == NULL) {
11283             p = yy_text;
11284             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11285         } else {
11286             q = strchr(p, *p == '{' ? '}' : ')');
11287             if (q != NULL) *q = NULLCHAR;
11288             p++;
11289         }
11290         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11291         GameEnds(moveType, p, GE_FILE);
11292         done = TRUE;
11293         if (cmailMsgLoaded) {
11294             ClearHighlights();
11295             flipView = WhiteOnMove(currentMove);
11296             if (moveType == GameUnfinished) flipView = !flipView;
11297             if (appData.debugMode)
11298               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11299         }
11300         break;
11301
11302       case EndOfFile:
11303         if (appData.debugMode)
11304           fprintf(debugFP, "Parser hit end of file\n");
11305         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11306           case MT_NONE:
11307           case MT_CHECK:
11308             break;
11309           case MT_CHECKMATE:
11310           case MT_STAINMATE:
11311             if (WhiteOnMove(currentMove)) {
11312                 GameEnds(BlackWins, "Black mates", GE_FILE);
11313             } else {
11314                 GameEnds(WhiteWins, "White mates", GE_FILE);
11315             }
11316             break;
11317           case MT_STALEMATE:
11318             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11319             break;
11320         }
11321         done = TRUE;
11322         break;
11323
11324       case MoveNumberOne:
11325         if (lastLoadGameStart == GNUChessGame) {
11326             /* GNUChessGames have numbers, but they aren't move numbers */
11327             if (appData.debugMode)
11328               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11329                       yy_text, (int) moveType);
11330             return LoadGameOneMove(EndOfFile); /* tail recursion */
11331         }
11332         /* else fall thru */
11333
11334       case XBoardGame:
11335       case GNUChessGame:
11336       case PGNTag:
11337         /* Reached start of next game in file */
11338         if (appData.debugMode)
11339           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11340         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11341           case MT_NONE:
11342           case MT_CHECK:
11343             break;
11344           case MT_CHECKMATE:
11345           case MT_STAINMATE:
11346             if (WhiteOnMove(currentMove)) {
11347                 GameEnds(BlackWins, "Black mates", GE_FILE);
11348             } else {
11349                 GameEnds(WhiteWins, "White mates", GE_FILE);
11350             }
11351             break;
11352           case MT_STALEMATE:
11353             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11354             break;
11355         }
11356         done = TRUE;
11357         break;
11358
11359       case PositionDiagram:     /* should not happen; ignore */
11360       case ElapsedTime:         /* ignore */
11361       case NAG:                 /* ignore */
11362         if (appData.debugMode)
11363           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11364                   yy_text, (int) moveType);
11365         return LoadGameOneMove(EndOfFile); /* tail recursion */
11366
11367       case IllegalMove:
11368         if (appData.testLegality) {
11369             if (appData.debugMode)
11370               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11371             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11372                     (forwardMostMove / 2) + 1,
11373                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11374             DisplayError(move, 0);
11375             done = TRUE;
11376         } else {
11377             if (appData.debugMode)
11378               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11379                       yy_text, currentMoveString);
11380             fromX = currentMoveString[0] - AAA;
11381             fromY = currentMoveString[1] - ONE;
11382             toX = currentMoveString[2] - AAA;
11383             toY = currentMoveString[3] - ONE;
11384             promoChar = currentMoveString[4];
11385         }
11386         break;
11387
11388       case AmbiguousMove:
11389         if (appData.debugMode)
11390           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11391         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11392                 (forwardMostMove / 2) + 1,
11393                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11394         DisplayError(move, 0);
11395         done = TRUE;
11396         break;
11397
11398       default:
11399       case ImpossibleMove:
11400         if (appData.debugMode)
11401           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11402         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11403                 (forwardMostMove / 2) + 1,
11404                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11405         DisplayError(move, 0);
11406         done = TRUE;
11407         break;
11408     }
11409
11410     if (done) {
11411         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11412             DrawPosition(FALSE, boards[currentMove]);
11413             DisplayBothClocks();
11414             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11415               DisplayComment(currentMove - 1, commentList[currentMove]);
11416         }
11417         (void) StopLoadGameTimer();
11418         gameFileFP = NULL;
11419         cmailOldMove = forwardMostMove;
11420         return FALSE;
11421     } else {
11422         /* currentMoveString is set as a side-effect of yylex */
11423
11424         thinkOutput[0] = NULLCHAR;
11425         MakeMove(fromX, fromY, toX, toY, promoChar);
11426         currentMove = forwardMostMove;
11427         return TRUE;
11428     }
11429 }
11430
11431 /* Load the nth game from the given file */
11432 int
11433 LoadGameFromFile (char *filename, int n, char *title, int useList)
11434 {
11435     FILE *f;
11436     char buf[MSG_SIZ];
11437
11438     if (strcmp(filename, "-") == 0) {
11439         f = stdin;
11440         title = "stdin";
11441     } else {
11442         f = fopen(filename, "rb");
11443         if (f == NULL) {
11444           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
11445             DisplayError(buf, errno);
11446             return FALSE;
11447         }
11448     }
11449     if (fseek(f, 0, 0) == -1) {
11450         /* f is not seekable; probably a pipe */
11451         useList = FALSE;
11452     }
11453     if (useList && n == 0) {
11454         int error = GameListBuild(f);
11455         if (error) {
11456             DisplayError(_("Cannot build game list"), error);
11457         } else if (!ListEmpty(&gameList) &&
11458                    ((ListGame *) gameList.tailPred)->number > 1) {
11459             GameListPopUp(f, title);
11460             return TRUE;
11461         }
11462         GameListDestroy();
11463         n = 1;
11464     }
11465     if (n == 0) n = 1;
11466     return LoadGame(f, n, title, FALSE);
11467 }
11468
11469
11470 void
11471 MakeRegisteredMove ()
11472 {
11473     int fromX, fromY, toX, toY;
11474     char promoChar;
11475     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11476         switch (cmailMoveType[lastLoadGameNumber - 1]) {
11477           case CMAIL_MOVE:
11478           case CMAIL_DRAW:
11479             if (appData.debugMode)
11480               fprintf(debugFP, "Restoring %s for game %d\n",
11481                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11482
11483             thinkOutput[0] = NULLCHAR;
11484             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11485             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11486             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11487             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11488             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11489             promoChar = cmailMove[lastLoadGameNumber - 1][4];
11490             MakeMove(fromX, fromY, toX, toY, promoChar);
11491             ShowMove(fromX, fromY, toX, toY);
11492
11493             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11494               case MT_NONE:
11495               case MT_CHECK:
11496                 break;
11497
11498               case MT_CHECKMATE:
11499               case MT_STAINMATE:
11500                 if (WhiteOnMove(currentMove)) {
11501                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
11502                 } else {
11503                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
11504                 }
11505                 break;
11506
11507               case MT_STALEMATE:
11508                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11509                 break;
11510             }
11511
11512             break;
11513
11514           case CMAIL_RESIGN:
11515             if (WhiteOnMove(currentMove)) {
11516                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11517             } else {
11518                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11519             }
11520             break;
11521
11522           case CMAIL_ACCEPT:
11523             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11524             break;
11525
11526           default:
11527             break;
11528         }
11529     }
11530
11531     return;
11532 }
11533
11534 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11535 int
11536 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11537 {
11538     int retVal;
11539
11540     if (gameNumber > nCmailGames) {
11541         DisplayError(_("No more games in this message"), 0);
11542         return FALSE;
11543     }
11544     if (f == lastLoadGameFP) {
11545         int offset = gameNumber - lastLoadGameNumber;
11546         if (offset == 0) {
11547             cmailMsg[0] = NULLCHAR;
11548             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11549                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11550                 nCmailMovesRegistered--;
11551             }
11552             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11553             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11554                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11555             }
11556         } else {
11557             if (! RegisterMove()) return FALSE;
11558         }
11559     }
11560
11561     retVal = LoadGame(f, gameNumber, title, useList);
11562
11563     /* Make move registered during previous look at this game, if any */
11564     MakeRegisteredMove();
11565
11566     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11567         commentList[currentMove]
11568           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11569         DisplayComment(currentMove - 1, commentList[currentMove]);
11570     }
11571
11572     return retVal;
11573 }
11574
11575 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11576 int
11577 ReloadGame (int offset)
11578 {
11579     int gameNumber = lastLoadGameNumber + offset;
11580     if (lastLoadGameFP == NULL) {
11581         DisplayError(_("No game has been loaded yet"), 0);
11582         return FALSE;
11583     }
11584     if (gameNumber <= 0) {
11585         DisplayError(_("Can't back up any further"), 0);
11586         return FALSE;
11587     }
11588     if (cmailMsgLoaded) {
11589         return CmailLoadGame(lastLoadGameFP, gameNumber,
11590                              lastLoadGameTitle, lastLoadGameUseList);
11591     } else {
11592         return LoadGame(lastLoadGameFP, gameNumber,
11593                         lastLoadGameTitle, lastLoadGameUseList);
11594     }
11595 }
11596
11597 int keys[EmptySquare+1];
11598
11599 int
11600 PositionMatches (Board b1, Board b2)
11601 {
11602     int r, f, sum=0;
11603     switch(appData.searchMode) {
11604         case 1: return CompareWithRights(b1, b2);
11605         case 2:
11606             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11607                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11608             }
11609             return TRUE;
11610         case 3:
11611             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11612               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11613                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11614             }
11615             return sum==0;
11616         case 4:
11617             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11618                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11619             }
11620             return sum==0;
11621     }
11622     return TRUE;
11623 }
11624
11625 #define Q_PROMO  4
11626 #define Q_EP     3
11627 #define Q_BCASTL 2
11628 #define Q_WCASTL 1
11629
11630 int pieceList[256], quickBoard[256];
11631 ChessSquare pieceType[256] = { EmptySquare };
11632 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11633 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11634 int soughtTotal, turn;
11635 Boolean epOK, flipSearch;
11636
11637 typedef struct {
11638     unsigned char piece, to;
11639 } Move;
11640
11641 #define DSIZE (250000)
11642
11643 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11644 Move *moveDatabase = initialSpace;
11645 unsigned int movePtr, dataSize = DSIZE;
11646
11647 int
11648 MakePieceList (Board board, int *counts)
11649 {
11650     int r, f, n=Q_PROMO, total=0;
11651     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11652     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11653         int sq = f + (r<<4);
11654         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
11655             quickBoard[sq] = ++n;
11656             pieceList[n] = sq;
11657             pieceType[n] = board[r][f];
11658             counts[board[r][f]]++;
11659             if(board[r][f] == WhiteKing) pieceList[1] = n; else
11660             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
11661             total++;
11662         }
11663     }
11664     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
11665     return total;
11666 }
11667
11668 void
11669 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
11670 {
11671     int sq = fromX + (fromY<<4);
11672     int piece = quickBoard[sq];
11673     quickBoard[sq] = 0;
11674     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
11675     if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11676         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
11677         moveDatabase[movePtr++].piece = Q_WCASTL;
11678         quickBoard[sq] = piece;
11679         piece = quickBoard[from]; quickBoard[from] = 0;
11680         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11681     } else
11682     if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11683         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
11684         moveDatabase[movePtr++].piece = Q_BCASTL;
11685         quickBoard[sq] = piece;
11686         piece = quickBoard[from]; quickBoard[from] = 0;
11687         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11688     } else
11689     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
11690         quickBoard[(fromY<<4)+toX] = 0;
11691         moveDatabase[movePtr].piece = Q_EP;
11692         moveDatabase[movePtr++].to = (fromY<<4)+toX;
11693         moveDatabase[movePtr].to = sq;
11694     } else
11695     if(promoPiece != pieceType[piece]) {
11696         moveDatabase[movePtr++].piece = Q_PROMO;
11697         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
11698     }
11699     moveDatabase[movePtr].piece = piece;
11700     quickBoard[sq] = piece;
11701     movePtr++;
11702 }
11703
11704 int
11705 PackGame (Board board)
11706 {
11707     Move *newSpace = NULL;
11708     moveDatabase[movePtr].piece = 0; // terminate previous game
11709     if(movePtr > dataSize) {
11710         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
11711         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
11712         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
11713         if(newSpace) {
11714             int i;
11715             Move *p = moveDatabase, *q = newSpace;
11716             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
11717             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
11718             moveDatabase = newSpace;
11719         } else { // calloc failed, we must be out of memory. Too bad...
11720             dataSize = 0; // prevent calloc events for all subsequent games
11721             return 0;     // and signal this one isn't cached
11722         }
11723     }
11724     movePtr++;
11725     MakePieceList(board, counts);
11726     return movePtr;
11727 }
11728
11729 int
11730 QuickCompare (Board board, int *minCounts, int *maxCounts)
11731 {   // compare according to search mode
11732     int r, f;
11733     switch(appData.searchMode)
11734     {
11735       case 1: // exact position match
11736         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
11737         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11738             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11739         }
11740         break;
11741       case 2: // can have extra material on empty squares
11742         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11743             if(board[r][f] == EmptySquare) continue;
11744             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11745         }
11746         break;
11747       case 3: // material with exact Pawn structure
11748         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11749             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
11750             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11751         } // fall through to material comparison
11752       case 4: // exact material
11753         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
11754         break;
11755       case 6: // material range with given imbalance
11756         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
11757         // fall through to range comparison
11758       case 5: // material range
11759         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
11760     }
11761     return TRUE;
11762 }
11763
11764 int
11765 QuickScan (Board board, Move *move)
11766 {   // reconstruct game,and compare all positions in it
11767     int cnt=0, stretch=0, total = MakePieceList(board, counts);
11768     do {
11769         int piece = move->piece;
11770         int to = move->to, from = pieceList[piece];
11771         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
11772           if(!piece) return -1;
11773           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
11774             piece = (++move)->piece;
11775             from = pieceList[piece];
11776             counts[pieceType[piece]]--;
11777             pieceType[piece] = (ChessSquare) move->to;
11778             counts[move->to]++;
11779           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
11780             counts[pieceType[quickBoard[to]]]--;
11781             quickBoard[to] = 0; total--;
11782             move++;
11783             continue;
11784           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
11785             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
11786             from  = pieceList[piece]; // so this must be King
11787             quickBoard[from] = 0;
11788             pieceList[piece] = to;
11789             from = pieceList[(++move)->piece]; // for FRC this has to be done here
11790             quickBoard[from] = 0; // rook
11791             quickBoard[to] = piece;
11792             to = move->to; piece = move->piece;
11793             goto aftercastle;
11794           }
11795         }
11796         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
11797         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
11798         quickBoard[from] = 0;
11799       aftercastle:
11800         quickBoard[to] = piece;
11801         pieceList[piece] = to;
11802         cnt++; turn ^= 3;
11803         if(QuickCompare(soughtBoard, minSought, maxSought) ||
11804            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
11805            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
11806                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
11807           ) {
11808             static int lastCounts[EmptySquare+1];
11809             int i;
11810             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
11811             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
11812         } else stretch = 0;
11813         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
11814         move++;
11815     } while(1);
11816 }
11817
11818 void
11819 InitSearch ()
11820 {
11821     int r, f;
11822     flipSearch = FALSE;
11823     CopyBoard(soughtBoard, boards[currentMove]);
11824     soughtTotal = MakePieceList(soughtBoard, maxSought);
11825     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
11826     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
11827     CopyBoard(reverseBoard, boards[currentMove]);
11828     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11829         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
11830         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
11831         reverseBoard[r][f] = piece;
11832     }
11833     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
11834     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
11835     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
11836                  || (boards[currentMove][CASTLING][2] == NoRights ||
11837                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
11838                  && (boards[currentMove][CASTLING][5] == NoRights ||
11839                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
11840       ) {
11841         flipSearch = TRUE;
11842         CopyBoard(flipBoard, soughtBoard);
11843         CopyBoard(rotateBoard, reverseBoard);
11844         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11845             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
11846             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
11847         }
11848     }
11849     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
11850     if(appData.searchMode >= 5) {
11851         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
11852         MakePieceList(soughtBoard, minSought);
11853         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
11854     }
11855     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
11856         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
11857 }
11858
11859 GameInfo dummyInfo;
11860 static int creatingBook;
11861
11862 int
11863 GameContainsPosition (FILE *f, ListGame *lg)
11864 {
11865     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
11866     int fromX, fromY, toX, toY;
11867     char promoChar;
11868     static int initDone=FALSE;
11869
11870     // weed out games based on numerical tag comparison
11871     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
11872     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
11873     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
11874     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
11875     if(!initDone) {
11876         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
11877         initDone = TRUE;
11878     }
11879     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen);
11880     else CopyBoard(boards[scratch], initialPosition); // default start position
11881     if(lg->moves) {
11882         turn = btm + 1;
11883         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
11884         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
11885     }
11886     if(btm) plyNr++;
11887     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11888     fseek(f, lg->offset, 0);
11889     yynewfile(f);
11890     while(1) {
11891         yyboardindex = scratch;
11892         quickFlag = plyNr+1;
11893         next = Myylex();
11894         quickFlag = 0;
11895         switch(next) {
11896             case PGNTag:
11897                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
11898             default:
11899                 continue;
11900
11901             case XBoardGame:
11902             case GNUChessGame:
11903                 if(plyNr) return -1; // after we have seen moves, this is for new game
11904               continue;
11905
11906             case AmbiguousMove: // we cannot reconstruct the game beyond these two
11907             case ImpossibleMove:
11908             case WhiteWins: // game ends here with these four
11909             case BlackWins:
11910             case GameIsDrawn:
11911             case GameUnfinished:
11912                 return -1;
11913
11914             case IllegalMove:
11915                 if(appData.testLegality) return -1;
11916             case WhiteCapturesEnPassant:
11917             case BlackCapturesEnPassant:
11918             case WhitePromotion:
11919             case BlackPromotion:
11920             case WhiteNonPromotion:
11921             case BlackNonPromotion:
11922             case NormalMove:
11923             case WhiteKingSideCastle:
11924             case WhiteQueenSideCastle:
11925             case BlackKingSideCastle:
11926             case BlackQueenSideCastle:
11927             case WhiteKingSideCastleWild:
11928             case WhiteQueenSideCastleWild:
11929             case BlackKingSideCastleWild:
11930             case BlackQueenSideCastleWild:
11931             case WhiteHSideCastleFR:
11932             case WhiteASideCastleFR:
11933             case BlackHSideCastleFR:
11934             case BlackASideCastleFR:
11935                 fromX = currentMoveString[0] - AAA;
11936                 fromY = currentMoveString[1] - ONE;
11937                 toX = currentMoveString[2] - AAA;
11938                 toY = currentMoveString[3] - ONE;
11939                 promoChar = currentMoveString[4];
11940                 break;
11941             case WhiteDrop:
11942             case BlackDrop:
11943                 fromX = next == WhiteDrop ?
11944                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
11945                   (int) CharToPiece(ToLower(currentMoveString[0]));
11946                 fromY = DROP_RANK;
11947                 toX = currentMoveString[2] - AAA;
11948                 toY = currentMoveString[3] - ONE;
11949                 promoChar = 0;
11950                 break;
11951         }
11952         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
11953         plyNr++;
11954         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
11955         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11956         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
11957         if(appData.findMirror) {
11958             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
11959             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
11960         }
11961     }
11962 }
11963
11964 /* Load the nth game from open file f */
11965 int
11966 LoadGame (FILE *f, int gameNumber, char *title, int useList)
11967 {
11968     ChessMove cm;
11969     char buf[MSG_SIZ];
11970     int gn = gameNumber;
11971     ListGame *lg = NULL;
11972     int numPGNTags = 0;
11973     int err, pos = -1;
11974     GameMode oldGameMode;
11975     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
11976
11977     if (appData.debugMode)
11978         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
11979
11980     if (gameMode == Training )
11981         SetTrainingModeOff();
11982
11983     oldGameMode = gameMode;
11984     if (gameMode != BeginningOfGame) {
11985       Reset(FALSE, TRUE);
11986     }
11987
11988     gameFileFP = f;
11989     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
11990         fclose(lastLoadGameFP);
11991     }
11992
11993     if (useList) {
11994         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
11995
11996         if (lg) {
11997             fseek(f, lg->offset, 0);
11998             GameListHighlight(gameNumber);
11999             pos = lg->position;
12000             gn = 1;
12001         }
12002         else {
12003             if(gameMode == AnalyzeFile && appData.loadGameIndex == -1)
12004               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12005             else
12006             DisplayError(_("Game number out of range"), 0);
12007             return FALSE;
12008         }
12009     } else {
12010         GameListDestroy();
12011         if (fseek(f, 0, 0) == -1) {
12012             if (f == lastLoadGameFP ?
12013                 gameNumber == lastLoadGameNumber + 1 :
12014                 gameNumber == 1) {
12015                 gn = 1;
12016             } else {
12017                 DisplayError(_("Can't seek on game file"), 0);
12018                 return FALSE;
12019             }
12020         }
12021     }
12022     lastLoadGameFP = f;
12023     lastLoadGameNumber = gameNumber;
12024     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12025     lastLoadGameUseList = useList;
12026
12027     yynewfile(f);
12028
12029     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12030       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12031                 lg->gameInfo.black);
12032             DisplayTitle(buf);
12033     } else if (*title != NULLCHAR) {
12034         if (gameNumber > 1) {
12035           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12036             DisplayTitle(buf);
12037         } else {
12038             DisplayTitle(title);
12039         }
12040     }
12041
12042     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12043         gameMode = PlayFromGameFile;
12044         ModeHighlight();
12045     }
12046
12047     currentMove = forwardMostMove = backwardMostMove = 0;
12048     CopyBoard(boards[0], initialPosition);
12049     StopClocks();
12050
12051     /*
12052      * Skip the first gn-1 games in the file.
12053      * Also skip over anything that precedes an identifiable
12054      * start of game marker, to avoid being confused by
12055      * garbage at the start of the file.  Currently
12056      * recognized start of game markers are the move number "1",
12057      * the pattern "gnuchess .* game", the pattern
12058      * "^[#;%] [^ ]* game file", and a PGN tag block.
12059      * A game that starts with one of the latter two patterns
12060      * will also have a move number 1, possibly
12061      * following a position diagram.
12062      * 5-4-02: Let's try being more lenient and allowing a game to
12063      * start with an unnumbered move.  Does that break anything?
12064      */
12065     cm = lastLoadGameStart = EndOfFile;
12066     while (gn > 0) {
12067         yyboardindex = forwardMostMove;
12068         cm = (ChessMove) Myylex();
12069         switch (cm) {
12070           case EndOfFile:
12071             if (cmailMsgLoaded) {
12072                 nCmailGames = CMAIL_MAX_GAMES - gn;
12073             } else {
12074                 Reset(TRUE, TRUE);
12075                 DisplayError(_("Game not found in file"), 0);
12076             }
12077             return FALSE;
12078
12079           case GNUChessGame:
12080           case XBoardGame:
12081             gn--;
12082             lastLoadGameStart = cm;
12083             break;
12084
12085           case MoveNumberOne:
12086             switch (lastLoadGameStart) {
12087               case GNUChessGame:
12088               case XBoardGame:
12089               case PGNTag:
12090                 break;
12091               case MoveNumberOne:
12092               case EndOfFile:
12093                 gn--;           /* count this game */
12094                 lastLoadGameStart = cm;
12095                 break;
12096               default:
12097                 /* impossible */
12098                 break;
12099             }
12100             break;
12101
12102           case PGNTag:
12103             switch (lastLoadGameStart) {
12104               case GNUChessGame:
12105               case PGNTag:
12106               case MoveNumberOne:
12107               case EndOfFile:
12108                 gn--;           /* count this game */
12109                 lastLoadGameStart = cm;
12110                 break;
12111               case XBoardGame:
12112                 lastLoadGameStart = cm; /* game counted already */
12113                 break;
12114               default:
12115                 /* impossible */
12116                 break;
12117             }
12118             if (gn > 0) {
12119                 do {
12120                     yyboardindex = forwardMostMove;
12121                     cm = (ChessMove) Myylex();
12122                 } while (cm == PGNTag || cm == Comment);
12123             }
12124             break;
12125
12126           case WhiteWins:
12127           case BlackWins:
12128           case GameIsDrawn:
12129             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
12130                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
12131                     != CMAIL_OLD_RESULT) {
12132                     nCmailResults ++ ;
12133                     cmailResult[  CMAIL_MAX_GAMES
12134                                 - gn - 1] = CMAIL_OLD_RESULT;
12135                 }
12136             }
12137             break;
12138
12139           case NormalMove:
12140             /* Only a NormalMove can be at the start of a game
12141              * without a position diagram. */
12142             if (lastLoadGameStart == EndOfFile ) {
12143               gn--;
12144               lastLoadGameStart = MoveNumberOne;
12145             }
12146             break;
12147
12148           default:
12149             break;
12150         }
12151     }
12152
12153     if (appData.debugMode)
12154       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
12155
12156     if (cm == XBoardGame) {
12157         /* Skip any header junk before position diagram and/or move 1 */
12158         for (;;) {
12159             yyboardindex = forwardMostMove;
12160             cm = (ChessMove) Myylex();
12161
12162             if (cm == EndOfFile ||
12163                 cm == GNUChessGame || cm == XBoardGame) {
12164                 /* Empty game; pretend end-of-file and handle later */
12165                 cm = EndOfFile;
12166                 break;
12167             }
12168
12169             if (cm == MoveNumberOne || cm == PositionDiagram ||
12170                 cm == PGNTag || cm == Comment)
12171               break;
12172         }
12173     } else if (cm == GNUChessGame) {
12174         if (gameInfo.event != NULL) {
12175             free(gameInfo.event);
12176         }
12177         gameInfo.event = StrSave(yy_text);
12178     }
12179
12180     startedFromSetupPosition = FALSE;
12181     while (cm == PGNTag) {
12182         if (appData.debugMode)
12183           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
12184         err = ParsePGNTag(yy_text, &gameInfo);
12185         if (!err) numPGNTags++;
12186
12187         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
12188         if(gameInfo.variant != oldVariant) {
12189             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
12190             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
12191             InitPosition(TRUE);
12192             oldVariant = gameInfo.variant;
12193             if (appData.debugMode)
12194               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
12195         }
12196
12197
12198         if (gameInfo.fen != NULL) {
12199           Board initial_position;
12200           startedFromSetupPosition = TRUE;
12201           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
12202             Reset(TRUE, TRUE);
12203             DisplayError(_("Bad FEN position in file"), 0);
12204             return FALSE;
12205           }
12206           CopyBoard(boards[0], initial_position);
12207           if (blackPlaysFirst) {
12208             currentMove = forwardMostMove = backwardMostMove = 1;
12209             CopyBoard(boards[1], initial_position);
12210             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12211             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12212             timeRemaining[0][1] = whiteTimeRemaining;
12213             timeRemaining[1][1] = blackTimeRemaining;
12214             if (commentList[0] != NULL) {
12215               commentList[1] = commentList[0];
12216               commentList[0] = NULL;
12217             }
12218           } else {
12219             currentMove = forwardMostMove = backwardMostMove = 0;
12220           }
12221           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12222           {   int i;
12223               initialRulePlies = FENrulePlies;
12224               for( i=0; i< nrCastlingRights; i++ )
12225                   initialRights[i] = initial_position[CASTLING][i];
12226           }
12227           yyboardindex = forwardMostMove;
12228           free(gameInfo.fen);
12229           gameInfo.fen = NULL;
12230         }
12231
12232         yyboardindex = forwardMostMove;
12233         cm = (ChessMove) Myylex();
12234
12235         /* Handle comments interspersed among the tags */
12236         while (cm == Comment) {
12237             char *p;
12238             if (appData.debugMode)
12239               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12240             p = yy_text;
12241             AppendComment(currentMove, p, FALSE);
12242             yyboardindex = forwardMostMove;
12243             cm = (ChessMove) Myylex();
12244         }
12245     }
12246
12247     /* don't rely on existence of Event tag since if game was
12248      * pasted from clipboard the Event tag may not exist
12249      */
12250     if (numPGNTags > 0){
12251         char *tags;
12252         if (gameInfo.variant == VariantNormal) {
12253           VariantClass v = StringToVariant(gameInfo.event);
12254           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12255           if(v < VariantShogi) gameInfo.variant = v;
12256         }
12257         if (!matchMode) {
12258           if( appData.autoDisplayTags ) {
12259             tags = PGNTags(&gameInfo);
12260             TagsPopUp(tags, CmailMsg());
12261             free(tags);
12262           }
12263         }
12264     } else {
12265         /* Make something up, but don't display it now */
12266         SetGameInfo();
12267         TagsPopDown();
12268     }
12269
12270     if (cm == PositionDiagram) {
12271         int i, j;
12272         char *p;
12273         Board initial_position;
12274
12275         if (appData.debugMode)
12276           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12277
12278         if (!startedFromSetupPosition) {
12279             p = yy_text;
12280             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12281               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12282                 switch (*p) {
12283                   case '{':
12284                   case '[':
12285                   case '-':
12286                   case ' ':
12287                   case '\t':
12288                   case '\n':
12289                   case '\r':
12290                     break;
12291                   default:
12292                     initial_position[i][j++] = CharToPiece(*p);
12293                     break;
12294                 }
12295             while (*p == ' ' || *p == '\t' ||
12296                    *p == '\n' || *p == '\r') p++;
12297
12298             if (strncmp(p, "black", strlen("black"))==0)
12299               blackPlaysFirst = TRUE;
12300             else
12301               blackPlaysFirst = FALSE;
12302             startedFromSetupPosition = TRUE;
12303
12304             CopyBoard(boards[0], initial_position);
12305             if (blackPlaysFirst) {
12306                 currentMove = forwardMostMove = backwardMostMove = 1;
12307                 CopyBoard(boards[1], initial_position);
12308                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12309                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12310                 timeRemaining[0][1] = whiteTimeRemaining;
12311                 timeRemaining[1][1] = blackTimeRemaining;
12312                 if (commentList[0] != NULL) {
12313                     commentList[1] = commentList[0];
12314                     commentList[0] = NULL;
12315                 }
12316             } else {
12317                 currentMove = forwardMostMove = backwardMostMove = 0;
12318             }
12319         }
12320         yyboardindex = forwardMostMove;
12321         cm = (ChessMove) Myylex();
12322     }
12323
12324   if(!creatingBook) {
12325     if (first.pr == NoProc) {
12326         StartChessProgram(&first);
12327     }
12328     InitChessProgram(&first, FALSE);
12329     SendToProgram("force\n", &first);
12330     if (startedFromSetupPosition) {
12331         SendBoard(&first, forwardMostMove);
12332     if (appData.debugMode) {
12333         fprintf(debugFP, "Load Game\n");
12334     }
12335         DisplayBothClocks();
12336     }
12337   }
12338
12339     /* [HGM] server: flag to write setup moves in broadcast file as one */
12340     loadFlag = appData.suppressLoadMoves;
12341
12342     while (cm == Comment) {
12343         char *p;
12344         if (appData.debugMode)
12345           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12346         p = yy_text;
12347         AppendComment(currentMove, p, FALSE);
12348         yyboardindex = forwardMostMove;
12349         cm = (ChessMove) Myylex();
12350     }
12351
12352     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12353         cm == WhiteWins || cm == BlackWins ||
12354         cm == GameIsDrawn || cm == GameUnfinished) {
12355         DisplayMessage("", _("No moves in game"));
12356         if (cmailMsgLoaded) {
12357             if (appData.debugMode)
12358               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12359             ClearHighlights();
12360             flipView = FALSE;
12361         }
12362         DrawPosition(FALSE, boards[currentMove]);
12363         DisplayBothClocks();
12364         gameMode = EditGame;
12365         ModeHighlight();
12366         gameFileFP = NULL;
12367         cmailOldMove = 0;
12368         return TRUE;
12369     }
12370
12371     // [HGM] PV info: routine tests if comment empty
12372     if (!matchMode && (pausing || appData.timeDelay != 0)) {
12373         DisplayComment(currentMove - 1, commentList[currentMove]);
12374     }
12375     if (!matchMode && appData.timeDelay != 0)
12376       DrawPosition(FALSE, boards[currentMove]);
12377
12378     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12379       programStats.ok_to_send = 1;
12380     }
12381
12382     /* if the first token after the PGN tags is a move
12383      * and not move number 1, retrieve it from the parser
12384      */
12385     if (cm != MoveNumberOne)
12386         LoadGameOneMove(cm);
12387
12388     /* load the remaining moves from the file */
12389     while (LoadGameOneMove(EndOfFile)) {
12390       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12391       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12392     }
12393
12394     /* rewind to the start of the game */
12395     currentMove = backwardMostMove;
12396
12397     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12398
12399     if (oldGameMode == AnalyzeFile ||
12400         oldGameMode == AnalyzeMode) {
12401       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
12402       AnalyzeFileEvent();
12403     }
12404
12405     if(creatingBook) return TRUE;
12406     if (!matchMode && pos > 0) {
12407         ToNrEvent(pos); // [HGM] no autoplay if selected on position
12408     } else
12409     if (matchMode || appData.timeDelay == 0) {
12410       ToEndEvent();
12411     } else if (appData.timeDelay > 0) {
12412       AutoPlayGameLoop();
12413     }
12414
12415     if (appData.debugMode)
12416         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
12417
12418     loadFlag = 0; /* [HGM] true game starts */
12419     return TRUE;
12420 }
12421
12422 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
12423 int
12424 ReloadPosition (int offset)
12425 {
12426     int positionNumber = lastLoadPositionNumber + offset;
12427     if (lastLoadPositionFP == NULL) {
12428         DisplayError(_("No position has been loaded yet"), 0);
12429         return FALSE;
12430     }
12431     if (positionNumber <= 0) {
12432         DisplayError(_("Can't back up any further"), 0);
12433         return FALSE;
12434     }
12435     return LoadPosition(lastLoadPositionFP, positionNumber,
12436                         lastLoadPositionTitle);
12437 }
12438
12439 /* Load the nth position from the given file */
12440 int
12441 LoadPositionFromFile (char *filename, int n, char *title)
12442 {
12443     FILE *f;
12444     char buf[MSG_SIZ];
12445
12446     if (strcmp(filename, "-") == 0) {
12447         return LoadPosition(stdin, n, "stdin");
12448     } else {
12449         f = fopen(filename, "rb");
12450         if (f == NULL) {
12451             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12452             DisplayError(buf, errno);
12453             return FALSE;
12454         } else {
12455             return LoadPosition(f, n, title);
12456         }
12457     }
12458 }
12459
12460 /* Load the nth position from the given open file, and close it */
12461 int
12462 LoadPosition (FILE *f, int positionNumber, char *title)
12463 {
12464     char *p, line[MSG_SIZ];
12465     Board initial_position;
12466     int i, j, fenMode, pn;
12467
12468     if (gameMode == Training )
12469         SetTrainingModeOff();
12470
12471     if (gameMode != BeginningOfGame) {
12472         Reset(FALSE, TRUE);
12473     }
12474     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12475         fclose(lastLoadPositionFP);
12476     }
12477     if (positionNumber == 0) positionNumber = 1;
12478     lastLoadPositionFP = f;
12479     lastLoadPositionNumber = positionNumber;
12480     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12481     if (first.pr == NoProc && !appData.noChessProgram) {
12482       StartChessProgram(&first);
12483       InitChessProgram(&first, FALSE);
12484     }
12485     pn = positionNumber;
12486     if (positionNumber < 0) {
12487         /* Negative position number means to seek to that byte offset */
12488         if (fseek(f, -positionNumber, 0) == -1) {
12489             DisplayError(_("Can't seek on position file"), 0);
12490             return FALSE;
12491         };
12492         pn = 1;
12493     } else {
12494         if (fseek(f, 0, 0) == -1) {
12495             if (f == lastLoadPositionFP ?
12496                 positionNumber == lastLoadPositionNumber + 1 :
12497                 positionNumber == 1) {
12498                 pn = 1;
12499             } else {
12500                 DisplayError(_("Can't seek on position file"), 0);
12501                 return FALSE;
12502             }
12503         }
12504     }
12505     /* See if this file is FEN or old-style xboard */
12506     if (fgets(line, MSG_SIZ, f) == NULL) {
12507         DisplayError(_("Position not found in file"), 0);
12508         return FALSE;
12509     }
12510     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12511     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12512
12513     if (pn >= 2) {
12514         if (fenMode || line[0] == '#') pn--;
12515         while (pn > 0) {
12516             /* skip positions before number pn */
12517             if (fgets(line, MSG_SIZ, f) == NULL) {
12518                 Reset(TRUE, TRUE);
12519                 DisplayError(_("Position not found in file"), 0);
12520                 return FALSE;
12521             }
12522             if (fenMode || line[0] == '#') pn--;
12523         }
12524     }
12525
12526     if (fenMode) {
12527         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
12528             DisplayError(_("Bad FEN position in file"), 0);
12529             return FALSE;
12530         }
12531     } else {
12532         (void) fgets(line, MSG_SIZ, f);
12533         (void) fgets(line, MSG_SIZ, f);
12534
12535         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12536             (void) fgets(line, MSG_SIZ, f);
12537             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12538                 if (*p == ' ')
12539                   continue;
12540                 initial_position[i][j++] = CharToPiece(*p);
12541             }
12542         }
12543
12544         blackPlaysFirst = FALSE;
12545         if (!feof(f)) {
12546             (void) fgets(line, MSG_SIZ, f);
12547             if (strncmp(line, "black", strlen("black"))==0)
12548               blackPlaysFirst = TRUE;
12549         }
12550     }
12551     startedFromSetupPosition = TRUE;
12552
12553     CopyBoard(boards[0], initial_position);
12554     if (blackPlaysFirst) {
12555         currentMove = forwardMostMove = backwardMostMove = 1;
12556         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12557         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12558         CopyBoard(boards[1], initial_position);
12559         DisplayMessage("", _("Black to play"));
12560     } else {
12561         currentMove = forwardMostMove = backwardMostMove = 0;
12562         DisplayMessage("", _("White to play"));
12563     }
12564     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12565     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12566         SendToProgram("force\n", &first);
12567         SendBoard(&first, forwardMostMove);
12568     }
12569     if (appData.debugMode) {
12570 int i, j;
12571   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12572   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12573         fprintf(debugFP, "Load Position\n");
12574     }
12575
12576     if (positionNumber > 1) {
12577       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12578         DisplayTitle(line);
12579     } else {
12580         DisplayTitle(title);
12581     }
12582     gameMode = EditGame;
12583     ModeHighlight();
12584     ResetClocks();
12585     timeRemaining[0][1] = whiteTimeRemaining;
12586     timeRemaining[1][1] = blackTimeRemaining;
12587     DrawPosition(FALSE, boards[currentMove]);
12588
12589     return TRUE;
12590 }
12591
12592
12593 void
12594 CopyPlayerNameIntoFileName (char **dest, char *src)
12595 {
12596     while (*src != NULLCHAR && *src != ',') {
12597         if (*src == ' ') {
12598             *(*dest)++ = '_';
12599             src++;
12600         } else {
12601             *(*dest)++ = *src++;
12602         }
12603     }
12604 }
12605
12606 char *
12607 DefaultFileName (char *ext)
12608 {
12609     static char def[MSG_SIZ];
12610     char *p;
12611
12612     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12613         p = def;
12614         CopyPlayerNameIntoFileName(&p, gameInfo.white);
12615         *p++ = '-';
12616         CopyPlayerNameIntoFileName(&p, gameInfo.black);
12617         *p++ = '.';
12618         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12619     } else {
12620         def[0] = NULLCHAR;
12621     }
12622     return def;
12623 }
12624
12625 /* Save the current game to the given file */
12626 int
12627 SaveGameToFile (char *filename, int append)
12628 {
12629     FILE *f;
12630     char buf[MSG_SIZ];
12631     int result, i, t,tot=0;
12632
12633     if (strcmp(filename, "-") == 0) {
12634         return SaveGame(stdout, 0, NULL);
12635     } else {
12636         for(i=0; i<10; i++) { // upto 10 tries
12637              f = fopen(filename, append ? "a" : "w");
12638              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12639              if(f || errno != 13) break;
12640              DoSleep(t = 5 + random()%11); // wait 5-15 msec
12641              tot += t;
12642         }
12643         if (f == NULL) {
12644             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12645             DisplayError(buf, errno);
12646             return FALSE;
12647         } else {
12648             safeStrCpy(buf, lastMsg, MSG_SIZ);
12649             DisplayMessage(_("Waiting for access to save file"), "");
12650             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
12651             DisplayMessage(_("Saving game"), "");
12652             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
12653             result = SaveGame(f, 0, NULL);
12654             DisplayMessage(buf, "");
12655             return result;
12656         }
12657     }
12658 }
12659
12660 char *
12661 SavePart (char *str)
12662 {
12663     static char buf[MSG_SIZ];
12664     char *p;
12665
12666     p = strchr(str, ' ');
12667     if (p == NULL) return str;
12668     strncpy(buf, str, p - str);
12669     buf[p - str] = NULLCHAR;
12670     return buf;
12671 }
12672
12673 #define PGN_MAX_LINE 75
12674
12675 #define PGN_SIDE_WHITE  0
12676 #define PGN_SIDE_BLACK  1
12677
12678 static int
12679 FindFirstMoveOutOfBook (int side)
12680 {
12681     int result = -1;
12682
12683     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12684         int index = backwardMostMove;
12685         int has_book_hit = 0;
12686
12687         if( (index % 2) != side ) {
12688             index++;
12689         }
12690
12691         while( index < forwardMostMove ) {
12692             /* Check to see if engine is in book */
12693             int depth = pvInfoList[index].depth;
12694             int score = pvInfoList[index].score;
12695             int in_book = 0;
12696
12697             if( depth <= 2 ) {
12698                 in_book = 1;
12699             }
12700             else if( score == 0 && depth == 63 ) {
12701                 in_book = 1; /* Zappa */
12702             }
12703             else if( score == 2 && depth == 99 ) {
12704                 in_book = 1; /* Abrok */
12705             }
12706
12707             has_book_hit += in_book;
12708
12709             if( ! in_book ) {
12710                 result = index;
12711
12712                 break;
12713             }
12714
12715             index += 2;
12716         }
12717     }
12718
12719     return result;
12720 }
12721
12722 void
12723 GetOutOfBookInfo (char * buf)
12724 {
12725     int oob[2];
12726     int i;
12727     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12728
12729     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12730     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12731
12732     *buf = '\0';
12733
12734     if( oob[0] >= 0 || oob[1] >= 0 ) {
12735         for( i=0; i<2; i++ ) {
12736             int idx = oob[i];
12737
12738             if( idx >= 0 ) {
12739                 if( i > 0 && oob[0] >= 0 ) {
12740                     strcat( buf, "   " );
12741                 }
12742
12743                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12744                 sprintf( buf+strlen(buf), "%s%.2f",
12745                     pvInfoList[idx].score >= 0 ? "+" : "",
12746                     pvInfoList[idx].score / 100.0 );
12747             }
12748         }
12749     }
12750 }
12751
12752 /* Save game in PGN style and close the file */
12753 int
12754 SaveGamePGN (FILE *f)
12755 {
12756     int i, offset, linelen, newblock;
12757 //    char *movetext;
12758     char numtext[32];
12759     int movelen, numlen, blank;
12760     char move_buffer[100]; /* [AS] Buffer for move+PV info */
12761
12762     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12763
12764     PrintPGNTags(f, &gameInfo);
12765
12766     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
12767
12768     if (backwardMostMove > 0 || startedFromSetupPosition) {
12769         char *fen = PositionToFEN(backwardMostMove, NULL);
12770         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12771         fprintf(f, "\n{--------------\n");
12772         PrintPosition(f, backwardMostMove);
12773         fprintf(f, "--------------}\n");
12774         free(fen);
12775     }
12776     else {
12777         /* [AS] Out of book annotation */
12778         if( appData.saveOutOfBookInfo ) {
12779             char buf[64];
12780
12781             GetOutOfBookInfo( buf );
12782
12783             if( buf[0] != '\0' ) {
12784                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12785             }
12786         }
12787
12788         fprintf(f, "\n");
12789     }
12790
12791     i = backwardMostMove;
12792     linelen = 0;
12793     newblock = TRUE;
12794
12795     while (i < forwardMostMove) {
12796         /* Print comments preceding this move */
12797         if (commentList[i] != NULL) {
12798             if (linelen > 0) fprintf(f, "\n");
12799             fprintf(f, "%s", commentList[i]);
12800             linelen = 0;
12801             newblock = TRUE;
12802         }
12803
12804         /* Format move number */
12805         if ((i % 2) == 0)
12806           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
12807         else
12808           if (newblock)
12809             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
12810           else
12811             numtext[0] = NULLCHAR;
12812
12813         numlen = strlen(numtext);
12814         newblock = FALSE;
12815
12816         /* Print move number */
12817         blank = linelen > 0 && numlen > 0;
12818         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
12819             fprintf(f, "\n");
12820             linelen = 0;
12821             blank = 0;
12822         }
12823         if (blank) {
12824             fprintf(f, " ");
12825             linelen++;
12826         }
12827         fprintf(f, "%s", numtext);
12828         linelen += numlen;
12829
12830         /* Get move */
12831         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
12832         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
12833
12834         /* Print move */
12835         blank = linelen > 0 && movelen > 0;
12836         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12837             fprintf(f, "\n");
12838             linelen = 0;
12839             blank = 0;
12840         }
12841         if (blank) {
12842             fprintf(f, " ");
12843             linelen++;
12844         }
12845         fprintf(f, "%s", move_buffer);
12846         linelen += movelen;
12847
12848         /* [AS] Add PV info if present */
12849         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
12850             /* [HGM] add time */
12851             char buf[MSG_SIZ]; int seconds;
12852
12853             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
12854
12855             if( seconds <= 0)
12856               buf[0] = 0;
12857             else
12858               if( seconds < 30 )
12859                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
12860               else
12861                 {
12862                   seconds = (seconds + 4)/10; // round to full seconds
12863                   if( seconds < 60 )
12864                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
12865                   else
12866                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
12867                 }
12868
12869             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
12870                       pvInfoList[i].score >= 0 ? "+" : "",
12871                       pvInfoList[i].score / 100.0,
12872                       pvInfoList[i].depth,
12873                       buf );
12874
12875             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
12876
12877             /* Print score/depth */
12878             blank = linelen > 0 && movelen > 0;
12879             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12880                 fprintf(f, "\n");
12881                 linelen = 0;
12882                 blank = 0;
12883             }
12884             if (blank) {
12885                 fprintf(f, " ");
12886                 linelen++;
12887             }
12888             fprintf(f, "%s", move_buffer);
12889             linelen += movelen;
12890         }
12891
12892         i++;
12893     }
12894
12895     /* Start a new line */
12896     if (linelen > 0) fprintf(f, "\n");
12897
12898     /* Print comments after last move */
12899     if (commentList[i] != NULL) {
12900         fprintf(f, "%s\n", commentList[i]);
12901     }
12902
12903     /* Print result */
12904     if (gameInfo.resultDetails != NULL &&
12905         gameInfo.resultDetails[0] != NULLCHAR) {
12906         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
12907                 PGNResult(gameInfo.result));
12908     } else {
12909         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12910     }
12911
12912     fclose(f);
12913     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12914     return TRUE;
12915 }
12916
12917 /* Save game in old style and close the file */
12918 int
12919 SaveGameOldStyle (FILE *f)
12920 {
12921     int i, offset;
12922     time_t tm;
12923
12924     tm = time((time_t *) NULL);
12925
12926     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
12927     PrintOpponents(f);
12928
12929     if (backwardMostMove > 0 || startedFromSetupPosition) {
12930         fprintf(f, "\n[--------------\n");
12931         PrintPosition(f, backwardMostMove);
12932         fprintf(f, "--------------]\n");
12933     } else {
12934         fprintf(f, "\n");
12935     }
12936
12937     i = backwardMostMove;
12938     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12939
12940     while (i < forwardMostMove) {
12941         if (commentList[i] != NULL) {
12942             fprintf(f, "[%s]\n", commentList[i]);
12943         }
12944
12945         if ((i % 2) == 1) {
12946             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
12947             i++;
12948         } else {
12949             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
12950             i++;
12951             if (commentList[i] != NULL) {
12952                 fprintf(f, "\n");
12953                 continue;
12954             }
12955             if (i >= forwardMostMove) {
12956                 fprintf(f, "\n");
12957                 break;
12958             }
12959             fprintf(f, "%s\n", parseList[i]);
12960             i++;
12961         }
12962     }
12963
12964     if (commentList[i] != NULL) {
12965         fprintf(f, "[%s]\n", commentList[i]);
12966     }
12967
12968     /* This isn't really the old style, but it's close enough */
12969     if (gameInfo.resultDetails != NULL &&
12970         gameInfo.resultDetails[0] != NULLCHAR) {
12971         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
12972                 gameInfo.resultDetails);
12973     } else {
12974         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12975     }
12976
12977     fclose(f);
12978     return TRUE;
12979 }
12980
12981 /* Save the current game to open file f and close the file */
12982 int
12983 SaveGame (FILE *f, int dummy, char *dummy2)
12984 {
12985     if (gameMode == EditPosition) EditPositionDone(TRUE);
12986     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12987     if (appData.oldSaveStyle)
12988       return SaveGameOldStyle(f);
12989     else
12990       return SaveGamePGN(f);
12991 }
12992
12993 /* Save the current position to the given file */
12994 int
12995 SavePositionToFile (char *filename)
12996 {
12997     FILE *f;
12998     char buf[MSG_SIZ];
12999
13000     if (strcmp(filename, "-") == 0) {
13001         return SavePosition(stdout, 0, NULL);
13002     } else {
13003         f = fopen(filename, "a");
13004         if (f == NULL) {
13005             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13006             DisplayError(buf, errno);
13007             return FALSE;
13008         } else {
13009             safeStrCpy(buf, lastMsg, MSG_SIZ);
13010             DisplayMessage(_("Waiting for access to save file"), "");
13011             flock(fileno(f), LOCK_EX); // [HGM] lock
13012             DisplayMessage(_("Saving position"), "");
13013             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
13014             SavePosition(f, 0, NULL);
13015             DisplayMessage(buf, "");
13016             return TRUE;
13017         }
13018     }
13019 }
13020
13021 /* Save the current position to the given open file and close the file */
13022 int
13023 SavePosition (FILE *f, int dummy, char *dummy2)
13024 {
13025     time_t tm;
13026     char *fen;
13027
13028     if (gameMode == EditPosition) EditPositionDone(TRUE);
13029     if (appData.oldSaveStyle) {
13030         tm = time((time_t *) NULL);
13031
13032         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
13033         PrintOpponents(f);
13034         fprintf(f, "[--------------\n");
13035         PrintPosition(f, currentMove);
13036         fprintf(f, "--------------]\n");
13037     } else {
13038         fen = PositionToFEN(currentMove, NULL);
13039         fprintf(f, "%s\n", fen);
13040         free(fen);
13041     }
13042     fclose(f);
13043     return TRUE;
13044 }
13045
13046 void
13047 ReloadCmailMsgEvent (int unregister)
13048 {
13049 #if !WIN32
13050     static char *inFilename = NULL;
13051     static char *outFilename;
13052     int i;
13053     struct stat inbuf, outbuf;
13054     int status;
13055
13056     /* Any registered moves are unregistered if unregister is set, */
13057     /* i.e. invoked by the signal handler */
13058     if (unregister) {
13059         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13060             cmailMoveRegistered[i] = FALSE;
13061             if (cmailCommentList[i] != NULL) {
13062                 free(cmailCommentList[i]);
13063                 cmailCommentList[i] = NULL;
13064             }
13065         }
13066         nCmailMovesRegistered = 0;
13067     }
13068
13069     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13070         cmailResult[i] = CMAIL_NOT_RESULT;
13071     }
13072     nCmailResults = 0;
13073
13074     if (inFilename == NULL) {
13075         /* Because the filenames are static they only get malloced once  */
13076         /* and they never get freed                                      */
13077         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
13078         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
13079
13080         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
13081         sprintf(outFilename, "%s.out", appData.cmailGameName);
13082     }
13083
13084     status = stat(outFilename, &outbuf);
13085     if (status < 0) {
13086         cmailMailedMove = FALSE;
13087     } else {
13088         status = stat(inFilename, &inbuf);
13089         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
13090     }
13091
13092     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
13093        counts the games, notes how each one terminated, etc.
13094
13095        It would be nice to remove this kludge and instead gather all
13096        the information while building the game list.  (And to keep it
13097        in the game list nodes instead of having a bunch of fixed-size
13098        parallel arrays.)  Note this will require getting each game's
13099        termination from the PGN tags, as the game list builder does
13100        not process the game moves.  --mann
13101        */
13102     cmailMsgLoaded = TRUE;
13103     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
13104
13105     /* Load first game in the file or popup game menu */
13106     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
13107
13108 #endif /* !WIN32 */
13109     return;
13110 }
13111
13112 int
13113 RegisterMove ()
13114 {
13115     FILE *f;
13116     char string[MSG_SIZ];
13117
13118     if (   cmailMailedMove
13119         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
13120         return TRUE;            /* Allow free viewing  */
13121     }
13122
13123     /* Unregister move to ensure that we don't leave RegisterMove        */
13124     /* with the move registered when the conditions for registering no   */
13125     /* longer hold                                                       */
13126     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
13127         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
13128         nCmailMovesRegistered --;
13129
13130         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
13131           {
13132               free(cmailCommentList[lastLoadGameNumber - 1]);
13133               cmailCommentList[lastLoadGameNumber - 1] = NULL;
13134           }
13135     }
13136
13137     if (cmailOldMove == -1) {
13138         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
13139         return FALSE;
13140     }
13141
13142     if (currentMove > cmailOldMove + 1) {
13143         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
13144         return FALSE;
13145     }
13146
13147     if (currentMove < cmailOldMove) {
13148         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
13149         return FALSE;
13150     }
13151
13152     if (forwardMostMove > currentMove) {
13153         /* Silently truncate extra moves */
13154         TruncateGame();
13155     }
13156
13157     if (   (currentMove == cmailOldMove + 1)
13158         || (   (currentMove == cmailOldMove)
13159             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
13160                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
13161         if (gameInfo.result != GameUnfinished) {
13162             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
13163         }
13164
13165         if (commentList[currentMove] != NULL) {
13166             cmailCommentList[lastLoadGameNumber - 1]
13167               = StrSave(commentList[currentMove]);
13168         }
13169         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
13170
13171         if (appData.debugMode)
13172           fprintf(debugFP, "Saving %s for game %d\n",
13173                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
13174
13175         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
13176
13177         f = fopen(string, "w");
13178         if (appData.oldSaveStyle) {
13179             SaveGameOldStyle(f); /* also closes the file */
13180
13181             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
13182             f = fopen(string, "w");
13183             SavePosition(f, 0, NULL); /* also closes the file */
13184         } else {
13185             fprintf(f, "{--------------\n");
13186             PrintPosition(f, currentMove);
13187             fprintf(f, "--------------}\n\n");
13188
13189             SaveGame(f, 0, NULL); /* also closes the file*/
13190         }
13191
13192         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
13193         nCmailMovesRegistered ++;
13194     } else if (nCmailGames == 1) {
13195         DisplayError(_("You have not made a move yet"), 0);
13196         return FALSE;
13197     }
13198
13199     return TRUE;
13200 }
13201
13202 void
13203 MailMoveEvent ()
13204 {
13205 #if !WIN32
13206     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
13207     FILE *commandOutput;
13208     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
13209     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
13210     int nBuffers;
13211     int i;
13212     int archived;
13213     char *arcDir;
13214
13215     if (! cmailMsgLoaded) {
13216         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
13217         return;
13218     }
13219
13220     if (nCmailGames == nCmailResults) {
13221         DisplayError(_("No unfinished games"), 0);
13222         return;
13223     }
13224
13225 #if CMAIL_PROHIBIT_REMAIL
13226     if (cmailMailedMove) {
13227       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);
13228         DisplayError(msg, 0);
13229         return;
13230     }
13231 #endif
13232
13233     if (! (cmailMailedMove || RegisterMove())) return;
13234
13235     if (   cmailMailedMove
13236         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13237       snprintf(string, MSG_SIZ, partCommandString,
13238                appData.debugMode ? " -v" : "", appData.cmailGameName);
13239         commandOutput = popen(string, "r");
13240
13241         if (commandOutput == NULL) {
13242             DisplayError(_("Failed to invoke cmail"), 0);
13243         } else {
13244             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13245                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13246             }
13247             if (nBuffers > 1) {
13248                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13249                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13250                 nBytes = MSG_SIZ - 1;
13251             } else {
13252                 (void) memcpy(msg, buffer, nBytes);
13253             }
13254             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13255
13256             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13257                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
13258
13259                 archived = TRUE;
13260                 for (i = 0; i < nCmailGames; i ++) {
13261                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
13262                         archived = FALSE;
13263                     }
13264                 }
13265                 if (   archived
13266                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13267                         != NULL)) {
13268                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13269                            arcDir,
13270                            appData.cmailGameName,
13271                            gameInfo.date);
13272                     LoadGameFromFile(buffer, 1, buffer, FALSE);
13273                     cmailMsgLoaded = FALSE;
13274                 }
13275             }
13276
13277             DisplayInformation(msg);
13278             pclose(commandOutput);
13279         }
13280     } else {
13281         if ((*cmailMsg) != '\0') {
13282             DisplayInformation(cmailMsg);
13283         }
13284     }
13285
13286     return;
13287 #endif /* !WIN32 */
13288 }
13289
13290 char *
13291 CmailMsg ()
13292 {
13293 #if WIN32
13294     return NULL;
13295 #else
13296     int  prependComma = 0;
13297     char number[5];
13298     char string[MSG_SIZ];       /* Space for game-list */
13299     int  i;
13300
13301     if (!cmailMsgLoaded) return "";
13302
13303     if (cmailMailedMove) {
13304       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13305     } else {
13306         /* Create a list of games left */
13307       snprintf(string, MSG_SIZ, "[");
13308         for (i = 0; i < nCmailGames; i ++) {
13309             if (! (   cmailMoveRegistered[i]
13310                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13311                 if (prependComma) {
13312                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13313                 } else {
13314                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13315                     prependComma = 1;
13316                 }
13317
13318                 strcat(string, number);
13319             }
13320         }
13321         strcat(string, "]");
13322
13323         if (nCmailMovesRegistered + nCmailResults == 0) {
13324             switch (nCmailGames) {
13325               case 1:
13326                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13327                 break;
13328
13329               case 2:
13330                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13331                 break;
13332
13333               default:
13334                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13335                          nCmailGames);
13336                 break;
13337             }
13338         } else {
13339             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13340               case 1:
13341                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13342                          string);
13343                 break;
13344
13345               case 0:
13346                 if (nCmailResults == nCmailGames) {
13347                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13348                 } else {
13349                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13350                 }
13351                 break;
13352
13353               default:
13354                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13355                          string);
13356             }
13357         }
13358     }
13359     return cmailMsg;
13360 #endif /* WIN32 */
13361 }
13362
13363 void
13364 ResetGameEvent ()
13365 {
13366     if (gameMode == Training)
13367       SetTrainingModeOff();
13368
13369     Reset(TRUE, TRUE);
13370     cmailMsgLoaded = FALSE;
13371     if (appData.icsActive) {
13372       SendToICS(ics_prefix);
13373       SendToICS("refresh\n");
13374     }
13375 }
13376
13377 void
13378 ExitEvent (int status)
13379 {
13380     exiting++;
13381     if (exiting > 2) {
13382       /* Give up on clean exit */
13383       exit(status);
13384     }
13385     if (exiting > 1) {
13386       /* Keep trying for clean exit */
13387       return;
13388     }
13389
13390     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
13391
13392     if (telnetISR != NULL) {
13393       RemoveInputSource(telnetISR);
13394     }
13395     if (icsPR != NoProc) {
13396       DestroyChildProcess(icsPR, TRUE);
13397     }
13398
13399     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
13400     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
13401
13402     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
13403     /* make sure this other one finishes before killing it!                  */
13404     if(endingGame) { int count = 0;
13405         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
13406         while(endingGame && count++ < 10) DoSleep(1);
13407         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
13408     }
13409
13410     /* Kill off chess programs */
13411     if (first.pr != NoProc) {
13412         ExitAnalyzeMode();
13413
13414         DoSleep( appData.delayBeforeQuit );
13415         SendToProgram("quit\n", &first);
13416         DoSleep( appData.delayAfterQuit );
13417         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
13418     }
13419     if (second.pr != NoProc) {
13420         DoSleep( appData.delayBeforeQuit );
13421         SendToProgram("quit\n", &second);
13422         DoSleep( appData.delayAfterQuit );
13423         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
13424     }
13425     if (first.isr != NULL) {
13426         RemoveInputSource(first.isr);
13427     }
13428     if (second.isr != NULL) {
13429         RemoveInputSource(second.isr);
13430     }
13431
13432     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13433     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13434
13435     ShutDownFrontEnd();
13436     exit(status);
13437 }
13438
13439 void
13440 PauseEngine (ChessProgramState *cps)
13441 {
13442     SendToProgram("pause\n", cps);
13443     cps->pause = 2;
13444 }
13445
13446 void
13447 UnPauseEngine (ChessProgramState *cps)
13448 {
13449     SendToProgram("resume\n", cps);
13450     cps->pause = 1;
13451 }
13452
13453 void
13454 PauseEvent ()
13455 {
13456     if (appData.debugMode)
13457         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13458     if (pausing) {
13459         pausing = FALSE;
13460         ModeHighlight();
13461         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
13462             StartClocks();
13463             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
13464                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
13465                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
13466             }
13467             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
13468             HandleMachineMove(stashedInputMove, stalledEngine);
13469             stalledEngine = NULL;
13470             return;
13471         }
13472         if (gameMode == MachinePlaysWhite ||
13473             gameMode == TwoMachinesPlay   ||
13474             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
13475             if(first.pause)  UnPauseEngine(&first);
13476             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
13477             if(second.pause) UnPauseEngine(&second);
13478             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
13479             StartClocks();
13480         } else {
13481             DisplayBothClocks();
13482         }
13483         if (gameMode == PlayFromGameFile) {
13484             if (appData.timeDelay >= 0)
13485                 AutoPlayGameLoop();
13486         } else if (gameMode == IcsExamining && pauseExamInvalid) {
13487             Reset(FALSE, TRUE);
13488             SendToICS(ics_prefix);
13489             SendToICS("refresh\n");
13490         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
13491             ForwardInner(forwardMostMove);
13492         }
13493         pauseExamInvalid = FALSE;
13494     } else {
13495         switch (gameMode) {
13496           default:
13497             return;
13498           case IcsExamining:
13499             pauseExamForwardMostMove = forwardMostMove;
13500             pauseExamInvalid = FALSE;
13501             /* fall through */
13502           case IcsObserving:
13503           case IcsPlayingWhite:
13504           case IcsPlayingBlack:
13505             pausing = TRUE;
13506             ModeHighlight();
13507             return;
13508           case PlayFromGameFile:
13509             (void) StopLoadGameTimer();
13510             pausing = TRUE;
13511             ModeHighlight();
13512             break;
13513           case BeginningOfGame:
13514             if (appData.icsActive) return;
13515             /* else fall through */
13516           case MachinePlaysWhite:
13517           case MachinePlaysBlack:
13518           case TwoMachinesPlay:
13519             if (forwardMostMove == 0)
13520               return;           /* don't pause if no one has moved */
13521             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
13522                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
13523                 if(onMove->pause) {           // thinking engine can be paused
13524                     PauseEngine(onMove);      // do it
13525                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
13526                         PauseEngine(onMove->other);
13527                     else
13528                         SendToProgram("easy\n", onMove->other);
13529                     StopClocks();
13530                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
13531             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
13532                 if(first.pause) {
13533                     PauseEngine(&first);
13534                     StopClocks();
13535                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
13536             } else { // human on move, pause pondering by either method
13537                 if(first.pause)
13538                     PauseEngine(&first);
13539                 else if(appData.ponderNextMove)
13540                     SendToProgram("easy\n", &first);
13541                 StopClocks();
13542             }
13543             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
13544           case AnalyzeMode:
13545             pausing = TRUE;
13546             ModeHighlight();
13547             break;
13548         }
13549     }
13550 }
13551
13552 void
13553 EditCommentEvent ()
13554 {
13555     char title[MSG_SIZ];
13556
13557     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13558       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13559     } else {
13560       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13561                WhiteOnMove(currentMove - 1) ? " " : ".. ",
13562                parseList[currentMove - 1]);
13563     }
13564
13565     EditCommentPopUp(currentMove, title, commentList[currentMove]);
13566 }
13567
13568
13569 void
13570 EditTagsEvent ()
13571 {
13572     char *tags = PGNTags(&gameInfo);
13573     bookUp = FALSE;
13574     EditTagsPopUp(tags, NULL);
13575     free(tags);
13576 }
13577
13578 void
13579 ToggleSecond ()
13580 {
13581   if(second.analyzing) {
13582     SendToProgram("exit\n", &second);
13583     second.analyzing = FALSE;
13584   } else {
13585     if (second.pr == NoProc) StartChessProgram(&second);
13586     InitChessProgram(&second, FALSE);
13587     FeedMovesToProgram(&second, currentMove);
13588
13589     SendToProgram("analyze\n", &second);
13590     second.analyzing = TRUE;
13591   }
13592 }
13593
13594 /* Toggle ShowThinking */
13595 void
13596 ToggleShowThinking()
13597 {
13598   appData.showThinking = !appData.showThinking;
13599   ShowThinkingEvent();
13600 }
13601
13602 int
13603 AnalyzeModeEvent ()
13604 {
13605     char buf[MSG_SIZ];
13606
13607     if (!first.analysisSupport) {
13608       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
13609       DisplayError(buf, 0);
13610       return 0;
13611     }
13612     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
13613     if (appData.icsActive) {
13614         if (gameMode != IcsObserving) {
13615           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
13616             DisplayError(buf, 0);
13617             /* secure check */
13618             if (appData.icsEngineAnalyze) {
13619                 if (appData.debugMode)
13620                     fprintf(debugFP, _("Found unexpected active ICS engine analyze \n"));
13621                 ExitAnalyzeMode();
13622                 ModeHighlight();
13623             }
13624             return 0;
13625         }
13626         /* if enable, user wants to disable icsEngineAnalyze */
13627         if (appData.icsEngineAnalyze) {
13628                 ExitAnalyzeMode();
13629                 ModeHighlight();
13630                 return 0;
13631         }
13632         appData.icsEngineAnalyze = TRUE;
13633         if (appData.debugMode)
13634             fprintf(debugFP, _("ICS engine analyze starting... \n"));
13635     }
13636
13637     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
13638     if (appData.noChessProgram || gameMode == AnalyzeMode)
13639       return 0;
13640
13641     if (gameMode != AnalyzeFile) {
13642         if (!appData.icsEngineAnalyze) {
13643                EditGameEvent();
13644                if (gameMode != EditGame) return 0;
13645         }
13646         if (!appData.showThinking) ToggleShowThinking();
13647         ResurrectChessProgram();
13648         SendToProgram("analyze\n", &first);
13649         first.analyzing = TRUE;
13650         /*first.maybeThinking = TRUE;*/
13651         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13652         EngineOutputPopUp();
13653     }
13654     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
13655     pausing = FALSE;
13656     ModeHighlight();
13657     SetGameInfo();
13658
13659     StartAnalysisClock();
13660     GetTimeMark(&lastNodeCountTime);
13661     lastNodeCount = 0;
13662     return 1;
13663 }
13664
13665 void
13666 AnalyzeFileEvent ()
13667 {
13668     if (appData.noChessProgram || gameMode == AnalyzeFile)
13669       return;
13670
13671     if (!first.analysisSupport) {
13672       char buf[MSG_SIZ];
13673       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
13674       DisplayError(buf, 0);
13675       return;
13676     }
13677
13678     if (gameMode != AnalyzeMode) {
13679         keepInfo = 1; // mere annotating should not alter PGN tags
13680         EditGameEvent();
13681         keepInfo = 0;
13682         if (gameMode != EditGame) return;
13683         if (!appData.showThinking) ToggleShowThinking();
13684         ResurrectChessProgram();
13685         SendToProgram("analyze\n", &first);
13686         first.analyzing = TRUE;
13687         /*first.maybeThinking = TRUE;*/
13688         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13689         EngineOutputPopUp();
13690     }
13691     gameMode = AnalyzeFile;
13692     pausing = FALSE;
13693     ModeHighlight();
13694
13695     StartAnalysisClock();
13696     GetTimeMark(&lastNodeCountTime);
13697     lastNodeCount = 0;
13698     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
13699     AnalysisPeriodicEvent(1);
13700 }
13701
13702 void
13703 MachineWhiteEvent ()
13704 {
13705     char buf[MSG_SIZ];
13706     char *bookHit = NULL;
13707
13708     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
13709       return;
13710
13711
13712     if (gameMode == PlayFromGameFile ||
13713         gameMode == TwoMachinesPlay  ||
13714         gameMode == Training         ||
13715         gameMode == AnalyzeMode      ||
13716         gameMode == EndOfGame)
13717         EditGameEvent();
13718
13719     if (gameMode == EditPosition)
13720         EditPositionDone(TRUE);
13721
13722     if (!WhiteOnMove(currentMove)) {
13723         DisplayError(_("It is not White's turn"), 0);
13724         return;
13725     }
13726
13727     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13728       ExitAnalyzeMode();
13729
13730     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13731         gameMode == AnalyzeFile)
13732         TruncateGame();
13733
13734     ResurrectChessProgram();    /* in case it isn't running */
13735     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
13736         gameMode = MachinePlaysWhite;
13737         ResetClocks();
13738     } else
13739     gameMode = MachinePlaysWhite;
13740     pausing = FALSE;
13741     ModeHighlight();
13742     SetGameInfo();
13743     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13744     DisplayTitle(buf);
13745     if (first.sendName) {
13746       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
13747       SendToProgram(buf, &first);
13748     }
13749     if (first.sendTime) {
13750       if (first.useColors) {
13751         SendToProgram("black\n", &first); /*gnu kludge*/
13752       }
13753       SendTimeRemaining(&first, TRUE);
13754     }
13755     if (first.useColors) {
13756       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
13757     }
13758     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13759     SetMachineThinkingEnables();
13760     first.maybeThinking = TRUE;
13761     StartClocks();
13762     firstMove = FALSE;
13763
13764     if (appData.autoFlipView && !flipView) {
13765       flipView = !flipView;
13766       DrawPosition(FALSE, NULL);
13767       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13768     }
13769
13770     if(bookHit) { // [HGM] book: simulate book reply
13771         static char bookMove[MSG_SIZ]; // a bit generous?
13772
13773         programStats.nodes = programStats.depth = programStats.time =
13774         programStats.score = programStats.got_only_move = 0;
13775         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13776
13777         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13778         strcat(bookMove, bookHit);
13779         HandleMachineMove(bookMove, &first);
13780     }
13781 }
13782
13783 void
13784 MachineBlackEvent ()
13785 {
13786   char buf[MSG_SIZ];
13787   char *bookHit = NULL;
13788
13789     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
13790         return;
13791
13792
13793     if (gameMode == PlayFromGameFile ||
13794         gameMode == TwoMachinesPlay  ||
13795         gameMode == Training         ||
13796         gameMode == AnalyzeMode      ||
13797         gameMode == EndOfGame)
13798         EditGameEvent();
13799
13800     if (gameMode == EditPosition)
13801         EditPositionDone(TRUE);
13802
13803     if (WhiteOnMove(currentMove)) {
13804         DisplayError(_("It is not Black's turn"), 0);
13805         return;
13806     }
13807
13808     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13809       ExitAnalyzeMode();
13810
13811     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13812         gameMode == AnalyzeFile)
13813         TruncateGame();
13814
13815     ResurrectChessProgram();    /* in case it isn't running */
13816     gameMode = MachinePlaysBlack;
13817     pausing = FALSE;
13818     ModeHighlight();
13819     SetGameInfo();
13820     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13821     DisplayTitle(buf);
13822     if (first.sendName) {
13823       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
13824       SendToProgram(buf, &first);
13825     }
13826     if (first.sendTime) {
13827       if (first.useColors) {
13828         SendToProgram("white\n", &first); /*gnu kludge*/
13829       }
13830       SendTimeRemaining(&first, FALSE);
13831     }
13832     if (first.useColors) {
13833       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
13834     }
13835     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13836     SetMachineThinkingEnables();
13837     first.maybeThinking = TRUE;
13838     StartClocks();
13839
13840     if (appData.autoFlipView && flipView) {
13841       flipView = !flipView;
13842       DrawPosition(FALSE, NULL);
13843       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13844     }
13845     if(bookHit) { // [HGM] book: simulate book reply
13846         static char bookMove[MSG_SIZ]; // a bit generous?
13847
13848         programStats.nodes = programStats.depth = programStats.time =
13849         programStats.score = programStats.got_only_move = 0;
13850         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13851
13852         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13853         strcat(bookMove, bookHit);
13854         HandleMachineMove(bookMove, &first);
13855     }
13856 }
13857
13858
13859 void
13860 DisplayTwoMachinesTitle ()
13861 {
13862     char buf[MSG_SIZ];
13863     if (appData.matchGames > 0) {
13864         if(appData.tourneyFile[0]) {
13865           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
13866                    gameInfo.white, _("vs."), gameInfo.black,
13867                    nextGame+1, appData.matchGames+1,
13868                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
13869         } else
13870         if (first.twoMachinesColor[0] == 'w') {
13871           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13872                    gameInfo.white, _("vs."),  gameInfo.black,
13873                    first.matchWins, second.matchWins,
13874                    matchGame - 1 - (first.matchWins + second.matchWins));
13875         } else {
13876           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13877                    gameInfo.white, _("vs."), gameInfo.black,
13878                    second.matchWins, first.matchWins,
13879                    matchGame - 1 - (first.matchWins + second.matchWins));
13880         }
13881     } else {
13882       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13883     }
13884     DisplayTitle(buf);
13885 }
13886
13887 void
13888 SettingsMenuIfReady ()
13889 {
13890   if (second.lastPing != second.lastPong) {
13891     DisplayMessage("", _("Waiting for second chess program"));
13892     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
13893     return;
13894   }
13895   ThawUI();
13896   DisplayMessage("", "");
13897   SettingsPopUp(&second);
13898 }
13899
13900 int
13901 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
13902 {
13903     char buf[MSG_SIZ];
13904     if (cps->pr == NoProc) {
13905         StartChessProgram(cps);
13906         if (cps->protocolVersion == 1) {
13907           retry();
13908         } else {
13909           /* kludge: allow timeout for initial "feature" command */
13910           FreezeUI();
13911           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
13912           DisplayMessage("", buf);
13913           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
13914         }
13915         return 1;
13916     }
13917     return 0;
13918 }
13919
13920 void
13921 TwoMachinesEvent P((void))
13922 {
13923     int i;
13924     char buf[MSG_SIZ];
13925     ChessProgramState *onmove;
13926     char *bookHit = NULL;
13927     static int stalling = 0;
13928     TimeMark now;
13929     long wait;
13930
13931     if (appData.noChessProgram) return;
13932
13933     switch (gameMode) {
13934       case TwoMachinesPlay:
13935         return;
13936       case MachinePlaysWhite:
13937       case MachinePlaysBlack:
13938         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13939             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13940             return;
13941         }
13942         /* fall through */
13943       case BeginningOfGame:
13944       case PlayFromGameFile:
13945       case EndOfGame:
13946         EditGameEvent();
13947         if (gameMode != EditGame) return;
13948         break;
13949       case EditPosition:
13950         EditPositionDone(TRUE);
13951         break;
13952       case AnalyzeMode:
13953       case AnalyzeFile:
13954         ExitAnalyzeMode();
13955         break;
13956       case EditGame:
13957       default:
13958         break;
13959     }
13960
13961 //    forwardMostMove = currentMove;
13962     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
13963
13964     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
13965
13966     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
13967     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
13968       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13969       return;
13970     }
13971
13972     if(second.protocolVersion >= 2 && !strstr(second.variants, VariantName(gameInfo.variant))) {
13973         DisplayError("second engine does not play this", 0);
13974         return;
13975     }
13976
13977     if(!stalling) {
13978       InitChessProgram(&second, FALSE); // unbalances ping of second engine
13979       SendToProgram("force\n", &second);
13980       stalling = 1;
13981       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13982       return;
13983     }
13984     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
13985     if(appData.matchPause>10000 || appData.matchPause<10)
13986                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
13987     wait = SubtractTimeMarks(&now, &pauseStart);
13988     if(wait < appData.matchPause) {
13989         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
13990         return;
13991     }
13992     // we are now committed to starting the game
13993     stalling = 0;
13994     DisplayMessage("", "");
13995     if (startedFromSetupPosition) {
13996         SendBoard(&second, backwardMostMove);
13997     if (appData.debugMode) {
13998         fprintf(debugFP, "Two Machines\n");
13999     }
14000     }
14001     for (i = backwardMostMove; i < forwardMostMove; i++) {
14002         SendMoveToProgram(i, &second);
14003     }
14004
14005     gameMode = TwoMachinesPlay;
14006     pausing = FALSE;
14007     ModeHighlight(); // [HGM] logo: this triggers display update of logos
14008     SetGameInfo();
14009     DisplayTwoMachinesTitle();
14010     firstMove = TRUE;
14011     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14012         onmove = &first;
14013     } else {
14014         onmove = &second;
14015     }
14016     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14017     SendToProgram(first.computerString, &first);
14018     if (first.sendName) {
14019       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14020       SendToProgram(buf, &first);
14021     }
14022     SendToProgram(second.computerString, &second);
14023     if (second.sendName) {
14024       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
14025       SendToProgram(buf, &second);
14026     }
14027
14028     ResetClocks();
14029     if (!first.sendTime || !second.sendTime) {
14030         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14031         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14032     }
14033     if (onmove->sendTime) {
14034       if (onmove->useColors) {
14035         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
14036       }
14037       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
14038     }
14039     if (onmove->useColors) {
14040       SendToProgram(onmove->twoMachinesColor, onmove);
14041     }
14042     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
14043 //    SendToProgram("go\n", onmove);
14044     onmove->maybeThinking = TRUE;
14045     SetMachineThinkingEnables();
14046
14047     StartClocks();
14048
14049     if(bookHit) { // [HGM] book: simulate book reply
14050         static char bookMove[MSG_SIZ]; // a bit generous?
14051
14052         programStats.nodes = programStats.depth = programStats.time =
14053         programStats.score = programStats.got_only_move = 0;
14054         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14055
14056         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14057         strcat(bookMove, bookHit);
14058         savedMessage = bookMove; // args for deferred call
14059         savedState = onmove;
14060         ScheduleDelayedEvent(DeferredBookMove, 1);
14061     }
14062 }
14063
14064 void
14065 TrainingEvent ()
14066 {
14067     if (gameMode == Training) {
14068       SetTrainingModeOff();
14069       gameMode = PlayFromGameFile;
14070       DisplayMessage("", _("Training mode off"));
14071     } else {
14072       gameMode = Training;
14073       animateTraining = appData.animate;
14074
14075       /* make sure we are not already at the end of the game */
14076       if (currentMove < forwardMostMove) {
14077         SetTrainingModeOn();
14078         DisplayMessage("", _("Training mode on"));
14079       } else {
14080         gameMode = PlayFromGameFile;
14081         DisplayError(_("Already at end of game"), 0);
14082       }
14083     }
14084     ModeHighlight();
14085 }
14086
14087 void
14088 IcsClientEvent ()
14089 {
14090     if (!appData.icsActive) return;
14091     switch (gameMode) {
14092       case IcsPlayingWhite:
14093       case IcsPlayingBlack:
14094       case IcsObserving:
14095       case IcsIdle:
14096       case BeginningOfGame:
14097       case IcsExamining:
14098         return;
14099
14100       case EditGame:
14101         break;
14102
14103       case EditPosition:
14104         EditPositionDone(TRUE);
14105         break;
14106
14107       case AnalyzeMode:
14108       case AnalyzeFile:
14109         ExitAnalyzeMode();
14110         break;
14111
14112       default:
14113         EditGameEvent();
14114         break;
14115     }
14116
14117     gameMode = IcsIdle;
14118     ModeHighlight();
14119     return;
14120 }
14121
14122 void
14123 EditGameEvent ()
14124 {
14125     int i;
14126
14127     switch (gameMode) {
14128       case Training:
14129         SetTrainingModeOff();
14130         break;
14131       case MachinePlaysWhite:
14132       case MachinePlaysBlack:
14133       case BeginningOfGame:
14134         SendToProgram("force\n", &first);
14135         SetUserThinkingEnables();
14136         break;
14137       case PlayFromGameFile:
14138         (void) StopLoadGameTimer();
14139         if (gameFileFP != NULL) {
14140             gameFileFP = NULL;
14141         }
14142         break;
14143       case EditPosition:
14144         EditPositionDone(TRUE);
14145         break;
14146       case AnalyzeMode:
14147       case AnalyzeFile:
14148         ExitAnalyzeMode();
14149         SendToProgram("force\n", &first);
14150         break;
14151       case TwoMachinesPlay:
14152         GameEnds(EndOfFile, NULL, GE_PLAYER);
14153         ResurrectChessProgram();
14154         SetUserThinkingEnables();
14155         break;
14156       case EndOfGame:
14157         ResurrectChessProgram();
14158         break;
14159       case IcsPlayingBlack:
14160       case IcsPlayingWhite:
14161         DisplayError(_("Warning: You are still playing a game"), 0);
14162         break;
14163       case IcsObserving:
14164         DisplayError(_("Warning: You are still observing a game"), 0);
14165         break;
14166       case IcsExamining:
14167         DisplayError(_("Warning: You are still examining a game"), 0);
14168         break;
14169       case IcsIdle:
14170         break;
14171       case EditGame:
14172       default:
14173         return;
14174     }
14175
14176     pausing = FALSE;
14177     StopClocks();
14178     first.offeredDraw = second.offeredDraw = 0;
14179
14180     if (gameMode == PlayFromGameFile) {
14181         whiteTimeRemaining = timeRemaining[0][currentMove];
14182         blackTimeRemaining = timeRemaining[1][currentMove];
14183         DisplayTitle("");
14184     }
14185
14186     if (gameMode == MachinePlaysWhite ||
14187         gameMode == MachinePlaysBlack ||
14188         gameMode == TwoMachinesPlay ||
14189         gameMode == EndOfGame) {
14190         i = forwardMostMove;
14191         while (i > currentMove) {
14192             SendToProgram("undo\n", &first);
14193             i--;
14194         }
14195         if(!adjustedClock) {
14196         whiteTimeRemaining = timeRemaining[0][currentMove];
14197         blackTimeRemaining = timeRemaining[1][currentMove];
14198         DisplayBothClocks();
14199         }
14200         if (whiteFlag || blackFlag) {
14201             whiteFlag = blackFlag = 0;
14202         }
14203         DisplayTitle("");
14204     }
14205
14206     gameMode = EditGame;
14207     ModeHighlight();
14208     SetGameInfo();
14209 }
14210
14211
14212 void
14213 EditPositionEvent ()
14214 {
14215     if (gameMode == EditPosition) {
14216         EditGameEvent();
14217         return;
14218     }
14219
14220     EditGameEvent();
14221     if (gameMode != EditGame) return;
14222
14223     gameMode = EditPosition;
14224     ModeHighlight();
14225     SetGameInfo();
14226     if (currentMove > 0)
14227       CopyBoard(boards[0], boards[currentMove]);
14228
14229     blackPlaysFirst = !WhiteOnMove(currentMove);
14230     ResetClocks();
14231     currentMove = forwardMostMove = backwardMostMove = 0;
14232     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14233     DisplayMove(-1);
14234     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
14235 }
14236
14237 void
14238 ExitAnalyzeMode ()
14239 {
14240     /* [DM] icsEngineAnalyze - possible call from other functions */
14241     if (appData.icsEngineAnalyze) {
14242         appData.icsEngineAnalyze = FALSE;
14243
14244         DisplayMessage("",_("Close ICS engine analyze..."));
14245     }
14246     if (first.analysisSupport && first.analyzing) {
14247       SendToBoth("exit\n");
14248       first.analyzing = second.analyzing = FALSE;
14249     }
14250     thinkOutput[0] = NULLCHAR;
14251 }
14252
14253 void
14254 EditPositionDone (Boolean fakeRights)
14255 {
14256     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
14257
14258     startedFromSetupPosition = TRUE;
14259     InitChessProgram(&first, FALSE);
14260     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
14261       boards[0][EP_STATUS] = EP_NONE;
14262       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
14263       if(boards[0][0][BOARD_WIDTH>>1] == king) {
14264         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
14265         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
14266       } else boards[0][CASTLING][2] = NoRights;
14267       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
14268         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
14269         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
14270       } else boards[0][CASTLING][5] = NoRights;
14271       if(gameInfo.variant == VariantSChess) {
14272         int i;
14273         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
14274           boards[0][VIRGIN][i] = 0;
14275           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
14276           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
14277         }
14278       }
14279     }
14280     SendToProgram("force\n", &first);
14281     if (blackPlaysFirst) {
14282         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
14283         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
14284         currentMove = forwardMostMove = backwardMostMove = 1;
14285         CopyBoard(boards[1], boards[0]);
14286     } else {
14287         currentMove = forwardMostMove = backwardMostMove = 0;
14288     }
14289     SendBoard(&first, forwardMostMove);
14290     if (appData.debugMode) {
14291         fprintf(debugFP, "EditPosDone\n");
14292     }
14293     DisplayTitle("");
14294     DisplayMessage("", "");
14295     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14296     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14297     gameMode = EditGame;
14298     ModeHighlight();
14299     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14300     ClearHighlights(); /* [AS] */
14301 }
14302
14303 /* Pause for `ms' milliseconds */
14304 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14305 void
14306 TimeDelay (long ms)
14307 {
14308     TimeMark m1, m2;
14309
14310     GetTimeMark(&m1);
14311     do {
14312         GetTimeMark(&m2);
14313     } while (SubtractTimeMarks(&m2, &m1) < ms);
14314 }
14315
14316 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14317 void
14318 SendMultiLineToICS (char *buf)
14319 {
14320     char temp[MSG_SIZ+1], *p;
14321     int len;
14322
14323     len = strlen(buf);
14324     if (len > MSG_SIZ)
14325       len = MSG_SIZ;
14326
14327     strncpy(temp, buf, len);
14328     temp[len] = 0;
14329
14330     p = temp;
14331     while (*p) {
14332         if (*p == '\n' || *p == '\r')
14333           *p = ' ';
14334         ++p;
14335     }
14336
14337     strcat(temp, "\n");
14338     SendToICS(temp);
14339     SendToPlayer(temp, strlen(temp));
14340 }
14341
14342 void
14343 SetWhiteToPlayEvent ()
14344 {
14345     if (gameMode == EditPosition) {
14346         blackPlaysFirst = FALSE;
14347         DisplayBothClocks();    /* works because currentMove is 0 */
14348     } else if (gameMode == IcsExamining) {
14349         SendToICS(ics_prefix);
14350         SendToICS("tomove white\n");
14351     }
14352 }
14353
14354 void
14355 SetBlackToPlayEvent ()
14356 {
14357     if (gameMode == EditPosition) {
14358         blackPlaysFirst = TRUE;
14359         currentMove = 1;        /* kludge */
14360         DisplayBothClocks();
14361         currentMove = 0;
14362     } else if (gameMode == IcsExamining) {
14363         SendToICS(ics_prefix);
14364         SendToICS("tomove black\n");
14365     }
14366 }
14367
14368 void
14369 EditPositionMenuEvent (ChessSquare selection, int x, int y)
14370 {
14371     char buf[MSG_SIZ];
14372     ChessSquare piece = boards[0][y][x];
14373
14374     if (gameMode != EditPosition && gameMode != IcsExamining) return;
14375
14376     switch (selection) {
14377       case ClearBoard:
14378         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
14379             SendToICS(ics_prefix);
14380             SendToICS("bsetup clear\n");
14381         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
14382             SendToICS(ics_prefix);
14383             SendToICS("clearboard\n");
14384         } else {
14385             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
14386                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
14387                 for (y = 0; y < BOARD_HEIGHT; y++) {
14388                     if (gameMode == IcsExamining) {
14389                         if (boards[currentMove][y][x] != EmptySquare) {
14390                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
14391                                     AAA + x, ONE + y);
14392                             SendToICS(buf);
14393                         }
14394                     } else {
14395                         boards[0][y][x] = p;
14396                     }
14397                 }
14398             }
14399         }
14400         if (gameMode == EditPosition) {
14401             DrawPosition(FALSE, boards[0]);
14402         }
14403         break;
14404
14405       case WhitePlay:
14406         SetWhiteToPlayEvent();
14407         break;
14408
14409       case BlackPlay:
14410         SetBlackToPlayEvent();
14411         break;
14412
14413       case EmptySquare:
14414         if (gameMode == IcsExamining) {
14415             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14416             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
14417             SendToICS(buf);
14418         } else {
14419             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14420                 if(x == BOARD_LEFT-2) {
14421                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
14422                     boards[0][y][1] = 0;
14423                 } else
14424                 if(x == BOARD_RGHT+1) {
14425                     if(y >= gameInfo.holdingsSize) break;
14426                     boards[0][y][BOARD_WIDTH-2] = 0;
14427                 } else break;
14428             }
14429             boards[0][y][x] = EmptySquare;
14430             DrawPosition(FALSE, boards[0]);
14431         }
14432         break;
14433
14434       case PromotePiece:
14435         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
14436            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
14437             selection = (ChessSquare) (PROMOTED piece);
14438         } else if(piece == EmptySquare) selection = WhiteSilver;
14439         else selection = (ChessSquare)((int)piece - 1);
14440         goto defaultlabel;
14441
14442       case DemotePiece:
14443         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
14444            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
14445             selection = (ChessSquare) (DEMOTED piece);
14446         } else if(piece == EmptySquare) selection = BlackSilver;
14447         else selection = (ChessSquare)((int)piece + 1);
14448         goto defaultlabel;
14449
14450       case WhiteQueen:
14451       case BlackQueen:
14452         if(gameInfo.variant == VariantShatranj ||
14453            gameInfo.variant == VariantXiangqi  ||
14454            gameInfo.variant == VariantCourier  ||
14455            gameInfo.variant == VariantMakruk     )
14456             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
14457         goto defaultlabel;
14458
14459       case WhiteKing:
14460       case BlackKing:
14461         if(gameInfo.variant == VariantXiangqi)
14462             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
14463         if(gameInfo.variant == VariantKnightmate)
14464             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
14465       default:
14466         defaultlabel:
14467         if (gameMode == IcsExamining) {
14468             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14469             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
14470                      PieceToChar(selection), AAA + x, ONE + y);
14471             SendToICS(buf);
14472         } else {
14473             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14474                 int n;
14475                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
14476                     n = PieceToNumber(selection - BlackPawn);
14477                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
14478                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
14479                     boards[0][BOARD_HEIGHT-1-n][1]++;
14480                 } else
14481                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
14482                     n = PieceToNumber(selection);
14483                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
14484                     boards[0][n][BOARD_WIDTH-1] = selection;
14485                     boards[0][n][BOARD_WIDTH-2]++;
14486                 }
14487             } else
14488             boards[0][y][x] = selection;
14489             DrawPosition(TRUE, boards[0]);
14490             ClearHighlights();
14491             fromX = fromY = -1;
14492         }
14493         break;
14494     }
14495 }
14496
14497
14498 void
14499 DropMenuEvent (ChessSquare selection, int x, int y)
14500 {
14501     ChessMove moveType;
14502
14503     switch (gameMode) {
14504       case IcsPlayingWhite:
14505       case MachinePlaysBlack:
14506         if (!WhiteOnMove(currentMove)) {
14507             DisplayMoveError(_("It is Black's turn"));
14508             return;
14509         }
14510         moveType = WhiteDrop;
14511         break;
14512       case IcsPlayingBlack:
14513       case MachinePlaysWhite:
14514         if (WhiteOnMove(currentMove)) {
14515             DisplayMoveError(_("It is White's turn"));
14516             return;
14517         }
14518         moveType = BlackDrop;
14519         break;
14520       case EditGame:
14521         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
14522         break;
14523       default:
14524         return;
14525     }
14526
14527     if (moveType == BlackDrop && selection < BlackPawn) {
14528       selection = (ChessSquare) ((int) selection
14529                                  + (int) BlackPawn - (int) WhitePawn);
14530     }
14531     if (boards[currentMove][y][x] != EmptySquare) {
14532         DisplayMoveError(_("That square is occupied"));
14533         return;
14534     }
14535
14536     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
14537 }
14538
14539 void
14540 AcceptEvent ()
14541 {
14542     /* Accept a pending offer of any kind from opponent */
14543
14544     if (appData.icsActive) {
14545         SendToICS(ics_prefix);
14546         SendToICS("accept\n");
14547     } else if (cmailMsgLoaded) {
14548         if (currentMove == cmailOldMove &&
14549             commentList[cmailOldMove] != NULL &&
14550             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14551                    "Black offers a draw" : "White offers a draw")) {
14552             TruncateGame();
14553             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14554             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14555         } else {
14556             DisplayError(_("There is no pending offer on this move"), 0);
14557             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14558         }
14559     } else {
14560         /* Not used for offers from chess program */
14561     }
14562 }
14563
14564 void
14565 DeclineEvent ()
14566 {
14567     /* Decline a pending offer of any kind from opponent */
14568
14569     if (appData.icsActive) {
14570         SendToICS(ics_prefix);
14571         SendToICS("decline\n");
14572     } else if (cmailMsgLoaded) {
14573         if (currentMove == cmailOldMove &&
14574             commentList[cmailOldMove] != NULL &&
14575             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14576                    "Black offers a draw" : "White offers a draw")) {
14577 #ifdef NOTDEF
14578             AppendComment(cmailOldMove, "Draw declined", TRUE);
14579             DisplayComment(cmailOldMove - 1, "Draw declined");
14580 #endif /*NOTDEF*/
14581         } else {
14582             DisplayError(_("There is no pending offer on this move"), 0);
14583         }
14584     } else {
14585         /* Not used for offers from chess program */
14586     }
14587 }
14588
14589 void
14590 RematchEvent ()
14591 {
14592     /* Issue ICS rematch command */
14593     if (appData.icsActive) {
14594         SendToICS(ics_prefix);
14595         SendToICS("rematch\n");
14596     }
14597 }
14598
14599 void
14600 CallFlagEvent ()
14601 {
14602     /* Call your opponent's flag (claim a win on time) */
14603     if (appData.icsActive) {
14604         SendToICS(ics_prefix);
14605         SendToICS("flag\n");
14606     } else {
14607         switch (gameMode) {
14608           default:
14609             return;
14610           case MachinePlaysWhite:
14611             if (whiteFlag) {
14612                 if (blackFlag)
14613                   GameEnds(GameIsDrawn, "Both players ran out of time",
14614                            GE_PLAYER);
14615                 else
14616                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
14617             } else {
14618                 DisplayError(_("Your opponent is not out of time"), 0);
14619             }
14620             break;
14621           case MachinePlaysBlack:
14622             if (blackFlag) {
14623                 if (whiteFlag)
14624                   GameEnds(GameIsDrawn, "Both players ran out of time",
14625                            GE_PLAYER);
14626                 else
14627                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
14628             } else {
14629                 DisplayError(_("Your opponent is not out of time"), 0);
14630             }
14631             break;
14632         }
14633     }
14634 }
14635
14636 void
14637 ClockClick (int which)
14638 {       // [HGM] code moved to back-end from winboard.c
14639         if(which) { // black clock
14640           if (gameMode == EditPosition || gameMode == IcsExamining) {
14641             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14642             SetBlackToPlayEvent();
14643           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
14644           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
14645           } else if (shiftKey) {
14646             AdjustClock(which, -1);
14647           } else if (gameMode == IcsPlayingWhite ||
14648                      gameMode == MachinePlaysBlack) {
14649             CallFlagEvent();
14650           }
14651         } else { // white clock
14652           if (gameMode == EditPosition || gameMode == IcsExamining) {
14653             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14654             SetWhiteToPlayEvent();
14655           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
14656           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
14657           } else if (shiftKey) {
14658             AdjustClock(which, -1);
14659           } else if (gameMode == IcsPlayingBlack ||
14660                    gameMode == MachinePlaysWhite) {
14661             CallFlagEvent();
14662           }
14663         }
14664 }
14665
14666 void
14667 DrawEvent ()
14668 {
14669     /* Offer draw or accept pending draw offer from opponent */
14670
14671     if (appData.icsActive) {
14672         /* Note: tournament rules require draw offers to be
14673            made after you make your move but before you punch
14674            your clock.  Currently ICS doesn't let you do that;
14675            instead, you immediately punch your clock after making
14676            a move, but you can offer a draw at any time. */
14677
14678         SendToICS(ics_prefix);
14679         SendToICS("draw\n");
14680         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
14681     } else if (cmailMsgLoaded) {
14682         if (currentMove == cmailOldMove &&
14683             commentList[cmailOldMove] != NULL &&
14684             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14685                    "Black offers a draw" : "White offers a draw")) {
14686             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14687             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14688         } else if (currentMove == cmailOldMove + 1) {
14689             char *offer = WhiteOnMove(cmailOldMove) ?
14690               "White offers a draw" : "Black offers a draw";
14691             AppendComment(currentMove, offer, TRUE);
14692             DisplayComment(currentMove - 1, offer);
14693             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
14694         } else {
14695             DisplayError(_("You must make your move before offering a draw"), 0);
14696             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14697         }
14698     } else if (first.offeredDraw) {
14699         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
14700     } else {
14701         if (first.sendDrawOffers) {
14702             SendToProgram("draw\n", &first);
14703             userOfferedDraw = TRUE;
14704         }
14705     }
14706 }
14707
14708 void
14709 AdjournEvent ()
14710 {
14711     /* Offer Adjourn or accept pending Adjourn offer from opponent */
14712
14713     if (appData.icsActive) {
14714         SendToICS(ics_prefix);
14715         SendToICS("adjourn\n");
14716     } else {
14717         /* Currently GNU Chess doesn't offer or accept Adjourns */
14718     }
14719 }
14720
14721
14722 void
14723 AbortEvent ()
14724 {
14725     /* Offer Abort or accept pending Abort offer from opponent */
14726
14727     if (appData.icsActive) {
14728         SendToICS(ics_prefix);
14729         SendToICS("abort\n");
14730     } else {
14731         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
14732     }
14733 }
14734
14735 void
14736 ResignEvent ()
14737 {
14738     /* Resign.  You can do this even if it's not your turn. */
14739
14740     if (appData.icsActive) {
14741         SendToICS(ics_prefix);
14742         SendToICS("resign\n");
14743     } else {
14744         switch (gameMode) {
14745           case MachinePlaysWhite:
14746             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14747             break;
14748           case MachinePlaysBlack:
14749             GameEnds(BlackWins, "White resigns", GE_PLAYER);
14750             break;
14751           case EditGame:
14752             if (cmailMsgLoaded) {
14753                 TruncateGame();
14754                 if (WhiteOnMove(cmailOldMove)) {
14755                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
14756                 } else {
14757                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14758                 }
14759                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
14760             }
14761             break;
14762           default:
14763             break;
14764         }
14765     }
14766 }
14767
14768
14769 void
14770 StopObservingEvent ()
14771 {
14772     /* Stop observing current games */
14773     SendToICS(ics_prefix);
14774     SendToICS("unobserve\n");
14775 }
14776
14777 void
14778 StopExaminingEvent ()
14779 {
14780     /* Stop observing current game */
14781     SendToICS(ics_prefix);
14782     SendToICS("unexamine\n");
14783 }
14784
14785 void
14786 ForwardInner (int target)
14787 {
14788     int limit; int oldSeekGraphUp = seekGraphUp;
14789
14790     if (appData.debugMode)
14791         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
14792                 target, currentMove, forwardMostMove);
14793
14794     if (gameMode == EditPosition)
14795       return;
14796
14797     seekGraphUp = FALSE;
14798     MarkTargetSquares(1);
14799
14800     if (gameMode == PlayFromGameFile && !pausing)
14801       PauseEvent();
14802
14803     if (gameMode == IcsExamining && pausing)
14804       limit = pauseExamForwardMostMove;
14805     else
14806       limit = forwardMostMove;
14807
14808     if (target > limit) target = limit;
14809
14810     if (target > 0 && moveList[target - 1][0]) {
14811         int fromX, fromY, toX, toY;
14812         toX = moveList[target - 1][2] - AAA;
14813         toY = moveList[target - 1][3] - ONE;
14814         if (moveList[target - 1][1] == '@') {
14815             if (appData.highlightLastMove) {
14816                 SetHighlights(-1, -1, toX, toY);
14817             }
14818         } else {
14819             fromX = moveList[target - 1][0] - AAA;
14820             fromY = moveList[target - 1][1] - ONE;
14821             if (target == currentMove + 1) {
14822                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
14823             }
14824             if (appData.highlightLastMove) {
14825                 SetHighlights(fromX, fromY, toX, toY);
14826             }
14827         }
14828     }
14829     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14830         gameMode == Training || gameMode == PlayFromGameFile ||
14831         gameMode == AnalyzeFile) {
14832         while (currentMove < target) {
14833             if(second.analyzing) SendMoveToProgram(currentMove, &second);
14834             SendMoveToProgram(currentMove++, &first);
14835         }
14836     } else {
14837         currentMove = target;
14838     }
14839
14840     if (gameMode == EditGame || gameMode == EndOfGame) {
14841         whiteTimeRemaining = timeRemaining[0][currentMove];
14842         blackTimeRemaining = timeRemaining[1][currentMove];
14843     }
14844     DisplayBothClocks();
14845     DisplayMove(currentMove - 1);
14846     DrawPosition(oldSeekGraphUp, boards[currentMove]);
14847     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14848     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
14849         DisplayComment(currentMove - 1, commentList[currentMove]);
14850     }
14851     ClearMap(); // [HGM] exclude: invalidate map
14852 }
14853
14854
14855 void
14856 ForwardEvent ()
14857 {
14858     if (gameMode == IcsExamining && !pausing) {
14859         SendToICS(ics_prefix);
14860         SendToICS("forward\n");
14861     } else {
14862         ForwardInner(currentMove + 1);
14863     }
14864 }
14865
14866 void
14867 ToEndEvent ()
14868 {
14869     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14870         /* to optimze, we temporarily turn off analysis mode while we feed
14871          * the remaining moves to the engine. Otherwise we get analysis output
14872          * after each move.
14873          */
14874         if (first.analysisSupport) {
14875           SendToProgram("exit\nforce\n", &first);
14876           first.analyzing = FALSE;
14877         }
14878     }
14879
14880     if (gameMode == IcsExamining && !pausing) {
14881         SendToICS(ics_prefix);
14882         SendToICS("forward 999999\n");
14883     } else {
14884         ForwardInner(forwardMostMove);
14885     }
14886
14887     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14888         /* we have fed all the moves, so reactivate analysis mode */
14889         SendToProgram("analyze\n", &first);
14890         first.analyzing = TRUE;
14891         /*first.maybeThinking = TRUE;*/
14892         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14893     }
14894 }
14895
14896 void
14897 BackwardInner (int target)
14898 {
14899     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
14900
14901     if (appData.debugMode)
14902         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
14903                 target, currentMove, forwardMostMove);
14904
14905     if (gameMode == EditPosition) return;
14906     seekGraphUp = FALSE;
14907     MarkTargetSquares(1);
14908     if (currentMove <= backwardMostMove) {
14909         ClearHighlights();
14910         DrawPosition(full_redraw, boards[currentMove]);
14911         return;
14912     }
14913     if (gameMode == PlayFromGameFile && !pausing)
14914       PauseEvent();
14915
14916     if (moveList[target][0]) {
14917         int fromX, fromY, toX, toY;
14918         toX = moveList[target][2] - AAA;
14919         toY = moveList[target][3] - ONE;
14920         if (moveList[target][1] == '@') {
14921             if (appData.highlightLastMove) {
14922                 SetHighlights(-1, -1, toX, toY);
14923             }
14924         } else {
14925             fromX = moveList[target][0] - AAA;
14926             fromY = moveList[target][1] - ONE;
14927             if (target == currentMove - 1) {
14928                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
14929             }
14930             if (appData.highlightLastMove) {
14931                 SetHighlights(fromX, fromY, toX, toY);
14932             }
14933         }
14934     }
14935     if (gameMode == EditGame || gameMode==AnalyzeMode ||
14936         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
14937         while (currentMove > target) {
14938             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
14939                 // null move cannot be undone. Reload program with move history before it.
14940                 int i;
14941                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
14942                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
14943                 }
14944                 SendBoard(&first, i);
14945               if(second.analyzing) SendBoard(&second, i);
14946                 for(currentMove=i; currentMove<target; currentMove++) {
14947                     SendMoveToProgram(currentMove, &first);
14948                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
14949                 }
14950                 break;
14951             }
14952             SendToBoth("undo\n");
14953             currentMove--;
14954         }
14955     } else {
14956         currentMove = target;
14957     }
14958
14959     if (gameMode == EditGame || gameMode == EndOfGame) {
14960         whiteTimeRemaining = timeRemaining[0][currentMove];
14961         blackTimeRemaining = timeRemaining[1][currentMove];
14962     }
14963     DisplayBothClocks();
14964     DisplayMove(currentMove - 1);
14965     DrawPosition(full_redraw, boards[currentMove]);
14966     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14967     // [HGM] PV info: routine tests if comment empty
14968     DisplayComment(currentMove - 1, commentList[currentMove]);
14969     ClearMap(); // [HGM] exclude: invalidate map
14970 }
14971
14972 void
14973 BackwardEvent ()
14974 {
14975     if (gameMode == IcsExamining && !pausing) {
14976         SendToICS(ics_prefix);
14977         SendToICS("backward\n");
14978     } else {
14979         BackwardInner(currentMove - 1);
14980     }
14981 }
14982
14983 void
14984 ToStartEvent ()
14985 {
14986     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14987         /* to optimize, we temporarily turn off analysis mode while we undo
14988          * all the moves. Otherwise we get analysis output after each undo.
14989          */
14990         if (first.analysisSupport) {
14991           SendToProgram("exit\nforce\n", &first);
14992           first.analyzing = FALSE;
14993         }
14994     }
14995
14996     if (gameMode == IcsExamining && !pausing) {
14997         SendToICS(ics_prefix);
14998         SendToICS("backward 999999\n");
14999     } else {
15000         BackwardInner(backwardMostMove);
15001     }
15002
15003     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15004         /* we have fed all the moves, so reactivate analysis mode */
15005         SendToProgram("analyze\n", &first);
15006         first.analyzing = TRUE;
15007         /*first.maybeThinking = TRUE;*/
15008         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15009     }
15010 }
15011
15012 void
15013 ToNrEvent (int to)
15014 {
15015   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
15016   if (to >= forwardMostMove) to = forwardMostMove;
15017   if (to <= backwardMostMove) to = backwardMostMove;
15018   if (to < currentMove) {
15019     BackwardInner(to);
15020   } else {
15021     ForwardInner(to);
15022   }
15023 }
15024
15025 void
15026 RevertEvent (Boolean annotate)
15027 {
15028     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
15029         return;
15030     }
15031     if (gameMode != IcsExamining) {
15032         DisplayError(_("You are not examining a game"), 0);
15033         return;
15034     }
15035     if (pausing) {
15036         DisplayError(_("You can't revert while pausing"), 0);
15037         return;
15038     }
15039     SendToICS(ics_prefix);
15040     SendToICS("revert\n");
15041 }
15042
15043 void
15044 RetractMoveEvent ()
15045 {
15046     switch (gameMode) {
15047       case MachinePlaysWhite:
15048       case MachinePlaysBlack:
15049         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15050             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
15051             return;
15052         }
15053         if (forwardMostMove < 2) return;
15054         currentMove = forwardMostMove = forwardMostMove - 2;
15055         whiteTimeRemaining = timeRemaining[0][currentMove];
15056         blackTimeRemaining = timeRemaining[1][currentMove];
15057         DisplayBothClocks();
15058         DisplayMove(currentMove - 1);
15059         ClearHighlights();/*!! could figure this out*/
15060         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
15061         SendToProgram("remove\n", &first);
15062         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
15063         break;
15064
15065       case BeginningOfGame:
15066       default:
15067         break;
15068
15069       case IcsPlayingWhite:
15070       case IcsPlayingBlack:
15071         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
15072             SendToICS(ics_prefix);
15073             SendToICS("takeback 2\n");
15074         } else {
15075             SendToICS(ics_prefix);
15076             SendToICS("takeback 1\n");
15077         }
15078         break;
15079     }
15080 }
15081
15082 void
15083 MoveNowEvent ()
15084 {
15085     ChessProgramState *cps;
15086
15087     switch (gameMode) {
15088       case MachinePlaysWhite:
15089         if (!WhiteOnMove(forwardMostMove)) {
15090             DisplayError(_("It is your turn"), 0);
15091             return;
15092         }
15093         cps = &first;
15094         break;
15095       case MachinePlaysBlack:
15096         if (WhiteOnMove(forwardMostMove)) {
15097             DisplayError(_("It is your turn"), 0);
15098             return;
15099         }
15100         cps = &first;
15101         break;
15102       case TwoMachinesPlay:
15103         if (WhiteOnMove(forwardMostMove) ==
15104             (first.twoMachinesColor[0] == 'w')) {
15105             cps = &first;
15106         } else {
15107             cps = &second;
15108         }
15109         break;
15110       case BeginningOfGame:
15111       default:
15112         return;
15113     }
15114     SendToProgram("?\n", cps);
15115 }
15116
15117 void
15118 TruncateGameEvent ()
15119 {
15120     EditGameEvent();
15121     if (gameMode != EditGame) return;
15122     TruncateGame();
15123 }
15124
15125 void
15126 TruncateGame ()
15127 {
15128     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
15129     if (forwardMostMove > currentMove) {
15130         if (gameInfo.resultDetails != NULL) {
15131             free(gameInfo.resultDetails);
15132             gameInfo.resultDetails = NULL;
15133             gameInfo.result = GameUnfinished;
15134         }
15135         forwardMostMove = currentMove;
15136         HistorySet(parseList, backwardMostMove, forwardMostMove,
15137                    currentMove-1);
15138     }
15139 }
15140
15141 void
15142 HintEvent ()
15143 {
15144     if (appData.noChessProgram) return;
15145     switch (gameMode) {
15146       case MachinePlaysWhite:
15147         if (WhiteOnMove(forwardMostMove)) {
15148             DisplayError(_("Wait until your turn"), 0);
15149             return;
15150         }
15151         break;
15152       case BeginningOfGame:
15153       case MachinePlaysBlack:
15154         if (!WhiteOnMove(forwardMostMove)) {
15155             DisplayError(_("Wait until your turn"), 0);
15156             return;
15157         }
15158         break;
15159       default:
15160         DisplayError(_("No hint available"), 0);
15161         return;
15162     }
15163     SendToProgram("hint\n", &first);
15164     hintRequested = TRUE;
15165 }
15166
15167 void
15168 CreateBookEvent ()
15169 {
15170     ListGame * lg = (ListGame *) gameList.head;
15171     FILE *f;
15172     int nItem;
15173     static int secondTime = FALSE;
15174
15175     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
15176         DisplayError(_("Game list not loaded or empty"), 0);
15177         return;
15178     }
15179
15180     if(!secondTime && (f = fopen(appData.polyglotBook, "r"))) {
15181         fclose(f);
15182         secondTime++;
15183         DisplayNote(_("Book file exists! Try again for overwrite."));
15184         return;
15185     }
15186
15187     creatingBook = TRUE;
15188     secondTime = FALSE;
15189
15190     /* Get list size */
15191     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
15192         LoadGame(f, nItem, "", TRUE);
15193         AddGameToBook(TRUE);
15194         lg = (ListGame *) lg->node.succ;
15195     }
15196
15197     creatingBook = FALSE;
15198     FlushBook();
15199 }
15200
15201 void
15202 BookEvent ()
15203 {
15204     if (appData.noChessProgram) return;
15205     switch (gameMode) {
15206       case MachinePlaysWhite:
15207         if (WhiteOnMove(forwardMostMove)) {
15208             DisplayError(_("Wait until your turn"), 0);
15209             return;
15210         }
15211         break;
15212       case BeginningOfGame:
15213       case MachinePlaysBlack:
15214         if (!WhiteOnMove(forwardMostMove)) {
15215             DisplayError(_("Wait until your turn"), 0);
15216             return;
15217         }
15218         break;
15219       case EditPosition:
15220         EditPositionDone(TRUE);
15221         break;
15222       case TwoMachinesPlay:
15223         return;
15224       default:
15225         break;
15226     }
15227     SendToProgram("bk\n", &first);
15228     bookOutput[0] = NULLCHAR;
15229     bookRequested = TRUE;
15230 }
15231
15232 void
15233 AboutGameEvent ()
15234 {
15235     char *tags = PGNTags(&gameInfo);
15236     TagsPopUp(tags, CmailMsg());
15237     free(tags);
15238 }
15239
15240 /* end button procedures */
15241
15242 void
15243 PrintPosition (FILE *fp, int move)
15244 {
15245     int i, j;
15246
15247     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15248         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15249             char c = PieceToChar(boards[move][i][j]);
15250             fputc(c == 'x' ? '.' : c, fp);
15251             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
15252         }
15253     }
15254     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
15255       fprintf(fp, "white to play\n");
15256     else
15257       fprintf(fp, "black to play\n");
15258 }
15259
15260 void
15261 PrintOpponents (FILE *fp)
15262 {
15263     if (gameInfo.white != NULL) {
15264         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
15265     } else {
15266         fprintf(fp, "\n");
15267     }
15268 }
15269
15270 /* Find last component of program's own name, using some heuristics */
15271 void
15272 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
15273 {
15274     char *p, *q, c;
15275     int local = (strcmp(host, "localhost") == 0);
15276     while (!local && (p = strchr(prog, ';')) != NULL) {
15277         p++;
15278         while (*p == ' ') p++;
15279         prog = p;
15280     }
15281     if (*prog == '"' || *prog == '\'') {
15282         q = strchr(prog + 1, *prog);
15283     } else {
15284         q = strchr(prog, ' ');
15285     }
15286     if (q == NULL) q = prog + strlen(prog);
15287     p = q;
15288     while (p >= prog && *p != '/' && *p != '\\') p--;
15289     p++;
15290     if(p == prog && *p == '"') p++;
15291     c = *q; *q = 0;
15292     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
15293     memcpy(buf, p, q - p);
15294     buf[q - p] = NULLCHAR;
15295     if (!local) {
15296         strcat(buf, "@");
15297         strcat(buf, host);
15298     }
15299 }
15300
15301 char *
15302 TimeControlTagValue ()
15303 {
15304     char buf[MSG_SIZ];
15305     if (!appData.clockMode) {
15306       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
15307     } else if (movesPerSession > 0) {
15308       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
15309     } else if (timeIncrement == 0) {
15310       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
15311     } else {
15312       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
15313     }
15314     return StrSave(buf);
15315 }
15316
15317 void
15318 SetGameInfo ()
15319 {
15320     /* This routine is used only for certain modes */
15321     VariantClass v = gameInfo.variant;
15322     ChessMove r = GameUnfinished;
15323     char *p = NULL;
15324
15325     if(keepInfo) return;
15326
15327     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
15328         r = gameInfo.result;
15329         p = gameInfo.resultDetails;
15330         gameInfo.resultDetails = NULL;
15331     }
15332     ClearGameInfo(&gameInfo);
15333     gameInfo.variant = v;
15334
15335     switch (gameMode) {
15336       case MachinePlaysWhite:
15337         gameInfo.event = StrSave( appData.pgnEventHeader );
15338         gameInfo.site = StrSave(HostName());
15339         gameInfo.date = PGNDate();
15340         gameInfo.round = StrSave("-");
15341         gameInfo.white = StrSave(first.tidy);
15342         gameInfo.black = StrSave(UserName());
15343         gameInfo.timeControl = TimeControlTagValue();
15344         break;
15345
15346       case MachinePlaysBlack:
15347         gameInfo.event = StrSave( appData.pgnEventHeader );
15348         gameInfo.site = StrSave(HostName());
15349         gameInfo.date = PGNDate();
15350         gameInfo.round = StrSave("-");
15351         gameInfo.white = StrSave(UserName());
15352         gameInfo.black = StrSave(first.tidy);
15353         gameInfo.timeControl = TimeControlTagValue();
15354         break;
15355
15356       case TwoMachinesPlay:
15357         gameInfo.event = StrSave( appData.pgnEventHeader );
15358         gameInfo.site = StrSave(HostName());
15359         gameInfo.date = PGNDate();
15360         if (roundNr > 0) {
15361             char buf[MSG_SIZ];
15362             snprintf(buf, MSG_SIZ, "%d", roundNr);
15363             gameInfo.round = StrSave(buf);
15364         } else {
15365             gameInfo.round = StrSave("-");
15366         }
15367         if (first.twoMachinesColor[0] == 'w') {
15368             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15369             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15370         } else {
15371             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15372             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15373         }
15374         gameInfo.timeControl = TimeControlTagValue();
15375         break;
15376
15377       case EditGame:
15378         gameInfo.event = StrSave("Edited game");
15379         gameInfo.site = StrSave(HostName());
15380         gameInfo.date = PGNDate();
15381         gameInfo.round = StrSave("-");
15382         gameInfo.white = StrSave("-");
15383         gameInfo.black = StrSave("-");
15384         gameInfo.result = r;
15385         gameInfo.resultDetails = p;
15386         break;
15387
15388       case EditPosition:
15389         gameInfo.event = StrSave("Edited position");
15390         gameInfo.site = StrSave(HostName());
15391         gameInfo.date = PGNDate();
15392         gameInfo.round = StrSave("-");
15393         gameInfo.white = StrSave("-");
15394         gameInfo.black = StrSave("-");
15395         break;
15396
15397       case IcsPlayingWhite:
15398       case IcsPlayingBlack:
15399       case IcsObserving:
15400       case IcsExamining:
15401         break;
15402
15403       case PlayFromGameFile:
15404         gameInfo.event = StrSave("Game from non-PGN file");
15405         gameInfo.site = StrSave(HostName());
15406         gameInfo.date = PGNDate();
15407         gameInfo.round = StrSave("-");
15408         gameInfo.white = StrSave("?");
15409         gameInfo.black = StrSave("?");
15410         break;
15411
15412       default:
15413         break;
15414     }
15415 }
15416
15417 void
15418 ReplaceComment (int index, char *text)
15419 {
15420     int len;
15421     char *p;
15422     float score;
15423
15424     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
15425        pvInfoList[index-1].depth == len &&
15426        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
15427        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
15428     while (*text == '\n') text++;
15429     len = strlen(text);
15430     while (len > 0 && text[len - 1] == '\n') len--;
15431
15432     if (commentList[index] != NULL)
15433       free(commentList[index]);
15434
15435     if (len == 0) {
15436         commentList[index] = NULL;
15437         return;
15438     }
15439   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
15440       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
15441       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
15442     commentList[index] = (char *) malloc(len + 2);
15443     strncpy(commentList[index], text, len);
15444     commentList[index][len] = '\n';
15445     commentList[index][len + 1] = NULLCHAR;
15446   } else {
15447     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
15448     char *p;
15449     commentList[index] = (char *) malloc(len + 7);
15450     safeStrCpy(commentList[index], "{\n", 3);
15451     safeStrCpy(commentList[index]+2, text, len+1);
15452     commentList[index][len+2] = NULLCHAR;
15453     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
15454     strcat(commentList[index], "\n}\n");
15455   }
15456 }
15457
15458 void
15459 CrushCRs (char *text)
15460 {
15461   char *p = text;
15462   char *q = text;
15463   char ch;
15464
15465   do {
15466     ch = *p++;
15467     if (ch == '\r') continue;
15468     *q++ = ch;
15469   } while (ch != '\0');
15470 }
15471
15472 void
15473 AppendComment (int index, char *text, Boolean addBraces)
15474 /* addBraces  tells if we should add {} */
15475 {
15476     int oldlen, len;
15477     char *old;
15478
15479 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
15480     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
15481
15482     CrushCRs(text);
15483     while (*text == '\n') text++;
15484     len = strlen(text);
15485     while (len > 0 && text[len - 1] == '\n') len--;
15486     text[len] = NULLCHAR;
15487
15488     if (len == 0) return;
15489
15490     if (commentList[index] != NULL) {
15491       Boolean addClosingBrace = addBraces;
15492         old = commentList[index];
15493         oldlen = strlen(old);
15494         while(commentList[index][oldlen-1] ==  '\n')
15495           commentList[index][--oldlen] = NULLCHAR;
15496         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
15497         safeStrCpy(commentList[index], old, oldlen + len + 6);
15498         free(old);
15499         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
15500         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
15501           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
15502           while (*text == '\n') { text++; len--; }
15503           commentList[index][--oldlen] = NULLCHAR;
15504       }
15505         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
15506         else          strcat(commentList[index], "\n");
15507         strcat(commentList[index], text);
15508         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
15509         else          strcat(commentList[index], "\n");
15510     } else {
15511         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
15512         if(addBraces)
15513           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
15514         else commentList[index][0] = NULLCHAR;
15515         strcat(commentList[index], text);
15516         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
15517         if(addBraces == TRUE) strcat(commentList[index], "}\n");
15518     }
15519 }
15520
15521 static char *
15522 FindStr (char * text, char * sub_text)
15523 {
15524     char * result = strstr( text, sub_text );
15525
15526     if( result != NULL ) {
15527         result += strlen( sub_text );
15528     }
15529
15530     return result;
15531 }
15532
15533 /* [AS] Try to extract PV info from PGN comment */
15534 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
15535 char *
15536 GetInfoFromComment (int index, char * text)
15537 {
15538     char * sep = text, *p;
15539
15540     if( text != NULL && index > 0 ) {
15541         int score = 0;
15542         int depth = 0;
15543         int time = -1, sec = 0, deci;
15544         char * s_eval = FindStr( text, "[%eval " );
15545         char * s_emt = FindStr( text, "[%emt " );
15546
15547         if( s_eval != NULL || s_emt != NULL ) {
15548             /* New style */
15549             char delim;
15550
15551             if( s_eval != NULL ) {
15552                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
15553                     return text;
15554                 }
15555
15556                 if( delim != ']' ) {
15557                     return text;
15558                 }
15559             }
15560
15561             if( s_emt != NULL ) {
15562             }
15563                 return text;
15564         }
15565         else {
15566             /* We expect something like: [+|-]nnn.nn/dd */
15567             int score_lo = 0;
15568
15569             if(*text != '{') return text; // [HGM] braces: must be normal comment
15570
15571             sep = strchr( text, '/' );
15572             if( sep == NULL || sep < (text+4) ) {
15573                 return text;
15574             }
15575
15576             p = text;
15577             if(p[1] == '(') { // comment starts with PV
15578                p = strchr(p, ')'); // locate end of PV
15579                if(p == NULL || sep < p+5) return text;
15580                // at this point we have something like "{(.*) +0.23/6 ..."
15581                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
15582                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
15583                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
15584             }
15585             time = -1; sec = -1; deci = -1;
15586             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
15587                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
15588                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
15589                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
15590                 return text;
15591             }
15592
15593             if( score_lo < 0 || score_lo >= 100 ) {
15594                 return text;
15595             }
15596
15597             if(sec >= 0) time = 600*time + 10*sec; else
15598             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
15599
15600             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
15601
15602             /* [HGM] PV time: now locate end of PV info */
15603             while( *++sep >= '0' && *sep <= '9'); // strip depth
15604             if(time >= 0)
15605             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
15606             if(sec >= 0)
15607             while( *++sep >= '0' && *sep <= '9'); // strip seconds
15608             if(deci >= 0)
15609             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
15610             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
15611         }
15612
15613         if( depth <= 0 ) {
15614             return text;
15615         }
15616
15617         if( time < 0 ) {
15618             time = -1;
15619         }
15620
15621         pvInfoList[index-1].depth = depth;
15622         pvInfoList[index-1].score = score;
15623         pvInfoList[index-1].time  = 10*time; // centi-sec
15624         if(*sep == '}') *sep = 0; else *--sep = '{';
15625         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
15626     }
15627     return sep;
15628 }
15629
15630 void
15631 SendToProgram (char *message, ChessProgramState *cps)
15632 {
15633     int count, outCount, error;
15634     char buf[MSG_SIZ];
15635
15636     if (cps->pr == NoProc) return;
15637     Attention(cps);
15638
15639     if (appData.debugMode) {
15640         TimeMark now;
15641         GetTimeMark(&now);
15642         fprintf(debugFP, "%ld >%-6s: %s",
15643                 SubtractTimeMarks(&now, &programStartTime),
15644                 cps->which, message);
15645         if(serverFP)
15646             fprintf(serverFP, "%ld >%-6s: %s",
15647                 SubtractTimeMarks(&now, &programStartTime),
15648                 cps->which, message), fflush(serverFP);
15649     }
15650
15651     count = strlen(message);
15652     outCount = OutputToProcess(cps->pr, message, count, &error);
15653     if (outCount < count && !exiting
15654                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
15655       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15656       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
15657         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15658             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15659                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15660                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15661                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15662             } else {
15663                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15664                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15665                 gameInfo.result = res;
15666             }
15667             gameInfo.resultDetails = StrSave(buf);
15668         }
15669         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15670         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15671     }
15672 }
15673
15674 void
15675 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
15676 {
15677     char *end_str;
15678     char buf[MSG_SIZ];
15679     ChessProgramState *cps = (ChessProgramState *)closure;
15680
15681     if (isr != cps->isr) return; /* Killed intentionally */
15682     if (count <= 0) {
15683         if (count == 0) {
15684             RemoveInputSource(cps->isr);
15685             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
15686                     _(cps->which), cps->program);
15687             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
15688             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15689                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15690                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15691                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15692                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15693                 } else {
15694                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15695                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15696                     gameInfo.result = res;
15697                 }
15698                 gameInfo.resultDetails = StrSave(buf);
15699             }
15700             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15701             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
15702         } else {
15703             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
15704                     _(cps->which), cps->program);
15705             RemoveInputSource(cps->isr);
15706
15707             /* [AS] Program is misbehaving badly... kill it */
15708             if( count == -2 ) {
15709                 DestroyChildProcess( cps->pr, 9 );
15710                 cps->pr = NoProc;
15711             }
15712
15713             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15714         }
15715         return;
15716     }
15717
15718     if ((end_str = strchr(message, '\r')) != NULL)
15719       *end_str = NULLCHAR;
15720     if ((end_str = strchr(message, '\n')) != NULL)
15721       *end_str = NULLCHAR;
15722
15723     if (appData.debugMode) {
15724         TimeMark now; int print = 1;
15725         char *quote = ""; char c; int i;
15726
15727         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
15728                 char start = message[0];
15729                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
15730                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
15731                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
15732                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
15733                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
15734                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
15735                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
15736                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
15737                    sscanf(message, "hint: %c", &c)!=1 &&
15738                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
15739                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
15740                     print = (appData.engineComments >= 2);
15741                 }
15742                 message[0] = start; // restore original message
15743         }
15744         if(print) {
15745                 GetTimeMark(&now);
15746                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
15747                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15748                         quote,
15749                         message);
15750                 if(serverFP)
15751                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
15752                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15753                         quote,
15754                         message), fflush(serverFP);
15755         }
15756     }
15757
15758     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
15759     if (appData.icsEngineAnalyze) {
15760         if (strstr(message, "whisper") != NULL ||
15761              strstr(message, "kibitz") != NULL ||
15762             strstr(message, "tellics") != NULL) return;
15763     }
15764
15765     HandleMachineMove(message, cps);
15766 }
15767
15768
15769 void
15770 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
15771 {
15772     char buf[MSG_SIZ];
15773     int seconds;
15774
15775     if( timeControl_2 > 0 ) {
15776         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
15777             tc = timeControl_2;
15778         }
15779     }
15780     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
15781     inc /= cps->timeOdds;
15782     st  /= cps->timeOdds;
15783
15784     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
15785
15786     if (st > 0) {
15787       /* Set exact time per move, normally using st command */
15788       if (cps->stKludge) {
15789         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
15790         seconds = st % 60;
15791         if (seconds == 0) {
15792           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
15793         } else {
15794           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
15795         }
15796       } else {
15797         snprintf(buf, MSG_SIZ, "st %d\n", st);
15798       }
15799     } else {
15800       /* Set conventional or incremental time control, using level command */
15801       if (seconds == 0) {
15802         /* Note old gnuchess bug -- minutes:seconds used to not work.
15803            Fixed in later versions, but still avoid :seconds
15804            when seconds is 0. */
15805         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
15806       } else {
15807         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
15808                  seconds, inc/1000.);
15809       }
15810     }
15811     SendToProgram(buf, cps);
15812
15813     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
15814     /* Orthogonally, limit search to given depth */
15815     if (sd > 0) {
15816       if (cps->sdKludge) {
15817         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
15818       } else {
15819         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
15820       }
15821       SendToProgram(buf, cps);
15822     }
15823
15824     if(cps->nps >= 0) { /* [HGM] nps */
15825         if(cps->supportsNPS == FALSE)
15826           cps->nps = -1; // don't use if engine explicitly says not supported!
15827         else {
15828           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
15829           SendToProgram(buf, cps);
15830         }
15831     }
15832 }
15833
15834 ChessProgramState *
15835 WhitePlayer ()
15836 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
15837 {
15838     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
15839        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
15840         return &second;
15841     return &first;
15842 }
15843
15844 void
15845 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
15846 {
15847     char message[MSG_SIZ];
15848     long time, otime;
15849
15850     /* Note: this routine must be called when the clocks are stopped
15851        or when they have *just* been set or switched; otherwise
15852        it will be off by the time since the current tick started.
15853     */
15854     if (machineWhite) {
15855         time = whiteTimeRemaining / 10;
15856         otime = blackTimeRemaining / 10;
15857     } else {
15858         time = blackTimeRemaining / 10;
15859         otime = whiteTimeRemaining / 10;
15860     }
15861     /* [HGM] translate opponent's time by time-odds factor */
15862     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
15863
15864     if (time <= 0) time = 1;
15865     if (otime <= 0) otime = 1;
15866
15867     snprintf(message, MSG_SIZ, "time %ld\n", time);
15868     SendToProgram(message, cps);
15869
15870     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
15871     SendToProgram(message, cps);
15872 }
15873
15874 int
15875 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15876 {
15877   char buf[MSG_SIZ];
15878   int len = strlen(name);
15879   int val;
15880
15881   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15882     (*p) += len + 1;
15883     sscanf(*p, "%d", &val);
15884     *loc = (val != 0);
15885     while (**p && **p != ' ')
15886       (*p)++;
15887     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15888     SendToProgram(buf, cps);
15889     return TRUE;
15890   }
15891   return FALSE;
15892 }
15893
15894 int
15895 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15896 {
15897   char buf[MSG_SIZ];
15898   int len = strlen(name);
15899   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15900     (*p) += len + 1;
15901     sscanf(*p, "%d", loc);
15902     while (**p && **p != ' ') (*p)++;
15903     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15904     SendToProgram(buf, cps);
15905     return TRUE;
15906   }
15907   return FALSE;
15908 }
15909
15910 int
15911 StringFeature (char **p, char *name, char loc[], ChessProgramState *cps)
15912 {
15913   char buf[MSG_SIZ];
15914   int len = strlen(name);
15915   if (strncmp((*p), name, len) == 0
15916       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
15917     (*p) += len + 2;
15918     sscanf(*p, "%[^\"]", loc);
15919     while (**p && **p != '\"') (*p)++;
15920     if (**p == '\"') (*p)++;
15921     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15922     SendToProgram(buf, cps);
15923     return TRUE;
15924   }
15925   return FALSE;
15926 }
15927
15928 int
15929 ParseOption (Option *opt, ChessProgramState *cps)
15930 // [HGM] options: process the string that defines an engine option, and determine
15931 // name, type, default value, and allowed value range
15932 {
15933         char *p, *q, buf[MSG_SIZ];
15934         int n, min = (-1)<<31, max = 1<<31, def;
15935
15936         if(p = strstr(opt->name, " -spin ")) {
15937             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15938             if(max < min) max = min; // enforce consistency
15939             if(def < min) def = min;
15940             if(def > max) def = max;
15941             opt->value = def;
15942             opt->min = min;
15943             opt->max = max;
15944             opt->type = Spin;
15945         } else if((p = strstr(opt->name, " -slider "))) {
15946             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
15947             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15948             if(max < min) max = min; // enforce consistency
15949             if(def < min) def = min;
15950             if(def > max) def = max;
15951             opt->value = def;
15952             opt->min = min;
15953             opt->max = max;
15954             opt->type = Spin; // Slider;
15955         } else if((p = strstr(opt->name, " -string "))) {
15956             opt->textValue = p+9;
15957             opt->type = TextBox;
15958         } else if((p = strstr(opt->name, " -file "))) {
15959             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15960             opt->textValue = p+7;
15961             opt->type = FileName; // FileName;
15962         } else if((p = strstr(opt->name, " -path "))) {
15963             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15964             opt->textValue = p+7;
15965             opt->type = PathName; // PathName;
15966         } else if(p = strstr(opt->name, " -check ")) {
15967             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
15968             opt->value = (def != 0);
15969             opt->type = CheckBox;
15970         } else if(p = strstr(opt->name, " -combo ")) {
15971             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
15972             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
15973             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
15974             opt->value = n = 0;
15975             while(q = StrStr(q, " /// ")) {
15976                 n++; *q = 0;    // count choices, and null-terminate each of them
15977                 q += 5;
15978                 if(*q == '*') { // remember default, which is marked with * prefix
15979                     q++;
15980                     opt->value = n;
15981                 }
15982                 cps->comboList[cps->comboCnt++] = q;
15983             }
15984             cps->comboList[cps->comboCnt++] = NULL;
15985             opt->max = n + 1;
15986             opt->type = ComboBox;
15987         } else if(p = strstr(opt->name, " -button")) {
15988             opt->type = Button;
15989         } else if(p = strstr(opt->name, " -save")) {
15990             opt->type = SaveButton;
15991         } else return FALSE;
15992         *p = 0; // terminate option name
15993         // now look if the command-line options define a setting for this engine option.
15994         if(cps->optionSettings && cps->optionSettings[0])
15995             p = strstr(cps->optionSettings, opt->name); else p = NULL;
15996         if(p && (p == cps->optionSettings || p[-1] == ',')) {
15997           snprintf(buf, MSG_SIZ, "option %s", p);
15998                 if(p = strstr(buf, ",")) *p = 0;
15999                 if(q = strchr(buf, '=')) switch(opt->type) {
16000                     case ComboBox:
16001                         for(n=0; n<opt->max; n++)
16002                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
16003                         break;
16004                     case TextBox:
16005                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
16006                         break;
16007                     case Spin:
16008                     case CheckBox:
16009                         opt->value = atoi(q+1);
16010                     default:
16011                         break;
16012                 }
16013                 strcat(buf, "\n");
16014                 SendToProgram(buf, cps);
16015         }
16016         return TRUE;
16017 }
16018
16019 void
16020 FeatureDone (ChessProgramState *cps, int val)
16021 {
16022   DelayedEventCallback cb = GetDelayedEvent();
16023   if ((cb == InitBackEnd3 && cps == &first) ||
16024       (cb == SettingsMenuIfReady && cps == &second) ||
16025       (cb == LoadEngine) ||
16026       (cb == TwoMachinesEventIfReady)) {
16027     CancelDelayedEvent();
16028     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
16029   }
16030   cps->initDone = val;
16031   if(val) cps->reload = FALSE;
16032 }
16033
16034 /* Parse feature command from engine */
16035 void
16036 ParseFeatures (char *args, ChessProgramState *cps)
16037 {
16038   char *p = args;
16039   char *q;
16040   int val;
16041   char buf[MSG_SIZ];
16042
16043   for (;;) {
16044     while (*p == ' ') p++;
16045     if (*p == NULLCHAR) return;
16046
16047     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
16048     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
16049     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
16050     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
16051     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
16052     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
16053     if (BoolFeature(&p, "reuse", &val, cps)) {
16054       /* Engine can disable reuse, but can't enable it if user said no */
16055       if (!val) cps->reuse = FALSE;
16056       continue;
16057     }
16058     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
16059     if (StringFeature(&p, "myname", cps->tidy, cps)) {
16060       if (gameMode == TwoMachinesPlay) {
16061         DisplayTwoMachinesTitle();
16062       } else {
16063         DisplayTitle("");
16064       }
16065       continue;
16066     }
16067     if (StringFeature(&p, "variants", cps->variants, cps)) continue;
16068     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
16069     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
16070     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
16071     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
16072     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
16073     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
16074     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
16075     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
16076     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
16077     if (IntFeature(&p, "done", &val, cps)) {
16078       FeatureDone(cps, val);
16079       continue;
16080     }
16081     /* Added by Tord: */
16082     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
16083     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
16084     /* End of additions by Tord */
16085
16086     /* [HGM] added features: */
16087     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
16088     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
16089     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
16090     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
16091     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
16092     if (StringFeature(&p, "egt", cps->egtFormats, cps)) continue;
16093     if (StringFeature(&p, "option", buf, cps)) {
16094         if(cps->reload) continue; // we are reloading because of xreuse
16095         FREE(cps->option[cps->nrOptions].name);
16096         cps->option[cps->nrOptions].name = malloc(MSG_SIZ);
16097         safeStrCpy(cps->option[cps->nrOptions].name, buf, MSG_SIZ);
16098         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
16099           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
16100             SendToProgram(buf, cps);
16101             continue;
16102         }
16103         if(cps->nrOptions >= MAX_OPTIONS) {
16104             cps->nrOptions--;
16105             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
16106             DisplayError(buf, 0);
16107         }
16108         continue;
16109     }
16110     /* End of additions by HGM */
16111
16112     /* unknown feature: complain and skip */
16113     q = p;
16114     while (*q && *q != '=') q++;
16115     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
16116     SendToProgram(buf, cps);
16117     p = q;
16118     if (*p == '=') {
16119       p++;
16120       if (*p == '\"') {
16121         p++;
16122         while (*p && *p != '\"') p++;
16123         if (*p == '\"') p++;
16124       } else {
16125         while (*p && *p != ' ') p++;
16126       }
16127     }
16128   }
16129
16130 }
16131
16132 void
16133 PeriodicUpdatesEvent (int newState)
16134 {
16135     if (newState == appData.periodicUpdates)
16136       return;
16137
16138     appData.periodicUpdates=newState;
16139
16140     /* Display type changes, so update it now */
16141 //    DisplayAnalysis();
16142
16143     /* Get the ball rolling again... */
16144     if (newState) {
16145         AnalysisPeriodicEvent(1);
16146         StartAnalysisClock();
16147     }
16148 }
16149
16150 void
16151 PonderNextMoveEvent (int newState)
16152 {
16153     if (newState == appData.ponderNextMove) return;
16154     if (gameMode == EditPosition) EditPositionDone(TRUE);
16155     if (newState) {
16156         SendToProgram("hard\n", &first);
16157         if (gameMode == TwoMachinesPlay) {
16158             SendToProgram("hard\n", &second);
16159         }
16160     } else {
16161         SendToProgram("easy\n", &first);
16162         thinkOutput[0] = NULLCHAR;
16163         if (gameMode == TwoMachinesPlay) {
16164             SendToProgram("easy\n", &second);
16165         }
16166     }
16167     appData.ponderNextMove = newState;
16168 }
16169
16170 void
16171 NewSettingEvent (int option, int *feature, char *command, int value)
16172 {
16173     char buf[MSG_SIZ];
16174
16175     if (gameMode == EditPosition) EditPositionDone(TRUE);
16176     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
16177     if(feature == NULL || *feature) SendToProgram(buf, &first);
16178     if (gameMode == TwoMachinesPlay) {
16179         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
16180     }
16181 }
16182
16183 void
16184 ShowThinkingEvent ()
16185 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
16186 {
16187     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
16188     int newState = appData.showThinking
16189         // [HGM] thinking: other features now need thinking output as well
16190         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
16191
16192     if (oldState == newState) return;
16193     oldState = newState;
16194     if (gameMode == EditPosition) EditPositionDone(TRUE);
16195     if (oldState) {
16196         SendToProgram("post\n", &first);
16197         if (gameMode == TwoMachinesPlay) {
16198             SendToProgram("post\n", &second);
16199         }
16200     } else {
16201         SendToProgram("nopost\n", &first);
16202         thinkOutput[0] = NULLCHAR;
16203         if (gameMode == TwoMachinesPlay) {
16204             SendToProgram("nopost\n", &second);
16205         }
16206     }
16207 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
16208 }
16209
16210 void
16211 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
16212 {
16213   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
16214   if (pr == NoProc) return;
16215   AskQuestion(title, question, replyPrefix, pr);
16216 }
16217
16218 void
16219 TypeInEvent (char firstChar)
16220 {
16221     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
16222         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
16223         gameMode == AnalyzeMode || gameMode == EditGame ||
16224         gameMode == EditPosition || gameMode == IcsExamining ||
16225         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
16226         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
16227                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
16228                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
16229         gameMode == Training) PopUpMoveDialog(firstChar);
16230 }
16231
16232 void
16233 TypeInDoneEvent (char *move)
16234 {
16235         Board board;
16236         int n, fromX, fromY, toX, toY;
16237         char promoChar;
16238         ChessMove moveType;
16239
16240         // [HGM] FENedit
16241         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
16242                 EditPositionPasteFEN(move);
16243                 return;
16244         }
16245         // [HGM] movenum: allow move number to be typed in any mode
16246         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
16247           ToNrEvent(2*n-1);
16248           return;
16249         }
16250         // undocumented kludge: allow command-line option to be typed in!
16251         // (potentially fatal, and does not implement the effect of the option.)
16252         // should only be used for options that are values on which future decisions will be made,
16253         // and definitely not on options that would be used during initialization.
16254         if(strstr(move, "!!! -") == move) {
16255             ParseArgsFromString(move+4);
16256             return;
16257         }
16258
16259       if (gameMode != EditGame && currentMove != forwardMostMove &&
16260         gameMode != Training) {
16261         DisplayMoveError(_("Displayed move is not current"));
16262       } else {
16263         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16264           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
16265         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
16266         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16267           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
16268           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
16269         } else {
16270           DisplayMoveError(_("Could not parse move"));
16271         }
16272       }
16273 }
16274
16275 void
16276 DisplayMove (int moveNumber)
16277 {
16278     char message[MSG_SIZ];
16279     char res[MSG_SIZ];
16280     char cpThinkOutput[MSG_SIZ];
16281
16282     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
16283
16284     if (moveNumber == forwardMostMove - 1 ||
16285         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16286
16287         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
16288
16289         if (strchr(cpThinkOutput, '\n')) {
16290             *strchr(cpThinkOutput, '\n') = NULLCHAR;
16291         }
16292     } else {
16293         *cpThinkOutput = NULLCHAR;
16294     }
16295
16296     /* [AS] Hide thinking from human user */
16297     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
16298         *cpThinkOutput = NULLCHAR;
16299         if( thinkOutput[0] != NULLCHAR ) {
16300             int i;
16301
16302             for( i=0; i<=hiddenThinkOutputState; i++ ) {
16303                 cpThinkOutput[i] = '.';
16304             }
16305             cpThinkOutput[i] = NULLCHAR;
16306             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
16307         }
16308     }
16309
16310     if (moveNumber == forwardMostMove - 1 &&
16311         gameInfo.resultDetails != NULL) {
16312         if (gameInfo.resultDetails[0] == NULLCHAR) {
16313           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
16314         } else {
16315           snprintf(res, MSG_SIZ, " {%s} %s",
16316                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
16317         }
16318     } else {
16319         res[0] = NULLCHAR;
16320     }
16321
16322     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16323         DisplayMessage(res, cpThinkOutput);
16324     } else {
16325       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
16326                 WhiteOnMove(moveNumber) ? " " : ".. ",
16327                 parseList[moveNumber], res);
16328         DisplayMessage(message, cpThinkOutput);
16329     }
16330 }
16331
16332 void
16333 DisplayComment (int moveNumber, char *text)
16334 {
16335     char title[MSG_SIZ];
16336
16337     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16338       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
16339     } else {
16340       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
16341               WhiteOnMove(moveNumber) ? " " : ".. ",
16342               parseList[moveNumber]);
16343     }
16344     if (text != NULL && (appData.autoDisplayComment || commentUp))
16345         CommentPopUp(title, text);
16346 }
16347
16348 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
16349  * might be busy thinking or pondering.  It can be omitted if your
16350  * gnuchess is configured to stop thinking immediately on any user
16351  * input.  However, that gnuchess feature depends on the FIONREAD
16352  * ioctl, which does not work properly on some flavors of Unix.
16353  */
16354 void
16355 Attention (ChessProgramState *cps)
16356 {
16357 #if ATTENTION
16358     if (!cps->useSigint) return;
16359     if (appData.noChessProgram || (cps->pr == NoProc)) return;
16360     switch (gameMode) {
16361       case MachinePlaysWhite:
16362       case MachinePlaysBlack:
16363       case TwoMachinesPlay:
16364       case IcsPlayingWhite:
16365       case IcsPlayingBlack:
16366       case AnalyzeMode:
16367       case AnalyzeFile:
16368         /* Skip if we know it isn't thinking */
16369         if (!cps->maybeThinking) return;
16370         if (appData.debugMode)
16371           fprintf(debugFP, "Interrupting %s\n", cps->which);
16372         InterruptChildProcess(cps->pr);
16373         cps->maybeThinking = FALSE;
16374         break;
16375       default:
16376         break;
16377     }
16378 #endif /*ATTENTION*/
16379 }
16380
16381 int
16382 CheckFlags ()
16383 {
16384     if (whiteTimeRemaining <= 0) {
16385         if (!whiteFlag) {
16386             whiteFlag = TRUE;
16387             if (appData.icsActive) {
16388                 if (appData.autoCallFlag &&
16389                     gameMode == IcsPlayingBlack && !blackFlag) {
16390                   SendToICS(ics_prefix);
16391                   SendToICS("flag\n");
16392                 }
16393             } else {
16394                 if (blackFlag) {
16395                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16396                 } else {
16397                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
16398                     if (appData.autoCallFlag) {
16399                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
16400                         return TRUE;
16401                     }
16402                 }
16403             }
16404         }
16405     }
16406     if (blackTimeRemaining <= 0) {
16407         if (!blackFlag) {
16408             blackFlag = TRUE;
16409             if (appData.icsActive) {
16410                 if (appData.autoCallFlag &&
16411                     gameMode == IcsPlayingWhite && !whiteFlag) {
16412                   SendToICS(ics_prefix);
16413                   SendToICS("flag\n");
16414                 }
16415             } else {
16416                 if (whiteFlag) {
16417                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16418                 } else {
16419                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
16420                     if (appData.autoCallFlag) {
16421                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
16422                         return TRUE;
16423                     }
16424                 }
16425             }
16426         }
16427     }
16428     return FALSE;
16429 }
16430
16431 void
16432 CheckTimeControl ()
16433 {
16434     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
16435         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
16436
16437     /*
16438      * add time to clocks when time control is achieved ([HGM] now also used for increment)
16439      */
16440     if ( !WhiteOnMove(forwardMostMove) ) {
16441         /* White made time control */
16442         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
16443         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
16444         /* [HGM] time odds: correct new time quota for time odds! */
16445                                             / WhitePlayer()->timeOdds;
16446         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
16447     } else {
16448         lastBlack -= blackTimeRemaining;
16449         /* Black made time control */
16450         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
16451                                             / WhitePlayer()->other->timeOdds;
16452         lastWhite = whiteTimeRemaining;
16453     }
16454 }
16455
16456 void
16457 DisplayBothClocks ()
16458 {
16459     int wom = gameMode == EditPosition ?
16460       !blackPlaysFirst : WhiteOnMove(currentMove);
16461     DisplayWhiteClock(whiteTimeRemaining, wom);
16462     DisplayBlackClock(blackTimeRemaining, !wom);
16463 }
16464
16465
16466 /* Timekeeping seems to be a portability nightmare.  I think everyone
16467    has ftime(), but I'm really not sure, so I'm including some ifdefs
16468    to use other calls if you don't.  Clocks will be less accurate if
16469    you have neither ftime nor gettimeofday.
16470 */
16471
16472 /* VS 2008 requires the #include outside of the function */
16473 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
16474 #include <sys/timeb.h>
16475 #endif
16476
16477 /* Get the current time as a TimeMark */
16478 void
16479 GetTimeMark (TimeMark *tm)
16480 {
16481 #if HAVE_GETTIMEOFDAY
16482
16483     struct timeval timeVal;
16484     struct timezone timeZone;
16485
16486     gettimeofday(&timeVal, &timeZone);
16487     tm->sec = (long) timeVal.tv_sec;
16488     tm->ms = (int) (timeVal.tv_usec / 1000L);
16489
16490 #else /*!HAVE_GETTIMEOFDAY*/
16491 #if HAVE_FTIME
16492
16493 // include <sys/timeb.h> / moved to just above start of function
16494     struct timeb timeB;
16495
16496     ftime(&timeB);
16497     tm->sec = (long) timeB.time;
16498     tm->ms = (int) timeB.millitm;
16499
16500 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
16501     tm->sec = (long) time(NULL);
16502     tm->ms = 0;
16503 #endif
16504 #endif
16505 }
16506
16507 /* Return the difference in milliseconds between two
16508    time marks.  We assume the difference will fit in a long!
16509 */
16510 long
16511 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
16512 {
16513     return 1000L*(tm2->sec - tm1->sec) +
16514            (long) (tm2->ms - tm1->ms);
16515 }
16516
16517
16518 /*
16519  * Code to manage the game clocks.
16520  *
16521  * In tournament play, black starts the clock and then white makes a move.
16522  * We give the human user a slight advantage if he is playing white---the
16523  * clocks don't run until he makes his first move, so it takes zero time.
16524  * Also, we don't account for network lag, so we could get out of sync
16525  * with GNU Chess's clock -- but then, referees are always right.
16526  */
16527
16528 static TimeMark tickStartTM;
16529 static long intendedTickLength;
16530
16531 long
16532 NextTickLength (long timeRemaining)
16533 {
16534     long nominalTickLength, nextTickLength;
16535
16536     if (timeRemaining > 0L && timeRemaining <= 10000L)
16537       nominalTickLength = 100L;
16538     else
16539       nominalTickLength = 1000L;
16540     nextTickLength = timeRemaining % nominalTickLength;
16541     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
16542
16543     return nextTickLength;
16544 }
16545
16546 /* Adjust clock one minute up or down */
16547 void
16548 AdjustClock (Boolean which, int dir)
16549 {
16550     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
16551     if(which) blackTimeRemaining += 60000*dir;
16552     else      whiteTimeRemaining += 60000*dir;
16553     DisplayBothClocks();
16554     adjustedClock = TRUE;
16555 }
16556
16557 /* Stop clocks and reset to a fresh time control */
16558 void
16559 ResetClocks ()
16560 {
16561     (void) StopClockTimer();
16562     if (appData.icsActive) {
16563         whiteTimeRemaining = blackTimeRemaining = 0;
16564     } else if (searchTime) {
16565         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16566         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16567     } else { /* [HGM] correct new time quote for time odds */
16568         whiteTC = blackTC = fullTimeControlString;
16569         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
16570         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
16571     }
16572     if (whiteFlag || blackFlag) {
16573         DisplayTitle("");
16574         whiteFlag = blackFlag = FALSE;
16575     }
16576     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
16577     DisplayBothClocks();
16578     adjustedClock = FALSE;
16579 }
16580
16581 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
16582
16583 /* Decrement running clock by amount of time that has passed */
16584 void
16585 DecrementClocks ()
16586 {
16587     long timeRemaining;
16588     long lastTickLength, fudge;
16589     TimeMark now;
16590
16591     if (!appData.clockMode) return;
16592     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
16593
16594     GetTimeMark(&now);
16595
16596     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16597
16598     /* Fudge if we woke up a little too soon */
16599     fudge = intendedTickLength - lastTickLength;
16600     if (fudge < 0 || fudge > FUDGE) fudge = 0;
16601
16602     if (WhiteOnMove(forwardMostMove)) {
16603         if(whiteNPS >= 0) lastTickLength = 0;
16604         timeRemaining = whiteTimeRemaining -= lastTickLength;
16605         if(timeRemaining < 0 && !appData.icsActive) {
16606             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
16607             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
16608                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
16609                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
16610             }
16611         }
16612         DisplayWhiteClock(whiteTimeRemaining - fudge,
16613                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16614     } else {
16615         if(blackNPS >= 0) lastTickLength = 0;
16616         timeRemaining = blackTimeRemaining -= lastTickLength;
16617         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
16618             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
16619             if(suddenDeath) {
16620                 blackStartMove = forwardMostMove;
16621                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
16622             }
16623         }
16624         DisplayBlackClock(blackTimeRemaining - fudge,
16625                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16626     }
16627     if (CheckFlags()) return;
16628
16629     if(twoBoards) { // count down secondary board's clocks as well
16630         activePartnerTime -= lastTickLength;
16631         partnerUp = 1;
16632         if(activePartner == 'W')
16633             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
16634         else
16635             DisplayBlackClock(activePartnerTime, TRUE);
16636         partnerUp = 0;
16637     }
16638
16639     tickStartTM = now;
16640     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
16641     StartClockTimer(intendedTickLength);
16642
16643     /* if the time remaining has fallen below the alarm threshold, sound the
16644      * alarm. if the alarm has sounded and (due to a takeback or time control
16645      * with increment) the time remaining has increased to a level above the
16646      * threshold, reset the alarm so it can sound again.
16647      */
16648
16649     if (appData.icsActive && appData.icsAlarm) {
16650
16651         /* make sure we are dealing with the user's clock */
16652         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
16653                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
16654            )) return;
16655
16656         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
16657             alarmSounded = FALSE;
16658         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
16659             PlayAlarmSound();
16660             alarmSounded = TRUE;
16661         }
16662     }
16663 }
16664
16665
16666 /* A player has just moved, so stop the previously running
16667    clock and (if in clock mode) start the other one.
16668    We redisplay both clocks in case we're in ICS mode, because
16669    ICS gives us an update to both clocks after every move.
16670    Note that this routine is called *after* forwardMostMove
16671    is updated, so the last fractional tick must be subtracted
16672    from the color that is *not* on move now.
16673 */
16674 void
16675 SwitchClocks (int newMoveNr)
16676 {
16677     long lastTickLength;
16678     TimeMark now;
16679     int flagged = FALSE;
16680
16681     GetTimeMark(&now);
16682
16683     if (StopClockTimer() && appData.clockMode) {
16684         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16685         if (!WhiteOnMove(forwardMostMove)) {
16686             if(blackNPS >= 0) lastTickLength = 0;
16687             blackTimeRemaining -= lastTickLength;
16688            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16689 //         if(pvInfoList[forwardMostMove].time == -1)
16690                  pvInfoList[forwardMostMove].time =               // use GUI time
16691                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
16692         } else {
16693            if(whiteNPS >= 0) lastTickLength = 0;
16694            whiteTimeRemaining -= lastTickLength;
16695            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16696 //         if(pvInfoList[forwardMostMove].time == -1)
16697                  pvInfoList[forwardMostMove].time =
16698                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
16699         }
16700         flagged = CheckFlags();
16701     }
16702     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
16703     CheckTimeControl();
16704
16705     if (flagged || !appData.clockMode) return;
16706
16707     switch (gameMode) {
16708       case MachinePlaysBlack:
16709       case MachinePlaysWhite:
16710       case BeginningOfGame:
16711         if (pausing) return;
16712         break;
16713
16714       case EditGame:
16715       case PlayFromGameFile:
16716       case IcsExamining:
16717         return;
16718
16719       default:
16720         break;
16721     }
16722
16723     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
16724         if(WhiteOnMove(forwardMostMove))
16725              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16726         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16727     }
16728
16729     tickStartTM = now;
16730     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16731       whiteTimeRemaining : blackTimeRemaining);
16732     StartClockTimer(intendedTickLength);
16733 }
16734
16735
16736 /* Stop both clocks */
16737 void
16738 StopClocks ()
16739 {
16740     long lastTickLength;
16741     TimeMark now;
16742
16743     if (!StopClockTimer()) return;
16744     if (!appData.clockMode) return;
16745
16746     GetTimeMark(&now);
16747
16748     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16749     if (WhiteOnMove(forwardMostMove)) {
16750         if(whiteNPS >= 0) lastTickLength = 0;
16751         whiteTimeRemaining -= lastTickLength;
16752         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
16753     } else {
16754         if(blackNPS >= 0) lastTickLength = 0;
16755         blackTimeRemaining -= lastTickLength;
16756         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
16757     }
16758     CheckFlags();
16759 }
16760
16761 /* Start clock of player on move.  Time may have been reset, so
16762    if clock is already running, stop and restart it. */
16763 void
16764 StartClocks ()
16765 {
16766     (void) StopClockTimer(); /* in case it was running already */
16767     DisplayBothClocks();
16768     if (CheckFlags()) return;
16769
16770     if (!appData.clockMode) return;
16771     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
16772
16773     GetTimeMark(&tickStartTM);
16774     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16775       whiteTimeRemaining : blackTimeRemaining);
16776
16777    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
16778     whiteNPS = blackNPS = -1;
16779     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
16780        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
16781         whiteNPS = first.nps;
16782     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
16783        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
16784         blackNPS = first.nps;
16785     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
16786         whiteNPS = second.nps;
16787     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
16788         blackNPS = second.nps;
16789     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
16790
16791     StartClockTimer(intendedTickLength);
16792 }
16793
16794 char *
16795 TimeString (long ms)
16796 {
16797     long second, minute, hour, day;
16798     char *sign = "";
16799     static char buf[32];
16800
16801     if (ms > 0 && ms <= 9900) {
16802       /* convert milliseconds to tenths, rounding up */
16803       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
16804
16805       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
16806       return buf;
16807     }
16808
16809     /* convert milliseconds to seconds, rounding up */
16810     /* use floating point to avoid strangeness of integer division
16811        with negative dividends on many machines */
16812     second = (long) floor(((double) (ms + 999L)) / 1000.0);
16813
16814     if (second < 0) {
16815         sign = "-";
16816         second = -second;
16817     }
16818
16819     day = second / (60 * 60 * 24);
16820     second = second % (60 * 60 * 24);
16821     hour = second / (60 * 60);
16822     second = second % (60 * 60);
16823     minute = second / 60;
16824     second = second % 60;
16825
16826     if (day > 0)
16827       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
16828               sign, day, hour, minute, second);
16829     else if (hour > 0)
16830       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
16831     else
16832       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
16833
16834     return buf;
16835 }
16836
16837
16838 /*
16839  * This is necessary because some C libraries aren't ANSI C compliant yet.
16840  */
16841 char *
16842 StrStr (char *string, char *match)
16843 {
16844     int i, length;
16845
16846     length = strlen(match);
16847
16848     for (i = strlen(string) - length; i >= 0; i--, string++)
16849       if (!strncmp(match, string, length))
16850         return string;
16851
16852     return NULL;
16853 }
16854
16855 char *
16856 StrCaseStr (char *string, char *match)
16857 {
16858     int i, j, length;
16859
16860     length = strlen(match);
16861
16862     for (i = strlen(string) - length; i >= 0; i--, string++) {
16863         for (j = 0; j < length; j++) {
16864             if (ToLower(match[j]) != ToLower(string[j]))
16865               break;
16866         }
16867         if (j == length) return string;
16868     }
16869
16870     return NULL;
16871 }
16872
16873 #ifndef _amigados
16874 int
16875 StrCaseCmp (char *s1, char *s2)
16876 {
16877     char c1, c2;
16878
16879     for (;;) {
16880         c1 = ToLower(*s1++);
16881         c2 = ToLower(*s2++);
16882         if (c1 > c2) return 1;
16883         if (c1 < c2) return -1;
16884         if (c1 == NULLCHAR) return 0;
16885     }
16886 }
16887
16888
16889 int
16890 ToLower (int c)
16891 {
16892     return isupper(c) ? tolower(c) : c;
16893 }
16894
16895
16896 int
16897 ToUpper (int c)
16898 {
16899     return islower(c) ? toupper(c) : c;
16900 }
16901 #endif /* !_amigados    */
16902
16903 char *
16904 StrSave (char *s)
16905 {
16906   char *ret;
16907
16908   if ((ret = (char *) malloc(strlen(s) + 1)))
16909     {
16910       safeStrCpy(ret, s, strlen(s)+1);
16911     }
16912   return ret;
16913 }
16914
16915 char *
16916 StrSavePtr (char *s, char **savePtr)
16917 {
16918     if (*savePtr) {
16919         free(*savePtr);
16920     }
16921     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
16922       safeStrCpy(*savePtr, s, strlen(s)+1);
16923     }
16924     return(*savePtr);
16925 }
16926
16927 char *
16928 PGNDate ()
16929 {
16930     time_t clock;
16931     struct tm *tm;
16932     char buf[MSG_SIZ];
16933
16934     clock = time((time_t *)NULL);
16935     tm = localtime(&clock);
16936     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
16937             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
16938     return StrSave(buf);
16939 }
16940
16941
16942 char *
16943 PositionToFEN (int move, char *overrideCastling)
16944 {
16945     int i, j, fromX, fromY, toX, toY;
16946     int whiteToPlay;
16947     char buf[MSG_SIZ];
16948     char *p, *q;
16949     int emptycount;
16950     ChessSquare piece;
16951
16952     whiteToPlay = (gameMode == EditPosition) ?
16953       !blackPlaysFirst : (move % 2 == 0);
16954     p = buf;
16955
16956     /* Piece placement data */
16957     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16958         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
16959         emptycount = 0;
16960         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16961             if (boards[move][i][j] == EmptySquare) {
16962                 emptycount++;
16963             } else { ChessSquare piece = boards[move][i][j];
16964                 if (emptycount > 0) {
16965                     if(emptycount<10) /* [HGM] can be >= 10 */
16966                         *p++ = '0' + emptycount;
16967                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16968                     emptycount = 0;
16969                 }
16970                 if(PieceToChar(piece) == '+') {
16971                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
16972                     *p++ = '+';
16973                     piece = (ChessSquare)(DEMOTED piece);
16974                 }
16975                 *p++ = PieceToChar(piece);
16976                 if(p[-1] == '~') {
16977                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
16978                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
16979                     *p++ = '~';
16980                 }
16981             }
16982         }
16983         if (emptycount > 0) {
16984             if(emptycount<10) /* [HGM] can be >= 10 */
16985                 *p++ = '0' + emptycount;
16986             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16987             emptycount = 0;
16988         }
16989         *p++ = '/';
16990     }
16991     *(p - 1) = ' ';
16992
16993     /* [HGM] print Crazyhouse or Shogi holdings */
16994     if( gameInfo.holdingsWidth ) {
16995         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
16996         q = p;
16997         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
16998             piece = boards[move][i][BOARD_WIDTH-1];
16999             if( piece != EmptySquare )
17000               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
17001                   *p++ = PieceToChar(piece);
17002         }
17003         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
17004             piece = boards[move][BOARD_HEIGHT-i-1][0];
17005             if( piece != EmptySquare )
17006               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
17007                   *p++ = PieceToChar(piece);
17008         }
17009
17010         if( q == p ) *p++ = '-';
17011         *p++ = ']';
17012         *p++ = ' ';
17013     }
17014
17015     /* Active color */
17016     *p++ = whiteToPlay ? 'w' : 'b';
17017     *p++ = ' ';
17018
17019   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
17020     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
17021   } else {
17022   if(nrCastlingRights) {
17023      q = p;
17024      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
17025        /* [HGM] write directly from rights */
17026            if(boards[move][CASTLING][2] != NoRights &&
17027               boards[move][CASTLING][0] != NoRights   )
17028                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
17029            if(boards[move][CASTLING][2] != NoRights &&
17030               boards[move][CASTLING][1] != NoRights   )
17031                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
17032            if(boards[move][CASTLING][5] != NoRights &&
17033               boards[move][CASTLING][3] != NoRights   )
17034                 *p++ = boards[move][CASTLING][3] + AAA;
17035            if(boards[move][CASTLING][5] != NoRights &&
17036               boards[move][CASTLING][4] != NoRights   )
17037                 *p++ = boards[move][CASTLING][4] + AAA;
17038      } else {
17039
17040         /* [HGM] write true castling rights */
17041         if( nrCastlingRights == 6 ) {
17042             int q, k=0;
17043             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
17044                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
17045             q = (boards[move][CASTLING][1] == BOARD_LEFT &&
17046                  boards[move][CASTLING][2] != NoRights  );
17047             if(gameInfo.variant == VariantSChess) { // for S-Chess, indicate all vrgin backrank pieces
17048                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_RGHT]; // count white held pieces
17049                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17050                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
17051                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
17052             }
17053             if(q) *p++ = 'Q';
17054             k = 0;
17055             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
17056                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
17057             q = (boards[move][CASTLING][4] == BOARD_LEFT &&
17058                  boards[move][CASTLING][5] != NoRights  );
17059             if(gameInfo.variant == VariantSChess) {
17060                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_LEFT-1]; // count black held pieces
17061                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17062                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
17063                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17064             }
17065             if(q) *p++ = 'q';
17066         }
17067      }
17068      if (q == p) *p++ = '-'; /* No castling rights */
17069      *p++ = ' ';
17070   }
17071
17072   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17073      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
17074     /* En passant target square */
17075     if (move > backwardMostMove) {
17076         fromX = moveList[move - 1][0] - AAA;
17077         fromY = moveList[move - 1][1] - ONE;
17078         toX = moveList[move - 1][2] - AAA;
17079         toY = moveList[move - 1][3] - ONE;
17080         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
17081             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
17082             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
17083             fromX == toX) {
17084             /* 2-square pawn move just happened */
17085             *p++ = toX + AAA;
17086             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17087         } else {
17088             *p++ = '-';
17089         }
17090     } else if(move == backwardMostMove) {
17091         // [HGM] perhaps we should always do it like this, and forget the above?
17092         if((signed char)boards[move][EP_STATUS] >= 0) {
17093             *p++ = boards[move][EP_STATUS] + AAA;
17094             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17095         } else {
17096             *p++ = '-';
17097         }
17098     } else {
17099         *p++ = '-';
17100     }
17101     *p++ = ' ';
17102   }
17103   }
17104
17105     /* [HGM] find reversible plies */
17106     {   int i = 0, j=move;
17107
17108         if (appData.debugMode) { int k;
17109             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
17110             for(k=backwardMostMove; k<=forwardMostMove; k++)
17111                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
17112
17113         }
17114
17115         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
17116         if( j == backwardMostMove ) i += initialRulePlies;
17117         sprintf(p, "%d ", i);
17118         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
17119     }
17120     /* Fullmove number */
17121     sprintf(p, "%d", (move / 2) + 1);
17122
17123     return StrSave(buf);
17124 }
17125
17126 Boolean
17127 ParseFEN (Board board, int *blackPlaysFirst, char *fen)
17128 {
17129     int i, j;
17130     char *p, c;
17131     int emptycount, virgin[BOARD_FILES];
17132     ChessSquare piece;
17133
17134     p = fen;
17135
17136     /* [HGM] by default clear Crazyhouse holdings, if present */
17137     if(gameInfo.holdingsWidth) {
17138        for(i=0; i<BOARD_HEIGHT; i++) {
17139            board[i][0]             = EmptySquare; /* black holdings */
17140            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
17141            board[i][1]             = (ChessSquare) 0; /* black counts */
17142            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
17143        }
17144     }
17145
17146     /* Piece placement data */
17147     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17148         j = 0;
17149         for (;;) {
17150             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
17151                 if (*p == '/') p++;
17152                 emptycount = gameInfo.boardWidth - j;
17153                 while (emptycount--)
17154                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17155                 break;
17156 #if(BOARD_FILES >= 10)
17157             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
17158                 p++; emptycount=10;
17159                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17160                 while (emptycount--)
17161                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17162 #endif
17163             } else if (isdigit(*p)) {
17164                 emptycount = *p++ - '0';
17165                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
17166                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17167                 while (emptycount--)
17168                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17169             } else if (*p == '+' || isalpha(*p)) {
17170                 if (j >= gameInfo.boardWidth) return FALSE;
17171                 if(*p=='+') {
17172                     piece = CharToPiece(*++p);
17173                     if(piece == EmptySquare) return FALSE; /* unknown piece */
17174                     piece = (ChessSquare) (PROMOTED piece ); p++;
17175                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
17176                 } else piece = CharToPiece(*p++);
17177
17178                 if(piece==EmptySquare) return FALSE; /* unknown piece */
17179                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
17180                     piece = (ChessSquare) (PROMOTED piece);
17181                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
17182                     p++;
17183                 }
17184                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
17185             } else {
17186                 return FALSE;
17187             }
17188         }
17189     }
17190     while (*p == '/' || *p == ' ') p++;
17191
17192     /* [HGM] look for Crazyhouse holdings here */
17193     while(*p==' ') p++;
17194     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
17195         if(*p == '[') p++;
17196         if(*p == '-' ) p++; /* empty holdings */ else {
17197             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
17198             /* if we would allow FEN reading to set board size, we would   */
17199             /* have to add holdings and shift the board read so far here   */
17200             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
17201                 p++;
17202                 if((int) piece >= (int) BlackPawn ) {
17203                     i = (int)piece - (int)BlackPawn;
17204                     i = PieceToNumber((ChessSquare)i);
17205                     if( i >= gameInfo.holdingsSize ) return FALSE;
17206                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
17207                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
17208                 } else {
17209                     i = (int)piece - (int)WhitePawn;
17210                     i = PieceToNumber((ChessSquare)i);
17211                     if( i >= gameInfo.holdingsSize ) return FALSE;
17212                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
17213                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
17214                 }
17215             }
17216         }
17217         if(*p == ']') p++;
17218     }
17219
17220     while(*p == ' ') p++;
17221
17222     /* Active color */
17223     c = *p++;
17224     if(appData.colorNickNames) {
17225       if( c == appData.colorNickNames[0] ) c = 'w'; else
17226       if( c == appData.colorNickNames[1] ) c = 'b';
17227     }
17228     switch (c) {
17229       case 'w':
17230         *blackPlaysFirst = FALSE;
17231         break;
17232       case 'b':
17233         *blackPlaysFirst = TRUE;
17234         break;
17235       default:
17236         return FALSE;
17237     }
17238
17239     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
17240     /* return the extra info in global variiables             */
17241
17242     /* set defaults in case FEN is incomplete */
17243     board[EP_STATUS] = EP_UNKNOWN;
17244     for(i=0; i<nrCastlingRights; i++ ) {
17245         board[CASTLING][i] =
17246             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
17247     }   /* assume possible unless obviously impossible */
17248     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
17249     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
17250     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
17251                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
17252     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
17253     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
17254     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
17255                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
17256     FENrulePlies = 0;
17257
17258     while(*p==' ') p++;
17259     if(nrCastlingRights) {
17260       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
17261       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
17262           /* castling indicator present, so default becomes no castlings */
17263           for(i=0; i<nrCastlingRights; i++ ) {
17264                  board[CASTLING][i] = NoRights;
17265           }
17266       }
17267       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
17268              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess) &&
17269              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
17270              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
17271         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
17272
17273         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
17274             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
17275             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
17276         }
17277         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
17278             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
17279         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
17280                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
17281         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
17282                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
17283         switch(c) {
17284           case'K':
17285               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
17286               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
17287               board[CASTLING][2] = whiteKingFile;
17288               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
17289               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17290               break;
17291           case'Q':
17292               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
17293               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
17294               board[CASTLING][2] = whiteKingFile;
17295               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
17296               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17297               break;
17298           case'k':
17299               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
17300               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
17301               board[CASTLING][5] = blackKingFile;
17302               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
17303               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17304               break;
17305           case'q':
17306               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
17307               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
17308               board[CASTLING][5] = blackKingFile;
17309               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
17310               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17311           case '-':
17312               break;
17313           default: /* FRC castlings */
17314               if(c >= 'a') { /* black rights */
17315                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
17316                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17317                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
17318                   if(i == BOARD_RGHT) break;
17319                   board[CASTLING][5] = i;
17320                   c -= AAA;
17321                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
17322                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
17323                   if(c > i)
17324                       board[CASTLING][3] = c;
17325                   else
17326                       board[CASTLING][4] = c;
17327               } else { /* white rights */
17328                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
17329                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17330                     if(board[0][i] == WhiteKing) break;
17331                   if(i == BOARD_RGHT) break;
17332                   board[CASTLING][2] = i;
17333                   c -= AAA - 'a' + 'A';
17334                   if(board[0][c] >= WhiteKing) break;
17335                   if(c > i)
17336                       board[CASTLING][0] = c;
17337                   else
17338                       board[CASTLING][1] = c;
17339               }
17340         }
17341       }
17342       for(i=0; i<nrCastlingRights; i++)
17343         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
17344       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = virgin[i];
17345     if (appData.debugMode) {
17346         fprintf(debugFP, "FEN castling rights:");
17347         for(i=0; i<nrCastlingRights; i++)
17348         fprintf(debugFP, " %d", board[CASTLING][i]);
17349         fprintf(debugFP, "\n");
17350     }
17351
17352       while(*p==' ') p++;
17353     }
17354
17355     /* read e.p. field in games that know e.p. capture */
17356     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17357        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
17358       if(*p=='-') {
17359         p++; board[EP_STATUS] = EP_NONE;
17360       } else {
17361          char c = *p++ - AAA;
17362
17363          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
17364          if(*p >= '0' && *p <='9') p++;
17365          board[EP_STATUS] = c;
17366       }
17367     }
17368
17369
17370     if(sscanf(p, "%d", &i) == 1) {
17371         FENrulePlies = i; /* 50-move ply counter */
17372         /* (The move number is still ignored)    */
17373     }
17374
17375     return TRUE;
17376 }
17377
17378 void
17379 EditPositionPasteFEN (char *fen)
17380 {
17381   if (fen != NULL) {
17382     Board initial_position;
17383
17384     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
17385       DisplayError(_("Bad FEN position in clipboard"), 0);
17386       return ;
17387     } else {
17388       int savedBlackPlaysFirst = blackPlaysFirst;
17389       EditPositionEvent();
17390       blackPlaysFirst = savedBlackPlaysFirst;
17391       CopyBoard(boards[0], initial_position);
17392       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
17393       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
17394       DisplayBothClocks();
17395       DrawPosition(FALSE, boards[currentMove]);
17396     }
17397   }
17398 }
17399
17400 static char cseq[12] = "\\   ";
17401
17402 Boolean
17403 set_cont_sequence (char *new_seq)
17404 {
17405     int len;
17406     Boolean ret;
17407
17408     // handle bad attempts to set the sequence
17409         if (!new_seq)
17410                 return 0; // acceptable error - no debug
17411
17412     len = strlen(new_seq);
17413     ret = (len > 0) && (len < sizeof(cseq));
17414     if (ret)
17415       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
17416     else if (appData.debugMode)
17417       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
17418     return ret;
17419 }
17420
17421 /*
17422     reformat a source message so words don't cross the width boundary.  internal
17423     newlines are not removed.  returns the wrapped size (no null character unless
17424     included in source message).  If dest is NULL, only calculate the size required
17425     for the dest buffer.  lp argument indicats line position upon entry, and it's
17426     passed back upon exit.
17427 */
17428 int
17429 wrap (char *dest, char *src, int count, int width, int *lp)
17430 {
17431     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
17432
17433     cseq_len = strlen(cseq);
17434     old_line = line = *lp;
17435     ansi = len = clen = 0;
17436
17437     for (i=0; i < count; i++)
17438     {
17439         if (src[i] == '\033')
17440             ansi = 1;
17441
17442         // if we hit the width, back up
17443         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
17444         {
17445             // store i & len in case the word is too long
17446             old_i = i, old_len = len;
17447
17448             // find the end of the last word
17449             while (i && src[i] != ' ' && src[i] != '\n')
17450             {
17451                 i--;
17452                 len--;
17453             }
17454
17455             // word too long?  restore i & len before splitting it
17456             if ((old_i-i+clen) >= width)
17457             {
17458                 i = old_i;
17459                 len = old_len;
17460             }
17461
17462             // extra space?
17463             if (i && src[i-1] == ' ')
17464                 len--;
17465
17466             if (src[i] != ' ' && src[i] != '\n')
17467             {
17468                 i--;
17469                 if (len)
17470                     len--;
17471             }
17472
17473             // now append the newline and continuation sequence
17474             if (dest)
17475                 dest[len] = '\n';
17476             len++;
17477             if (dest)
17478                 strncpy(dest+len, cseq, cseq_len);
17479             len += cseq_len;
17480             line = cseq_len;
17481             clen = cseq_len;
17482             continue;
17483         }
17484
17485         if (dest)
17486             dest[len] = src[i];
17487         len++;
17488         if (!ansi)
17489             line++;
17490         if (src[i] == '\n')
17491             line = 0;
17492         if (src[i] == 'm')
17493             ansi = 0;
17494     }
17495     if (dest && appData.debugMode)
17496     {
17497         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
17498             count, width, line, len, *lp);
17499         show_bytes(debugFP, src, count);
17500         fprintf(debugFP, "\ndest: ");
17501         show_bytes(debugFP, dest, len);
17502         fprintf(debugFP, "\n");
17503     }
17504     *lp = dest ? line : old_line;
17505
17506     return len;
17507 }
17508
17509 // [HGM] vari: routines for shelving variations
17510 Boolean modeRestore = FALSE;
17511
17512 void
17513 PushInner (int firstMove, int lastMove)
17514 {
17515         int i, j, nrMoves = lastMove - firstMove;
17516
17517         // push current tail of game on stack
17518         savedResult[storedGames] = gameInfo.result;
17519         savedDetails[storedGames] = gameInfo.resultDetails;
17520         gameInfo.resultDetails = NULL;
17521         savedFirst[storedGames] = firstMove;
17522         savedLast [storedGames] = lastMove;
17523         savedFramePtr[storedGames] = framePtr;
17524         framePtr -= nrMoves; // reserve space for the boards
17525         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
17526             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
17527             for(j=0; j<MOVE_LEN; j++)
17528                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
17529             for(j=0; j<2*MOVE_LEN; j++)
17530                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
17531             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
17532             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
17533             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
17534             pvInfoList[firstMove+i-1].depth = 0;
17535             commentList[framePtr+i] = commentList[firstMove+i];
17536             commentList[firstMove+i] = NULL;
17537         }
17538
17539         storedGames++;
17540         forwardMostMove = firstMove; // truncate game so we can start variation
17541 }
17542
17543 void
17544 PushTail (int firstMove, int lastMove)
17545 {
17546         if(appData.icsActive) { // only in local mode
17547                 forwardMostMove = currentMove; // mimic old ICS behavior
17548                 return;
17549         }
17550         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
17551
17552         PushInner(firstMove, lastMove);
17553         if(storedGames == 1) GreyRevert(FALSE);
17554         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
17555 }
17556
17557 void
17558 PopInner (Boolean annotate)
17559 {
17560         int i, j, nrMoves;
17561         char buf[8000], moveBuf[20];
17562
17563         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
17564         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
17565         nrMoves = savedLast[storedGames] - currentMove;
17566         if(annotate) {
17567                 int cnt = 10;
17568                 if(!WhiteOnMove(currentMove))
17569                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
17570                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
17571                 for(i=currentMove; i<forwardMostMove; i++) {
17572                         if(WhiteOnMove(i))
17573                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
17574                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
17575                         strcat(buf, moveBuf);
17576                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
17577                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
17578                 }
17579                 strcat(buf, ")");
17580         }
17581         for(i=1; i<=nrMoves; i++) { // copy last variation back
17582             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
17583             for(j=0; j<MOVE_LEN; j++)
17584                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
17585             for(j=0; j<2*MOVE_LEN; j++)
17586                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
17587             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
17588             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
17589             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
17590             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
17591             commentList[currentMove+i] = commentList[framePtr+i];
17592             commentList[framePtr+i] = NULL;
17593         }
17594         if(annotate) AppendComment(currentMove+1, buf, FALSE);
17595         framePtr = savedFramePtr[storedGames];
17596         gameInfo.result = savedResult[storedGames];
17597         if(gameInfo.resultDetails != NULL) {
17598             free(gameInfo.resultDetails);
17599       }
17600         gameInfo.resultDetails = savedDetails[storedGames];
17601         forwardMostMove = currentMove + nrMoves;
17602 }
17603
17604 Boolean
17605 PopTail (Boolean annotate)
17606 {
17607         if(appData.icsActive) return FALSE; // only in local mode
17608         if(!storedGames) return FALSE; // sanity
17609         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
17610
17611         PopInner(annotate);
17612         if(currentMove < forwardMostMove) ForwardEvent(); else
17613         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
17614
17615         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
17616         return TRUE;
17617 }
17618
17619 void
17620 CleanupTail ()
17621 {       // remove all shelved variations
17622         int i;
17623         for(i=0; i<storedGames; i++) {
17624             if(savedDetails[i])
17625                 free(savedDetails[i]);
17626             savedDetails[i] = NULL;
17627         }
17628         for(i=framePtr; i<MAX_MOVES; i++) {
17629                 if(commentList[i]) free(commentList[i]);
17630                 commentList[i] = NULL;
17631         }
17632         framePtr = MAX_MOVES-1;
17633         storedGames = 0;
17634 }
17635
17636 void
17637 LoadVariation (int index, char *text)
17638 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
17639         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
17640         int level = 0, move;
17641
17642         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
17643         // first find outermost bracketing variation
17644         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
17645             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
17646                 if(*p == '{') wait = '}'; else
17647                 if(*p == '[') wait = ']'; else
17648                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
17649                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
17650             }
17651             if(*p == wait) wait = NULLCHAR; // closing ]} found
17652             p++;
17653         }
17654         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
17655         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
17656         end[1] = NULLCHAR; // clip off comment beyond variation
17657         ToNrEvent(currentMove-1);
17658         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
17659         // kludge: use ParsePV() to append variation to game
17660         move = currentMove;
17661         ParsePV(start, TRUE, TRUE);
17662         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
17663         ClearPremoveHighlights();
17664         CommentPopDown();
17665         ToNrEvent(currentMove+1);
17666 }
17667
17668 void
17669 LoadTheme ()
17670 {
17671     char *p, *q, buf[MSG_SIZ];
17672     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
17673         snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
17674         ParseArgsFromString(buf);
17675         ActivateTheme(TRUE); // also redo colors
17676         return;
17677     }
17678     p = nickName;
17679     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
17680     {
17681         int len;
17682         q = appData.themeNames;
17683         snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
17684       if(appData.useBitmaps) {
17685         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
17686                 appData.liteBackTextureFile, appData.darkBackTextureFile,
17687                 appData.liteBackTextureMode,
17688                 appData.darkBackTextureMode );
17689       } else {
17690         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
17691                 Col2Text(2),   // lightSquareColor
17692                 Col2Text(3) ); // darkSquareColor
17693       }
17694       if(appData.useBorder) {
17695         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
17696                 appData.border);
17697       } else {
17698         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
17699       }
17700       if(appData.useFont) {
17701         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
17702                 appData.renderPiecesWithFont,
17703                 appData.fontToPieceTable,
17704                 Col2Text(9),    // appData.fontBackColorWhite
17705                 Col2Text(10) ); // appData.fontForeColorBlack
17706       } else {
17707         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
17708                 appData.pieceDirectory);
17709         if(!appData.pieceDirectory[0])
17710           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
17711                 Col2Text(0),   // whitePieceColor
17712                 Col2Text(1) ); // blackPieceColor
17713       }
17714       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
17715                 Col2Text(4),   // highlightSquareColor
17716                 Col2Text(5) ); // premoveHighlightColor
17717         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
17718         if(insert != q) insert[-1] = NULLCHAR;
17719         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
17720         if(q)   free(q);
17721     }
17722     ActivateTheme(FALSE);
17723 }