4b09ab80bf1e31ebc3e035370408e69e2473e336
[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 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
228 #ifdef WIN32
229        extern void ConsoleCreate();
230 #endif
231
232 ChessProgramState *WhitePlayer();
233 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
234 int VerifyDisplayMode P(());
235
236 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
237 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
238 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
239 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
240 void ics_update_width P((int new_width));
241 extern char installDir[MSG_SIZ];
242 VariantClass startVariant; /* [HGM] nicks: initial variant */
243 Boolean abortMatch;
244
245 extern int tinyLayout, smallLayout;
246 ChessProgramStats programStats;
247 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
248 int endPV = -1;
249 static int exiting = 0; /* [HGM] moved to top */
250 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
251 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
252 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
253 int partnerHighlight[2];
254 Boolean partnerBoardValid = 0;
255 char partnerStatus[MSG_SIZ];
256 Boolean partnerUp;
257 Boolean originalFlip;
258 Boolean twoBoards = 0;
259 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
260 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
261 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
262 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
263 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
264 int opponentKibitzes;
265 int lastSavedGame; /* [HGM] save: ID of game */
266 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
267 extern int chatCount;
268 int chattingPartner;
269 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
270 char lastMsg[MSG_SIZ];
271 ChessSquare pieceSweep = EmptySquare;
272 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
273 int promoDefaultAltered;
274 int keepInfo = 0; /* [HGM] to protect PGN tags in auto-step game analysis */
275
276 /* States for ics_getting_history */
277 #define H_FALSE 0
278 #define H_REQUESTED 1
279 #define H_GOT_REQ_HEADER 2
280 #define H_GOT_UNREQ_HEADER 3
281 #define H_GETTING_MOVES 4
282 #define H_GOT_UNWANTED_HEADER 5
283
284 /* whosays values for GameEnds */
285 #define GE_ICS 0
286 #define GE_ENGINE 1
287 #define GE_PLAYER 2
288 #define GE_FILE 3
289 #define GE_XBOARD 4
290 #define GE_ENGINE1 5
291 #define GE_ENGINE2 6
292
293 /* Maximum number of games in a cmail message */
294 #define CMAIL_MAX_GAMES 20
295
296 /* Different types of move when calling RegisterMove */
297 #define CMAIL_MOVE   0
298 #define CMAIL_RESIGN 1
299 #define CMAIL_DRAW   2
300 #define CMAIL_ACCEPT 3
301
302 /* Different types of result to remember for each game */
303 #define CMAIL_NOT_RESULT 0
304 #define CMAIL_OLD_RESULT 1
305 #define CMAIL_NEW_RESULT 2
306
307 /* Telnet protocol constants */
308 #define TN_WILL 0373
309 #define TN_WONT 0374
310 #define TN_DO   0375
311 #define TN_DONT 0376
312 #define TN_IAC  0377
313 #define TN_ECHO 0001
314 #define TN_SGA  0003
315 #define TN_PORT 23
316
317 char*
318 safeStrCpy (char *dst, const char *src, size_t count)
319 { // [HGM] made safe
320   int i;
321   assert( dst != NULL );
322   assert( src != NULL );
323   assert( count > 0 );
324
325   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
326   if(  i == count && dst[count-1] != NULLCHAR)
327     {
328       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
329       if(appData.debugMode)
330       fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
331     }
332
333   return dst;
334 }
335
336 /* Some compiler can't cast u64 to double
337  * This function do the job for us:
338
339  * We use the highest bit for cast, this only
340  * works if the highest bit is not
341  * in use (This should not happen)
342  *
343  * We used this for all compiler
344  */
345 double
346 u64ToDouble (u64 value)
347 {
348   double r;
349   u64 tmp = value & u64Const(0x7fffffffffffffff);
350   r = (double)(s64)tmp;
351   if (value & u64Const(0x8000000000000000))
352        r +=  9.2233720368547758080e18; /* 2^63 */
353  return r;
354 }
355
356 /* Fake up flags for now, as we aren't keeping track of castling
357    availability yet. [HGM] Change of logic: the flag now only
358    indicates the type of castlings allowed by the rule of the game.
359    The actual rights themselves are maintained in the array
360    castlingRights, as part of the game history, and are not probed
361    by this function.
362  */
363 int
364 PosFlags (index)
365 {
366   int flags = F_ALL_CASTLE_OK;
367   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
368   switch (gameInfo.variant) {
369   case VariantSuicide:
370     flags &= ~F_ALL_CASTLE_OK;
371   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
372     flags |= F_IGNORE_CHECK;
373   case VariantLosers:
374     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
375     break;
376   case VariantAtomic:
377     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
378     break;
379   case VariantKriegspiel:
380     flags |= F_KRIEGSPIEL_CAPTURE;
381     break;
382   case VariantCapaRandom:
383   case VariantFischeRandom:
384     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
385   case VariantNoCastle:
386   case VariantShatranj:
387   case VariantCourier:
388   case VariantMakruk:
389   case VariantGrand:
390     flags &= ~F_ALL_CASTLE_OK;
391     break;
392   default:
393     break;
394   }
395   return flags;
396 }
397
398 FILE *gameFileFP, *debugFP, *serverFP;
399 char *currentDebugFile; // [HGM] debug split: to remember name
400
401 /*
402     [AS] Note: sometimes, the sscanf() function is used to parse the input
403     into a fixed-size buffer. Because of this, we must be prepared to
404     receive strings as long as the size of the input buffer, which is currently
405     set to 4K for Windows and 8K for the rest.
406     So, we must either allocate sufficiently large buffers here, or
407     reduce the size of the input buffer in the input reading part.
408 */
409
410 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
411 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
412 char thinkOutput1[MSG_SIZ*10];
413
414 ChessProgramState first, second, pairing;
415
416 /* premove variables */
417 int premoveToX = 0;
418 int premoveToY = 0;
419 int premoveFromX = 0;
420 int premoveFromY = 0;
421 int premovePromoChar = 0;
422 int gotPremove = 0;
423 Boolean alarmSounded;
424 /* end premove variables */
425
426 char *ics_prefix = "$";
427 enum ICS_TYPE ics_type = ICS_GENERIC;
428
429 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
430 int pauseExamForwardMostMove = 0;
431 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
432 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
433 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
434 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
435 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
436 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
437 int whiteFlag = FALSE, blackFlag = FALSE;
438 int userOfferedDraw = FALSE;
439 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
440 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
441 int cmailMoveType[CMAIL_MAX_GAMES];
442 long ics_clock_paused = 0;
443 ProcRef icsPR = NoProc, cmailPR = NoProc;
444 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
445 GameMode gameMode = BeginningOfGame;
446 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
447 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
448 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
449 int hiddenThinkOutputState = 0; /* [AS] */
450 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
451 int adjudicateLossPlies = 6;
452 char white_holding[64], black_holding[64];
453 TimeMark lastNodeCountTime;
454 long lastNodeCount=0;
455 int shiftKey, controlKey; // [HGM] set by mouse handler
456
457 int have_sent_ICS_logon = 0;
458 int movesPerSession;
459 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
460 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack, activePartnerTime;
461 Boolean adjustedClock;
462 long timeControl_2; /* [AS] Allow separate time controls */
463 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC, activePartner; /* [HGM] secondary TC: merge of MPS, TC and inc */
464 long timeRemaining[2][MAX_MOVES];
465 int matchGame = 0, nextGame = 0, roundNr = 0;
466 Boolean waitingForGame = FALSE;
467 TimeMark programStartTime, pauseStart;
468 char ics_handle[MSG_SIZ];
469 int have_set_title = 0;
470
471 /* animateTraining preserves the state of appData.animate
472  * when Training mode is activated. This allows the
473  * response to be animated when appData.animate == TRUE and
474  * appData.animateDragging == TRUE.
475  */
476 Boolean animateTraining;
477
478 GameInfo gameInfo;
479
480 AppData appData;
481
482 Board boards[MAX_MOVES];
483 /* [HGM] Following 7 needed for accurate legality tests: */
484 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
485 signed char  initialRights[BOARD_FILES];
486 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
487 int   initialRulePlies, FENrulePlies;
488 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
489 int loadFlag = 0;
490 Boolean shuffleOpenings;
491 int mute; // mute all sounds
492
493 // [HGM] vari: next 12 to save and restore variations
494 #define MAX_VARIATIONS 10
495 int framePtr = MAX_MOVES-1; // points to free stack entry
496 int storedGames = 0;
497 int savedFirst[MAX_VARIATIONS];
498 int savedLast[MAX_VARIATIONS];
499 int savedFramePtr[MAX_VARIATIONS];
500 char *savedDetails[MAX_VARIATIONS];
501 ChessMove savedResult[MAX_VARIATIONS];
502
503 void PushTail P((int firstMove, int lastMove));
504 Boolean PopTail P((Boolean annotate));
505 void PushInner P((int firstMove, int lastMove));
506 void PopInner P((Boolean annotate));
507 void CleanupTail P((void));
508
509 ChessSquare  FIDEArray[2][BOARD_FILES] = {
510     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
511         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
512     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
513         BlackKing, BlackBishop, BlackKnight, BlackRook }
514 };
515
516 ChessSquare twoKingsArray[2][BOARD_FILES] = {
517     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
518         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
519     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
520         BlackKing, BlackKing, BlackKnight, BlackRook }
521 };
522
523 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
524     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
525         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
526     { BlackRook, BlackMan, BlackBishop, BlackQueen,
527         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
528 };
529
530 ChessSquare SpartanArray[2][BOARD_FILES] = {
531     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
532         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
533     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
534         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
535 };
536
537 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
538     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
539         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
540     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
541         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
542 };
543
544 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
545     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
546         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
547     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
548         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
549 };
550
551 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
552     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
553         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
554     { BlackRook, BlackKnight, BlackMan, BlackFerz,
555         BlackKing, BlackMan, BlackKnight, BlackRook }
556 };
557
558
559 #if (BOARD_FILES>=10)
560 ChessSquare ShogiArray[2][BOARD_FILES] = {
561     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
562         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
563     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
564         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
565 };
566
567 ChessSquare XiangqiArray[2][BOARD_FILES] = {
568     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
569         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
570     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
571         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
572 };
573
574 ChessSquare CapablancaArray[2][BOARD_FILES] = {
575     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
576         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
577     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
578         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
579 };
580
581 ChessSquare GreatArray[2][BOARD_FILES] = {
582     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
583         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
584     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
585         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
586 };
587
588 ChessSquare JanusArray[2][BOARD_FILES] = {
589     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
590         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
591     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
592         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
593 };
594
595 ChessSquare GrandArray[2][BOARD_FILES] = {
596     { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
597         WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
598     { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
599         BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
600 };
601
602 #ifdef GOTHIC
603 ChessSquare GothicArray[2][BOARD_FILES] = {
604     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
605         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
606     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
607         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
608 };
609 #else // !GOTHIC
610 #define GothicArray CapablancaArray
611 #endif // !GOTHIC
612
613 #ifdef FALCON
614 ChessSquare FalconArray[2][BOARD_FILES] = {
615     { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
616         WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
617     { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
618         BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
619 };
620 #else // !FALCON
621 #define FalconArray CapablancaArray
622 #endif // !FALCON
623
624 #else // !(BOARD_FILES>=10)
625 #define XiangqiPosition FIDEArray
626 #define CapablancaArray FIDEArray
627 #define GothicArray FIDEArray
628 #define GreatArray FIDEArray
629 #endif // !(BOARD_FILES>=10)
630
631 #if (BOARD_FILES>=12)
632 ChessSquare CourierArray[2][BOARD_FILES] = {
633     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
634         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
635     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
636         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
637 };
638 #else // !(BOARD_FILES>=12)
639 #define CourierArray CapablancaArray
640 #endif // !(BOARD_FILES>=12)
641
642
643 Board initialPosition;
644
645
646 /* Convert str to a rating. Checks for special cases of "----",
647
648    "++++", etc. Also strips ()'s */
649 int
650 string_to_rating (char *str)
651 {
652   while(*str && !isdigit(*str)) ++str;
653   if (!*str)
654     return 0;   /* One of the special "no rating" cases */
655   else
656     return atoi(str);
657 }
658
659 void
660 ClearProgramStats ()
661 {
662     /* Init programStats */
663     programStats.movelist[0] = 0;
664     programStats.depth = 0;
665     programStats.nr_moves = 0;
666     programStats.moves_left = 0;
667     programStats.nodes = 0;
668     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
669     programStats.score = 0;
670     programStats.got_only_move = 0;
671     programStats.got_fail = 0;
672     programStats.line_is_book = 0;
673 }
674
675 void
676 CommonEngineInit ()
677 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
678     if (appData.firstPlaysBlack) {
679         first.twoMachinesColor = "black\n";
680         second.twoMachinesColor = "white\n";
681     } else {
682         first.twoMachinesColor = "white\n";
683         second.twoMachinesColor = "black\n";
684     }
685
686     first.other = &second;
687     second.other = &first;
688
689     { float norm = 1;
690         if(appData.timeOddsMode) {
691             norm = appData.timeOdds[0];
692             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
693         }
694         first.timeOdds  = appData.timeOdds[0]/norm;
695         second.timeOdds = appData.timeOdds[1]/norm;
696     }
697
698     if(programVersion) free(programVersion);
699     if (appData.noChessProgram) {
700         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
701         sprintf(programVersion, "%s", PACKAGE_STRING);
702     } else {
703       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
704       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
705       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
706     }
707 }
708
709 void
710 UnloadEngine (ChessProgramState *cps)
711 {
712         /* Kill off first chess program */
713         if (cps->isr != NULL)
714           RemoveInputSource(cps->isr);
715         cps->isr = NULL;
716
717         if (cps->pr != NoProc) {
718             ExitAnalyzeMode();
719             DoSleep( appData.delayBeforeQuit );
720             SendToProgram("quit\n", cps);
721             DoSleep( appData.delayAfterQuit );
722             DestroyChildProcess(cps->pr, cps->useSigterm);
723         }
724         cps->pr = NoProc;
725         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
726 }
727
728 void
729 ClearOptions (ChessProgramState *cps)
730 {
731     int i;
732     cps->nrOptions = cps->comboCnt = 0;
733     for(i=0; i<MAX_OPTIONS; i++) {
734         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
735         cps->option[i].textValue = 0;
736     }
737 }
738
739 char *engineNames[] = {
740   /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
741      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
742 N_("first"),
743   /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
744      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
745 N_("second")
746 };
747
748 void
749 InitEngine (ChessProgramState *cps, int n)
750 {   // [HGM] all engine initialiation put in a function that does one engine
751
752     ClearOptions(cps);
753
754     cps->which = engineNames[n];
755     cps->maybeThinking = FALSE;
756     cps->pr = NoProc;
757     cps->isr = NULL;
758     cps->sendTime = 2;
759     cps->sendDrawOffers = 1;
760
761     cps->program = appData.chessProgram[n];
762     cps->host = appData.host[n];
763     cps->dir = appData.directory[n];
764     cps->initString = appData.engInitString[n];
765     cps->computerString = appData.computerString[n];
766     cps->useSigint  = TRUE;
767     cps->useSigterm = TRUE;
768     cps->reuse = appData.reuse[n];
769     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
770     cps->useSetboard = FALSE;
771     cps->useSAN = FALSE;
772     cps->usePing = FALSE;
773     cps->lastPing = 0;
774     cps->lastPong = 0;
775     cps->usePlayother = FALSE;
776     cps->useColors = TRUE;
777     cps->useUsermove = FALSE;
778     cps->sendICS = FALSE;
779     cps->sendName = appData.icsActive;
780     cps->sdKludge = FALSE;
781     cps->stKludge = FALSE;
782     TidyProgramName(cps->program, cps->host, cps->tidy);
783     cps->matchWins = 0;
784     safeStrCpy(cps->variants, appData.variant, MSG_SIZ);
785     cps->analysisSupport = 2; /* detect */
786     cps->analyzing = FALSE;
787     cps->initDone = FALSE;
788
789     /* New features added by Tord: */
790     cps->useFEN960 = FALSE;
791     cps->useOOCastle = TRUE;
792     /* End of new features added by Tord. */
793     cps->fenOverride  = appData.fenOverride[n];
794
795     /* [HGM] time odds: set factor for each machine */
796     cps->timeOdds  = appData.timeOdds[n];
797
798     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
799     cps->accumulateTC = appData.accumulateTC[n];
800     cps->maxNrOfSessions = 1;
801
802     /* [HGM] debug */
803     cps->debug = FALSE;
804
805     cps->supportsNPS = UNKNOWN;
806     cps->memSize = FALSE;
807     cps->maxCores = FALSE;
808     cps->egtFormats[0] = NULLCHAR;
809
810     /* [HGM] options */
811     cps->optionSettings  = appData.engOptions[n];
812
813     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
814     cps->isUCI = appData.isUCI[n]; /* [AS] */
815     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
816
817     if (appData.protocolVersion[n] > PROTOVER
818         || appData.protocolVersion[n] < 1)
819       {
820         char buf[MSG_SIZ];
821         int len;
822
823         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
824                        appData.protocolVersion[n]);
825         if( (len >= MSG_SIZ) && appData.debugMode )
826           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
827
828         DisplayFatalError(buf, 0, 2);
829       }
830     else
831       {
832         cps->protocolVersion = appData.protocolVersion[n];
833       }
834
835     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
836     ParseFeatures(appData.featureDefaults, cps);
837 }
838
839 ChessProgramState *savCps;
840
841 void
842 LoadEngine ()
843 {
844     int i;
845     if(WaitForEngine(savCps, LoadEngine)) return;
846     CommonEngineInit(); // recalculate time odds
847     if(gameInfo.variant != StringToVariant(appData.variant)) {
848         // we changed variant when loading the engine; this forces us to reset
849         Reset(TRUE, savCps != &first);
850         EditGameEvent(); // for consistency with other path, as Reset changes mode
851     }
852     InitChessProgram(savCps, FALSE);
853     SendToProgram("force\n", savCps);
854     DisplayMessage("", "");
855     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
856     for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
857     ThawUI();
858     SetGNUMode();
859 }
860
861 void
862 ReplaceEngine (ChessProgramState *cps, int n)
863 {
864     EditGameEvent();
865     UnloadEngine(cps);
866     appData.noChessProgram = FALSE;
867     appData.clockMode = TRUE;
868     InitEngine(cps, n);
869     UpdateLogos(TRUE);
870     if(n) return; // only startup first engine immediately; second can wait
871     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
872     LoadEngine();
873 }
874
875 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
876 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
877
878 static char resetOptions[] = 
879         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
880         "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
881         "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 "
882         "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
883
884 void
885 FloatToFront(char **list, char *engineLine)
886 {
887     char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
888     int i=0;
889     if(appData.recentEngines <= 0) return;
890     TidyProgramName(engineLine, "localhost", tidy+1);
891     tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
892     strncpy(buf+1, *list, MSG_SIZ-50);
893     if(p = strstr(buf, tidy)) { // tidy name appears in list
894         q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
895         while(*p++ = *++q); // squeeze out
896     }
897     strcat(tidy, buf+1); // put list behind tidy name
898     p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
899     if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
900     ASSIGN(*list, tidy+1);
901 }
902
903 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
904
905 void
906 Load (ChessProgramState *cps, int i)
907 {
908     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ];
909     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
910         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
911         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
912         ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
913         FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
914         appData.firstProtocolVersion = PROTOVER;
915         ParseArgsFromString(buf);
916         SwapEngines(i);
917         ReplaceEngine(cps, i);
918         FloatToFront(&appData.recentEngineList, engineLine);
919         return;
920     }
921     p = engineName;
922     while(q = strchr(p, SLASH)) p = q+1;
923     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
924     if(engineDir[0] != NULLCHAR) {
925         ASSIGN(appData.directory[i], engineDir); p = engineName;
926     } else if(p != engineName) { // derive directory from engine path, when not given
927         p[-1] = 0;
928         ASSIGN(appData.directory[i], engineName);
929         p[-1] = SLASH;
930         if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
931     } else { ASSIGN(appData.directory[i], "."); }
932     if(params[0]) {
933         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
934         snprintf(command, MSG_SIZ, "%s %s", p, params);
935         p = command;
936     }
937     ASSIGN(appData.chessProgram[i], p);
938     appData.isUCI[i] = isUCI;
939     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
940     appData.hasOwnBookUCI[i] = hasBook;
941     if(!nickName[0]) useNick = FALSE;
942     if(useNick) ASSIGN(appData.pgnName[i], nickName);
943     if(addToList) {
944         int len;
945         char quote;
946         q = firstChessProgramNames;
947         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
948         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
949         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
950                         quote, p, quote, appData.directory[i], 
951                         useNick ? " -fn \"" : "",
952                         useNick ? nickName : "",
953                         useNick ? "\"" : "",
954                         v1 ? " -firstProtocolVersion 1" : "",
955                         hasBook ? "" : " -fNoOwnBookUCI",
956                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
957                         storeVariant ? " -variant " : "",
958                         storeVariant ? VariantName(gameInfo.variant) : "");
959         if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
960         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
961         if(insert != q) insert[-1] = NULLCHAR;
962         snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
963         if(q)   free(q);
964         FloatToFront(&appData.recentEngineList, buf);
965     }
966     ReplaceEngine(cps, i);
967 }
968
969 void
970 InitTimeControls ()
971 {
972     int matched, min, sec;
973     /*
974      * Parse timeControl resource
975      */
976     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
977                           appData.movesPerSession)) {
978         char buf[MSG_SIZ];
979         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
980         DisplayFatalError(buf, 0, 2);
981     }
982
983     /*
984      * Parse searchTime resource
985      */
986     if (*appData.searchTime != NULLCHAR) {
987         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
988         if (matched == 1) {
989             searchTime = min * 60;
990         } else if (matched == 2) {
991             searchTime = min * 60 + sec;
992         } else {
993             char buf[MSG_SIZ];
994             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
995             DisplayFatalError(buf, 0, 2);
996         }
997     }
998 }
999
1000 void
1001 InitBackEnd1 ()
1002 {
1003
1004     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1005     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1006
1007     GetTimeMark(&programStartTime);
1008     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1009     appData.seedBase = random() + (random()<<15);
1010     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1011
1012     ClearProgramStats();
1013     programStats.ok_to_send = 1;
1014     programStats.seen_stat = 0;
1015
1016     /*
1017      * Initialize game list
1018      */
1019     ListNew(&gameList);
1020
1021
1022     /*
1023      * Internet chess server status
1024      */
1025     if (appData.icsActive) {
1026         appData.matchMode = FALSE;
1027         appData.matchGames = 0;
1028 #if ZIPPY
1029         appData.noChessProgram = !appData.zippyPlay;
1030 #else
1031         appData.zippyPlay = FALSE;
1032         appData.zippyTalk = FALSE;
1033         appData.noChessProgram = TRUE;
1034 #endif
1035         if (*appData.icsHelper != NULLCHAR) {
1036             appData.useTelnet = TRUE;
1037             appData.telnetProgram = appData.icsHelper;
1038         }
1039     } else {
1040         appData.zippyTalk = appData.zippyPlay = FALSE;
1041     }
1042
1043     /* [AS] Initialize pv info list [HGM] and game state */
1044     {
1045         int i, j;
1046
1047         for( i=0; i<=framePtr; i++ ) {
1048             pvInfoList[i].depth = -1;
1049             boards[i][EP_STATUS] = EP_NONE;
1050             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1051         }
1052     }
1053
1054     InitTimeControls();
1055
1056     /* [AS] Adjudication threshold */
1057     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1058
1059     InitEngine(&first, 0);
1060     InitEngine(&second, 1);
1061     CommonEngineInit();
1062
1063     pairing.which = "pairing"; // pairing engine
1064     pairing.pr = NoProc;
1065     pairing.isr = NULL;
1066     pairing.program = appData.pairingEngine;
1067     pairing.host = "localhost";
1068     pairing.dir = ".";
1069
1070     if (appData.icsActive) {
1071         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1072     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1073         appData.clockMode = FALSE;
1074         first.sendTime = second.sendTime = 0;
1075     }
1076
1077 #if ZIPPY
1078     /* Override some settings from environment variables, for backward
1079        compatibility.  Unfortunately it's not feasible to have the env
1080        vars just set defaults, at least in xboard.  Ugh.
1081     */
1082     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1083       ZippyInit();
1084     }
1085 #endif
1086
1087     if (!appData.icsActive) {
1088       char buf[MSG_SIZ];
1089       int len;
1090
1091       /* Check for variants that are supported only in ICS mode,
1092          or not at all.  Some that are accepted here nevertheless
1093          have bugs; see comments below.
1094       */
1095       VariantClass variant = StringToVariant(appData.variant);
1096       switch (variant) {
1097       case VariantBughouse:     /* need four players and two boards */
1098       case VariantKriegspiel:   /* need to hide pieces and move details */
1099         /* case VariantFischeRandom: (Fabien: moved below) */
1100         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1101         if( (len >= MSG_SIZ) && appData.debugMode )
1102           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1103
1104         DisplayFatalError(buf, 0, 2);
1105         return;
1106
1107       case VariantUnknown:
1108       case VariantLoadable:
1109       case Variant29:
1110       case Variant30:
1111       case Variant31:
1112       case Variant32:
1113       case Variant33:
1114       case Variant34:
1115       case Variant35:
1116       case Variant36:
1117       default:
1118         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1119         if( (len >= MSG_SIZ) && appData.debugMode )
1120           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1121
1122         DisplayFatalError(buf, 0, 2);
1123         return;
1124
1125       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1126       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1127       case VariantGothic:     /* [HGM] should work */
1128       case VariantCapablanca: /* [HGM] should work */
1129       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1130       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1131       case VariantKnightmate: /* [HGM] should work */
1132       case VariantCylinder:   /* [HGM] untested */
1133       case VariantFalcon:     /* [HGM] untested */
1134       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1135                                  offboard interposition not understood */
1136       case VariantNormal:     /* definitely works! */
1137       case VariantWildCastle: /* pieces not automatically shuffled */
1138       case VariantNoCastle:   /* pieces not automatically shuffled */
1139       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1140       case VariantLosers:     /* should work except for win condition,
1141                                  and doesn't know captures are mandatory */
1142       case VariantSuicide:    /* should work except for win condition,
1143                                  and doesn't know captures are mandatory */
1144       case VariantGiveaway:   /* should work except for win condition,
1145                                  and doesn't know captures are mandatory */
1146       case VariantTwoKings:   /* should work */
1147       case VariantAtomic:     /* should work except for win condition */
1148       case Variant3Check:     /* should work except for win condition */
1149       case VariantShatranj:   /* should work except for all win conditions */
1150       case VariantMakruk:     /* should work except for draw countdown */
1151       case VariantBerolina:   /* might work if TestLegality is off */
1152       case VariantCapaRandom: /* should work */
1153       case VariantJanus:      /* should work */
1154       case VariantSuper:      /* experimental */
1155       case VariantGreat:      /* experimental, requires legality testing to be off */
1156       case VariantSChess:     /* S-Chess, should work */
1157       case VariantGrand:      /* should work */
1158       case VariantSpartan:    /* should work */
1159         break;
1160       }
1161     }
1162
1163 }
1164
1165 int
1166 NextIntegerFromString (char ** str, long * value)
1167 {
1168     int result = -1;
1169     char * s = *str;
1170
1171     while( *s == ' ' || *s == '\t' ) {
1172         s++;
1173     }
1174
1175     *value = 0;
1176
1177     if( *s >= '0' && *s <= '9' ) {
1178         while( *s >= '0' && *s <= '9' ) {
1179             *value = *value * 10 + (*s - '0');
1180             s++;
1181         }
1182
1183         result = 0;
1184     }
1185
1186     *str = s;
1187
1188     return result;
1189 }
1190
1191 int
1192 NextTimeControlFromString (char ** str, long * value)
1193 {
1194     long temp;
1195     int result = NextIntegerFromString( str, &temp );
1196
1197     if( result == 0 ) {
1198         *value = temp * 60; /* Minutes */
1199         if( **str == ':' ) {
1200             (*str)++;
1201             result = NextIntegerFromString( str, &temp );
1202             *value += temp; /* Seconds */
1203         }
1204     }
1205
1206     return result;
1207 }
1208
1209 int
1210 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1211 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1212     int result = -1, type = 0; long temp, temp2;
1213
1214     if(**str != ':') return -1; // old params remain in force!
1215     (*str)++;
1216     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1217     if( NextIntegerFromString( str, &temp ) ) return -1;
1218     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1219
1220     if(**str != '/') {
1221         /* time only: incremental or sudden-death time control */
1222         if(**str == '+') { /* increment follows; read it */
1223             (*str)++;
1224             if(**str == '!') type = *(*str)++; // Bronstein TC
1225             if(result = NextIntegerFromString( str, &temp2)) return -1;
1226             *inc = temp2 * 1000;
1227             if(**str == '.') { // read fraction of increment
1228                 char *start = ++(*str);
1229                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1230                 temp2 *= 1000;
1231                 while(start++ < *str) temp2 /= 10;
1232                 *inc += temp2;
1233             }
1234         } else *inc = 0;
1235         *moves = 0; *tc = temp * 1000; *incType = type;
1236         return 0;
1237     }
1238
1239     (*str)++; /* classical time control */
1240     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1241
1242     if(result == 0) {
1243         *moves = temp;
1244         *tc    = temp2 * 1000;
1245         *inc   = 0;
1246         *incType = type;
1247     }
1248     return result;
1249 }
1250
1251 int
1252 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1253 {   /* [HGM] get time to add from the multi-session time-control string */
1254     int incType, moves=1; /* kludge to force reading of first session */
1255     long time, increment;
1256     char *s = tcString;
1257
1258     if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1259     do {
1260         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1261         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1262         if(movenr == -1) return time;    /* last move before new session     */
1263         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1264         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1265         if(!moves) return increment;     /* current session is incremental   */
1266         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1267     } while(movenr >= -1);               /* try again for next session       */
1268
1269     return 0; // no new time quota on this move
1270 }
1271
1272 int
1273 ParseTimeControl (char *tc, float ti, int mps)
1274 {
1275   long tc1;
1276   long tc2;
1277   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1278   int min, sec=0;
1279
1280   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1281   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1282       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1283   if(ti > 0) {
1284
1285     if(mps)
1286       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1287     else 
1288       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1289   } else {
1290     if(mps)
1291       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1292     else 
1293       snprintf(buf, MSG_SIZ, ":%s", mytc);
1294   }
1295   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1296   
1297   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1298     return FALSE;
1299   }
1300
1301   if( *tc == '/' ) {
1302     /* Parse second time control */
1303     tc++;
1304
1305     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1306       return FALSE;
1307     }
1308
1309     if( tc2 == 0 ) {
1310       return FALSE;
1311     }
1312
1313     timeControl_2 = tc2 * 1000;
1314   }
1315   else {
1316     timeControl_2 = 0;
1317   }
1318
1319   if( tc1 == 0 ) {
1320     return FALSE;
1321   }
1322
1323   timeControl = tc1 * 1000;
1324
1325   if (ti >= 0) {
1326     timeIncrement = ti * 1000;  /* convert to ms */
1327     movesPerSession = 0;
1328   } else {
1329     timeIncrement = 0;
1330     movesPerSession = mps;
1331   }
1332   return TRUE;
1333 }
1334
1335 void
1336 InitBackEnd2 ()
1337 {
1338     if (appData.debugMode) {
1339         fprintf(debugFP, "%s\n", programVersion);
1340     }
1341     ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1342
1343     set_cont_sequence(appData.wrapContSeq);
1344     if (appData.matchGames > 0) {
1345         appData.matchMode = TRUE;
1346     } else if (appData.matchMode) {
1347         appData.matchGames = 1;
1348     }
1349     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1350         appData.matchGames = appData.sameColorGames;
1351     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1352         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1353         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1354     }
1355     Reset(TRUE, FALSE);
1356     if (appData.noChessProgram || first.protocolVersion == 1) {
1357       InitBackEnd3();
1358     } else {
1359       /* kludge: allow timeout for initial "feature" commands */
1360       FreezeUI();
1361       DisplayMessage("", _("Starting chess program"));
1362       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1363     }
1364 }
1365
1366 int
1367 CalculateIndex (int index, int gameNr)
1368 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1369     int res;
1370     if(index > 0) return index; // fixed nmber
1371     if(index == 0) return 1;
1372     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1373     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1374     return res;
1375 }
1376
1377 int
1378 LoadGameOrPosition (int gameNr)
1379 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1380     if (*appData.loadGameFile != NULLCHAR) {
1381         if (!LoadGameFromFile(appData.loadGameFile,
1382                 CalculateIndex(appData.loadGameIndex, gameNr),
1383                               appData.loadGameFile, FALSE)) {
1384             DisplayFatalError(_("Bad game file"), 0, 1);
1385             return 0;
1386         }
1387     } else if (*appData.loadPositionFile != NULLCHAR) {
1388         if (!LoadPositionFromFile(appData.loadPositionFile,
1389                 CalculateIndex(appData.loadPositionIndex, gameNr),
1390                                   appData.loadPositionFile)) {
1391             DisplayFatalError(_("Bad position file"), 0, 1);
1392             return 0;
1393         }
1394     }
1395     return 1;
1396 }
1397
1398 void
1399 ReserveGame (int gameNr, char resChar)
1400 {
1401     FILE *tf = fopen(appData.tourneyFile, "r+");
1402     char *p, *q, c, buf[MSG_SIZ];
1403     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1404     safeStrCpy(buf, lastMsg, MSG_SIZ);
1405     DisplayMessage(_("Pick new game"), "");
1406     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1407     ParseArgsFromFile(tf);
1408     p = q = appData.results;
1409     if(appData.debugMode) {
1410       char *r = appData.participants;
1411       fprintf(debugFP, "results = '%s'\n", p);
1412       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1413       fprintf(debugFP, "\n");
1414     }
1415     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1416     nextGame = q - p;
1417     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1418     safeStrCpy(q, p, strlen(p) + 2);
1419     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1420     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1421     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1422         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1423         q[nextGame] = '*';
1424     }
1425     fseek(tf, -(strlen(p)+4), SEEK_END);
1426     c = fgetc(tf);
1427     if(c != '"') // depending on DOS or Unix line endings we can be one off
1428          fseek(tf, -(strlen(p)+2), SEEK_END);
1429     else fseek(tf, -(strlen(p)+3), SEEK_END);
1430     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1431     DisplayMessage(buf, "");
1432     free(p); appData.results = q;
1433     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1434        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1435       int round = appData.defaultMatchGames * appData.tourneyType;
1436       if(gameNr < 0 || appData.tourneyType < 1 ||  // gauntlet engine can always stay loaded as first engine
1437          appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1438         UnloadEngine(&first);  // next game belongs to other pairing;
1439         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1440     }
1441     if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1442 }
1443
1444 void
1445 MatchEvent (int mode)
1446 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1447         int dummy;
1448         if(matchMode) { // already in match mode: switch it off
1449             abortMatch = TRUE;
1450             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1451             return;
1452         }
1453 //      if(gameMode != BeginningOfGame) {
1454 //          DisplayError(_("You can only start a match from the initial position."), 0);
1455 //          return;
1456 //      }
1457         abortMatch = FALSE;
1458         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1459         /* Set up machine vs. machine match */
1460         nextGame = 0;
1461         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1462         if(appData.tourneyFile[0]) {
1463             ReserveGame(-1, 0);
1464             if(nextGame > appData.matchGames) {
1465                 char buf[MSG_SIZ];
1466                 if(strchr(appData.results, '*') == NULL) {
1467                     FILE *f;
1468                     appData.tourneyCycles++;
1469                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1470                         fclose(f);
1471                         NextTourneyGame(-1, &dummy);
1472                         ReserveGame(-1, 0);
1473                         if(nextGame <= appData.matchGames) {
1474                             DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1475                             matchMode = mode;
1476                             ScheduleDelayedEvent(NextMatchGame, 10000);
1477                             return;
1478                         }
1479                     }
1480                 }
1481                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1482                 DisplayError(buf, 0);
1483                 appData.tourneyFile[0] = 0;
1484                 return;
1485             }
1486         } else
1487         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1488             DisplayFatalError(_("Can't have a match with no chess programs"),
1489                               0, 2);
1490             return;
1491         }
1492         matchMode = mode;
1493         matchGame = roundNr = 1;
1494         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1495         NextMatchGame();
1496 }
1497
1498 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1499
1500 void
1501 InitBackEnd3 P((void))
1502 {
1503     GameMode initialMode;
1504     char buf[MSG_SIZ];
1505     int err, len;
1506
1507     InitChessProgram(&first, startedFromSetupPosition);
1508
1509     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1510         free(programVersion);
1511         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1512         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1513         FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1514     }
1515
1516     if (appData.icsActive) {
1517 #ifdef WIN32
1518         /* [DM] Make a console window if needed [HGM] merged ifs */
1519         ConsoleCreate();
1520 #endif
1521         err = establish();
1522         if (err != 0)
1523           {
1524             if (*appData.icsCommPort != NULLCHAR)
1525               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1526                              appData.icsCommPort);
1527             else
1528               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1529                         appData.icsHost, appData.icsPort);
1530
1531             if( (len >= MSG_SIZ) && appData.debugMode )
1532               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1533
1534             DisplayFatalError(buf, err, 1);
1535             return;
1536         }
1537         SetICSMode();
1538         telnetISR =
1539           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1540         fromUserISR =
1541           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1542         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1543             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1544     } else if (appData.noChessProgram) {
1545         SetNCPMode();
1546     } else {
1547         SetGNUMode();
1548     }
1549
1550     if (*appData.cmailGameName != NULLCHAR) {
1551         SetCmailMode();
1552         OpenLoopback(&cmailPR);
1553         cmailISR =
1554           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1555     }
1556
1557     ThawUI();
1558     DisplayMessage("", "");
1559     if (StrCaseCmp(appData.initialMode, "") == 0) {
1560       initialMode = BeginningOfGame;
1561       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1562         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1563         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1564         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1565         ModeHighlight();
1566       }
1567     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1568       initialMode = TwoMachinesPlay;
1569     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1570       initialMode = AnalyzeFile;
1571     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1572       initialMode = AnalyzeMode;
1573     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1574       initialMode = MachinePlaysWhite;
1575     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1576       initialMode = MachinePlaysBlack;
1577     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1578       initialMode = EditGame;
1579     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1580       initialMode = EditPosition;
1581     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1582       initialMode = Training;
1583     } else {
1584       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1585       if( (len >= MSG_SIZ) && appData.debugMode )
1586         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1587
1588       DisplayFatalError(buf, 0, 2);
1589       return;
1590     }
1591
1592     if (appData.matchMode) {
1593         if(appData.tourneyFile[0]) { // start tourney from command line
1594             FILE *f;
1595             if(f = fopen(appData.tourneyFile, "r")) {
1596                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1597                 fclose(f);
1598                 appData.clockMode = TRUE;
1599                 SetGNUMode();
1600             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1601         }
1602         MatchEvent(TRUE);
1603     } else if (*appData.cmailGameName != NULLCHAR) {
1604         /* Set up cmail mode */
1605         ReloadCmailMsgEvent(TRUE);
1606     } else {
1607         /* Set up other modes */
1608         if (initialMode == AnalyzeFile) {
1609           if (*appData.loadGameFile == NULLCHAR) {
1610             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1611             return;
1612           }
1613         }
1614         if (*appData.loadGameFile != NULLCHAR) {
1615             (void) LoadGameFromFile(appData.loadGameFile,
1616                                     appData.loadGameIndex,
1617                                     appData.loadGameFile, TRUE);
1618         } else if (*appData.loadPositionFile != NULLCHAR) {
1619             (void) LoadPositionFromFile(appData.loadPositionFile,
1620                                         appData.loadPositionIndex,
1621                                         appData.loadPositionFile);
1622             /* [HGM] try to make self-starting even after FEN load */
1623             /* to allow automatic setup of fairy variants with wtm */
1624             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1625                 gameMode = BeginningOfGame;
1626                 setboardSpoiledMachineBlack = 1;
1627             }
1628             /* [HGM] loadPos: make that every new game uses the setup */
1629             /* from file as long as we do not switch variant          */
1630             if(!blackPlaysFirst) {
1631                 startedFromPositionFile = TRUE;
1632                 CopyBoard(filePosition, boards[0]);
1633             }
1634         }
1635         if (initialMode == AnalyzeMode) {
1636           if (appData.noChessProgram) {
1637             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1638             return;
1639           }
1640           if (appData.icsActive) {
1641             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1642             return;
1643           }
1644           AnalyzeModeEvent();
1645         } else if (initialMode == AnalyzeFile) {
1646           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1647           ShowThinkingEvent();
1648           AnalyzeFileEvent();
1649           AnalysisPeriodicEvent(1);
1650         } else if (initialMode == MachinePlaysWhite) {
1651           if (appData.noChessProgram) {
1652             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1653                               0, 2);
1654             return;
1655           }
1656           if (appData.icsActive) {
1657             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1658                               0, 2);
1659             return;
1660           }
1661           MachineWhiteEvent();
1662         } else if (initialMode == MachinePlaysBlack) {
1663           if (appData.noChessProgram) {
1664             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1665                               0, 2);
1666             return;
1667           }
1668           if (appData.icsActive) {
1669             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1670                               0, 2);
1671             return;
1672           }
1673           MachineBlackEvent();
1674         } else if (initialMode == TwoMachinesPlay) {
1675           if (appData.noChessProgram) {
1676             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1677                               0, 2);
1678             return;
1679           }
1680           if (appData.icsActive) {
1681             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1682                               0, 2);
1683             return;
1684           }
1685           TwoMachinesEvent();
1686         } else if (initialMode == EditGame) {
1687           EditGameEvent();
1688         } else if (initialMode == EditPosition) {
1689           EditPositionEvent();
1690         } else if (initialMode == Training) {
1691           if (*appData.loadGameFile == NULLCHAR) {
1692             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1693             return;
1694           }
1695           TrainingEvent();
1696         }
1697     }
1698 }
1699
1700 void
1701 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1702 {
1703     DisplayBook(current+1);
1704
1705     MoveHistorySet( movelist, first, last, current, pvInfoList );
1706
1707     EvalGraphSet( first, last, current, pvInfoList );
1708
1709     MakeEngineOutputTitle();
1710 }
1711
1712 /*
1713  * Establish will establish a contact to a remote host.port.
1714  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1715  *  used to talk to the host.
1716  * Returns 0 if okay, error code if not.
1717  */
1718 int
1719 establish ()
1720 {
1721     char buf[MSG_SIZ];
1722
1723     if (*appData.icsCommPort != NULLCHAR) {
1724         /* Talk to the host through a serial comm port */
1725         return OpenCommPort(appData.icsCommPort, &icsPR);
1726
1727     } else if (*appData.gateway != NULLCHAR) {
1728         if (*appData.remoteShell == NULLCHAR) {
1729             /* Use the rcmd protocol to run telnet program on a gateway host */
1730             snprintf(buf, sizeof(buf), "%s %s %s",
1731                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1732             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1733
1734         } else {
1735             /* Use the rsh program to run telnet program on a gateway host */
1736             if (*appData.remoteUser == NULLCHAR) {
1737                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1738                         appData.gateway, appData.telnetProgram,
1739                         appData.icsHost, appData.icsPort);
1740             } else {
1741                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1742                         appData.remoteShell, appData.gateway,
1743                         appData.remoteUser, appData.telnetProgram,
1744                         appData.icsHost, appData.icsPort);
1745             }
1746             return StartChildProcess(buf, "", &icsPR);
1747
1748         }
1749     } else if (appData.useTelnet) {
1750         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1751
1752     } else {
1753         /* TCP socket interface differs somewhat between
1754            Unix and NT; handle details in the front end.
1755            */
1756         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1757     }
1758 }
1759
1760 void
1761 EscapeExpand (char *p, char *q)
1762 {       // [HGM] initstring: routine to shape up string arguments
1763         while(*p++ = *q++) if(p[-1] == '\\')
1764             switch(*q++) {
1765                 case 'n': p[-1] = '\n'; break;
1766                 case 'r': p[-1] = '\r'; break;
1767                 case 't': p[-1] = '\t'; break;
1768                 case '\\': p[-1] = '\\'; break;
1769                 case 0: *p = 0; return;
1770                 default: p[-1] = q[-1]; break;
1771             }
1772 }
1773
1774 void
1775 show_bytes (FILE *fp, char *buf, int count)
1776 {
1777     while (count--) {
1778         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1779             fprintf(fp, "\\%03o", *buf & 0xff);
1780         } else {
1781             putc(*buf, fp);
1782         }
1783         buf++;
1784     }
1785     fflush(fp);
1786 }
1787
1788 /* Returns an errno value */
1789 int
1790 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1791 {
1792     char buf[8192], *p, *q, *buflim;
1793     int left, newcount, outcount;
1794
1795     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1796         *appData.gateway != NULLCHAR) {
1797         if (appData.debugMode) {
1798             fprintf(debugFP, ">ICS: ");
1799             show_bytes(debugFP, message, count);
1800             fprintf(debugFP, "\n");
1801         }
1802         return OutputToProcess(pr, message, count, outError);
1803     }
1804
1805     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1806     p = message;
1807     q = buf;
1808     left = count;
1809     newcount = 0;
1810     while (left) {
1811         if (q >= buflim) {
1812             if (appData.debugMode) {
1813                 fprintf(debugFP, ">ICS: ");
1814                 show_bytes(debugFP, buf, newcount);
1815                 fprintf(debugFP, "\n");
1816             }
1817             outcount = OutputToProcess(pr, buf, newcount, outError);
1818             if (outcount < newcount) return -1; /* to be sure */
1819             q = buf;
1820             newcount = 0;
1821         }
1822         if (*p == '\n') {
1823             *q++ = '\r';
1824             newcount++;
1825         } else if (((unsigned char) *p) == TN_IAC) {
1826             *q++ = (char) TN_IAC;
1827             newcount ++;
1828         }
1829         *q++ = *p++;
1830         newcount++;
1831         left--;
1832     }
1833     if (appData.debugMode) {
1834         fprintf(debugFP, ">ICS: ");
1835         show_bytes(debugFP, buf, newcount);
1836         fprintf(debugFP, "\n");
1837     }
1838     outcount = OutputToProcess(pr, buf, newcount, outError);
1839     if (outcount < newcount) return -1; /* to be sure */
1840     return count;
1841 }
1842
1843 void
1844 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1845 {
1846     int outError, outCount;
1847     static int gotEof = 0;
1848     static FILE *ini;
1849
1850     /* Pass data read from player on to ICS */
1851     if (count > 0) {
1852         gotEof = 0;
1853         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1854         if (outCount < count) {
1855             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1856         }
1857         if(have_sent_ICS_logon == 2) {
1858           if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1859             fprintf(ini, "%s", message);
1860             have_sent_ICS_logon = 3;
1861           } else
1862             have_sent_ICS_logon = 1;
1863         } else if(have_sent_ICS_logon == 3) {
1864             fprintf(ini, "%s", message);
1865             fclose(ini);
1866           have_sent_ICS_logon = 1;
1867         }
1868     } else if (count < 0) {
1869         RemoveInputSource(isr);
1870         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1871     } else if (gotEof++ > 0) {
1872         RemoveInputSource(isr);
1873         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1874     }
1875 }
1876
1877 void
1878 KeepAlive ()
1879 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1880     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1881     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1882     SendToICS("date\n");
1883     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1884 }
1885
1886 /* added routine for printf style output to ics */
1887 void
1888 ics_printf (char *format, ...)
1889 {
1890     char buffer[MSG_SIZ];
1891     va_list args;
1892
1893     va_start(args, format);
1894     vsnprintf(buffer, sizeof(buffer), format, args);
1895     buffer[sizeof(buffer)-1] = '\0';
1896     SendToICS(buffer);
1897     va_end(args);
1898 }
1899
1900 void
1901 SendToICS (char *s)
1902 {
1903     int count, outCount, outError;
1904
1905     if (icsPR == NoProc) return;
1906
1907     count = strlen(s);
1908     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1909     if (outCount < count) {
1910         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1911     }
1912 }
1913
1914 /* This is used for sending logon scripts to the ICS. Sending
1915    without a delay causes problems when using timestamp on ICC
1916    (at least on my machine). */
1917 void
1918 SendToICSDelayed (char *s, long msdelay)
1919 {
1920     int count, outCount, outError;
1921
1922     if (icsPR == NoProc) return;
1923
1924     count = strlen(s);
1925     if (appData.debugMode) {
1926         fprintf(debugFP, ">ICS: ");
1927         show_bytes(debugFP, s, count);
1928         fprintf(debugFP, "\n");
1929     }
1930     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1931                                       msdelay);
1932     if (outCount < count) {
1933         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1934     }
1935 }
1936
1937
1938 /* Remove all highlighting escape sequences in s
1939    Also deletes any suffix starting with '('
1940    */
1941 char *
1942 StripHighlightAndTitle (char *s)
1943 {
1944     static char retbuf[MSG_SIZ];
1945     char *p = retbuf;
1946
1947     while (*s != NULLCHAR) {
1948         while (*s == '\033') {
1949             while (*s != NULLCHAR && !isalpha(*s)) s++;
1950             if (*s != NULLCHAR) s++;
1951         }
1952         while (*s != NULLCHAR && *s != '\033') {
1953             if (*s == '(' || *s == '[') {
1954                 *p = NULLCHAR;
1955                 return retbuf;
1956             }
1957             *p++ = *s++;
1958         }
1959     }
1960     *p = NULLCHAR;
1961     return retbuf;
1962 }
1963
1964 /* Remove all highlighting escape sequences in s */
1965 char *
1966 StripHighlight (char *s)
1967 {
1968     static char retbuf[MSG_SIZ];
1969     char *p = retbuf;
1970
1971     while (*s != NULLCHAR) {
1972         while (*s == '\033') {
1973             while (*s != NULLCHAR && !isalpha(*s)) s++;
1974             if (*s != NULLCHAR) s++;
1975         }
1976         while (*s != NULLCHAR && *s != '\033') {
1977             *p++ = *s++;
1978         }
1979     }
1980     *p = NULLCHAR;
1981     return retbuf;
1982 }
1983
1984 char *variantNames[] = VARIANT_NAMES;
1985 char *
1986 VariantName (VariantClass v)
1987 {
1988     return variantNames[v];
1989 }
1990
1991
1992 /* Identify a variant from the strings the chess servers use or the
1993    PGN Variant tag names we use. */
1994 VariantClass
1995 StringToVariant (char *e)
1996 {
1997     char *p;
1998     int wnum = -1;
1999     VariantClass v = VariantNormal;
2000     int i, found = FALSE;
2001     char buf[MSG_SIZ];
2002     int len;
2003
2004     if (!e) return v;
2005
2006     /* [HGM] skip over optional board-size prefixes */
2007     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
2008         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
2009         while( *e++ != '_');
2010     }
2011
2012     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2013         v = VariantNormal;
2014         found = TRUE;
2015     } else
2016     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2017       if (StrCaseStr(e, variantNames[i])) {
2018         v = (VariantClass) i;
2019         found = TRUE;
2020         break;
2021       }
2022     }
2023
2024     if (!found) {
2025       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2026           || StrCaseStr(e, "wild/fr")
2027           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2028         v = VariantFischeRandom;
2029       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2030                  (i = 1, p = StrCaseStr(e, "w"))) {
2031         p += i;
2032         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2033         if (isdigit(*p)) {
2034           wnum = atoi(p);
2035         } else {
2036           wnum = -1;
2037         }
2038         switch (wnum) {
2039         case 0: /* FICS only, actually */
2040         case 1:
2041           /* Castling legal even if K starts on d-file */
2042           v = VariantWildCastle;
2043           break;
2044         case 2:
2045         case 3:
2046         case 4:
2047           /* Castling illegal even if K & R happen to start in
2048              normal positions. */
2049           v = VariantNoCastle;
2050           break;
2051         case 5:
2052         case 7:
2053         case 8:
2054         case 10:
2055         case 11:
2056         case 12:
2057         case 13:
2058         case 14:
2059         case 15:
2060         case 18:
2061         case 19:
2062           /* Castling legal iff K & R start in normal positions */
2063           v = VariantNormal;
2064           break;
2065         case 6:
2066         case 20:
2067         case 21:
2068           /* Special wilds for position setup; unclear what to do here */
2069           v = VariantLoadable;
2070           break;
2071         case 9:
2072           /* Bizarre ICC game */
2073           v = VariantTwoKings;
2074           break;
2075         case 16:
2076           v = VariantKriegspiel;
2077           break;
2078         case 17:
2079           v = VariantLosers;
2080           break;
2081         case 22:
2082           v = VariantFischeRandom;
2083           break;
2084         case 23:
2085           v = VariantCrazyhouse;
2086           break;
2087         case 24:
2088           v = VariantBughouse;
2089           break;
2090         case 25:
2091           v = Variant3Check;
2092           break;
2093         case 26:
2094           /* Not quite the same as FICS suicide! */
2095           v = VariantGiveaway;
2096           break;
2097         case 27:
2098           v = VariantAtomic;
2099           break;
2100         case 28:
2101           v = VariantShatranj;
2102           break;
2103
2104         /* Temporary names for future ICC types.  The name *will* change in
2105            the next xboard/WinBoard release after ICC defines it. */
2106         case 29:
2107           v = Variant29;
2108           break;
2109         case 30:
2110           v = Variant30;
2111           break;
2112         case 31:
2113           v = Variant31;
2114           break;
2115         case 32:
2116           v = Variant32;
2117           break;
2118         case 33:
2119           v = Variant33;
2120           break;
2121         case 34:
2122           v = Variant34;
2123           break;
2124         case 35:
2125           v = Variant35;
2126           break;
2127         case 36:
2128           v = Variant36;
2129           break;
2130         case 37:
2131           v = VariantShogi;
2132           break;
2133         case 38:
2134           v = VariantXiangqi;
2135           break;
2136         case 39:
2137           v = VariantCourier;
2138           break;
2139         case 40:
2140           v = VariantGothic;
2141           break;
2142         case 41:
2143           v = VariantCapablanca;
2144           break;
2145         case 42:
2146           v = VariantKnightmate;
2147           break;
2148         case 43:
2149           v = VariantFairy;
2150           break;
2151         case 44:
2152           v = VariantCylinder;
2153           break;
2154         case 45:
2155           v = VariantFalcon;
2156           break;
2157         case 46:
2158           v = VariantCapaRandom;
2159           break;
2160         case 47:
2161           v = VariantBerolina;
2162           break;
2163         case 48:
2164           v = VariantJanus;
2165           break;
2166         case 49:
2167           v = VariantSuper;
2168           break;
2169         case 50:
2170           v = VariantGreat;
2171           break;
2172         case -1:
2173           /* Found "wild" or "w" in the string but no number;
2174              must assume it's normal chess. */
2175           v = VariantNormal;
2176           break;
2177         default:
2178           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2179           if( (len >= MSG_SIZ) && appData.debugMode )
2180             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2181
2182           DisplayError(buf, 0);
2183           v = VariantUnknown;
2184           break;
2185         }
2186       }
2187     }
2188     if (appData.debugMode) {
2189       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2190               e, wnum, VariantName(v));
2191     }
2192     return v;
2193 }
2194
2195 static int leftover_start = 0, leftover_len = 0;
2196 char star_match[STAR_MATCH_N][MSG_SIZ];
2197
2198 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2199    advance *index beyond it, and set leftover_start to the new value of
2200    *index; else return FALSE.  If pattern contains the character '*', it
2201    matches any sequence of characters not containing '\r', '\n', or the
2202    character following the '*' (if any), and the matched sequence(s) are
2203    copied into star_match.
2204    */
2205 int
2206 looking_at ( char *buf, int *index, char *pattern)
2207 {
2208     char *bufp = &buf[*index], *patternp = pattern;
2209     int star_count = 0;
2210     char *matchp = star_match[0];
2211
2212     for (;;) {
2213         if (*patternp == NULLCHAR) {
2214             *index = leftover_start = bufp - buf;
2215             *matchp = NULLCHAR;
2216             return TRUE;
2217         }
2218         if (*bufp == NULLCHAR) return FALSE;
2219         if (*patternp == '*') {
2220             if (*bufp == *(patternp + 1)) {
2221                 *matchp = NULLCHAR;
2222                 matchp = star_match[++star_count];
2223                 patternp += 2;
2224                 bufp++;
2225                 continue;
2226             } else if (*bufp == '\n' || *bufp == '\r') {
2227                 patternp++;
2228                 if (*patternp == NULLCHAR)
2229                   continue;
2230                 else
2231                   return FALSE;
2232             } else {
2233                 *matchp++ = *bufp++;
2234                 continue;
2235             }
2236         }
2237         if (*patternp != *bufp) return FALSE;
2238         patternp++;
2239         bufp++;
2240     }
2241 }
2242
2243 void
2244 SendToPlayer (char *data, int length)
2245 {
2246     int error, outCount;
2247     outCount = OutputToProcess(NoProc, data, length, &error);
2248     if (outCount < length) {
2249         DisplayFatalError(_("Error writing to display"), error, 1);
2250     }
2251 }
2252
2253 void
2254 PackHolding (char packed[], char *holding)
2255 {
2256     char *p = holding;
2257     char *q = packed;
2258     int runlength = 0;
2259     int curr = 9999;
2260     do {
2261         if (*p == curr) {
2262             runlength++;
2263         } else {
2264             switch (runlength) {
2265               case 0:
2266                 break;
2267               case 1:
2268                 *q++ = curr;
2269                 break;
2270               case 2:
2271                 *q++ = curr;
2272                 *q++ = curr;
2273                 break;
2274               default:
2275                 sprintf(q, "%d", runlength);
2276                 while (*q) q++;
2277                 *q++ = curr;
2278                 break;
2279             }
2280             runlength = 1;
2281             curr = *p;
2282         }
2283     } while (*p++);
2284     *q = NULLCHAR;
2285 }
2286
2287 /* Telnet protocol requests from the front end */
2288 void
2289 TelnetRequest (unsigned char ddww, unsigned char option)
2290 {
2291     unsigned char msg[3];
2292     int outCount, outError;
2293
2294     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2295
2296     if (appData.debugMode) {
2297         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2298         switch (ddww) {
2299           case TN_DO:
2300             ddwwStr = "DO";
2301             break;
2302           case TN_DONT:
2303             ddwwStr = "DONT";
2304             break;
2305           case TN_WILL:
2306             ddwwStr = "WILL";
2307             break;
2308           case TN_WONT:
2309             ddwwStr = "WONT";
2310             break;
2311           default:
2312             ddwwStr = buf1;
2313             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2314             break;
2315         }
2316         switch (option) {
2317           case TN_ECHO:
2318             optionStr = "ECHO";
2319             break;
2320           default:
2321             optionStr = buf2;
2322             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2323             break;
2324         }
2325         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2326     }
2327     msg[0] = TN_IAC;
2328     msg[1] = ddww;
2329     msg[2] = option;
2330     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2331     if (outCount < 3) {
2332         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2333     }
2334 }
2335
2336 void
2337 DoEcho ()
2338 {
2339     if (!appData.icsActive) return;
2340     TelnetRequest(TN_DO, TN_ECHO);
2341 }
2342
2343 void
2344 DontEcho ()
2345 {
2346     if (!appData.icsActive) return;
2347     TelnetRequest(TN_DONT, TN_ECHO);
2348 }
2349
2350 void
2351 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2352 {
2353     /* put the holdings sent to us by the server on the board holdings area */
2354     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2355     char p;
2356     ChessSquare piece;
2357
2358     if(gameInfo.holdingsWidth < 2)  return;
2359     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2360         return; // prevent overwriting by pre-board holdings
2361
2362     if( (int)lowestPiece >= BlackPawn ) {
2363         holdingsColumn = 0;
2364         countsColumn = 1;
2365         holdingsStartRow = BOARD_HEIGHT-1;
2366         direction = -1;
2367     } else {
2368         holdingsColumn = BOARD_WIDTH-1;
2369         countsColumn = BOARD_WIDTH-2;
2370         holdingsStartRow = 0;
2371         direction = 1;
2372     }
2373
2374     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2375         board[i][holdingsColumn] = EmptySquare;
2376         board[i][countsColumn]   = (ChessSquare) 0;
2377     }
2378     while( (p=*holdings++) != NULLCHAR ) {
2379         piece = CharToPiece( ToUpper(p) );
2380         if(piece == EmptySquare) continue;
2381         /*j = (int) piece - (int) WhitePawn;*/
2382         j = PieceToNumber(piece);
2383         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2384         if(j < 0) continue;               /* should not happen */
2385         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2386         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2387         board[holdingsStartRow+j*direction][countsColumn]++;
2388     }
2389 }
2390
2391
2392 void
2393 VariantSwitch (Board board, VariantClass newVariant)
2394 {
2395    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2396    static Board oldBoard;
2397
2398    startedFromPositionFile = FALSE;
2399    if(gameInfo.variant == newVariant) return;
2400
2401    /* [HGM] This routine is called each time an assignment is made to
2402     * gameInfo.variant during a game, to make sure the board sizes
2403     * are set to match the new variant. If that means adding or deleting
2404     * holdings, we shift the playing board accordingly
2405     * This kludge is needed because in ICS observe mode, we get boards
2406     * of an ongoing game without knowing the variant, and learn about the
2407     * latter only later. This can be because of the move list we requested,
2408     * in which case the game history is refilled from the beginning anyway,
2409     * but also when receiving holdings of a crazyhouse game. In the latter
2410     * case we want to add those holdings to the already received position.
2411     */
2412
2413
2414    if (appData.debugMode) {
2415      fprintf(debugFP, "Switch board from %s to %s\n",
2416              VariantName(gameInfo.variant), VariantName(newVariant));
2417      setbuf(debugFP, NULL);
2418    }
2419    shuffleOpenings = 0;       /* [HGM] shuffle */
2420    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2421    switch(newVariant)
2422      {
2423      case VariantShogi:
2424        newWidth = 9;  newHeight = 9;
2425        gameInfo.holdingsSize = 7;
2426      case VariantBughouse:
2427      case VariantCrazyhouse:
2428        newHoldingsWidth = 2; break;
2429      case VariantGreat:
2430        newWidth = 10;
2431      case VariantSuper:
2432        newHoldingsWidth = 2;
2433        gameInfo.holdingsSize = 8;
2434        break;
2435      case VariantGothic:
2436      case VariantCapablanca:
2437      case VariantCapaRandom:
2438        newWidth = 10;
2439      default:
2440        newHoldingsWidth = gameInfo.holdingsSize = 0;
2441      };
2442
2443    if(newWidth  != gameInfo.boardWidth  ||
2444       newHeight != gameInfo.boardHeight ||
2445       newHoldingsWidth != gameInfo.holdingsWidth ) {
2446
2447      /* shift position to new playing area, if needed */
2448      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2449        for(i=0; i<BOARD_HEIGHT; i++)
2450          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2451            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2452              board[i][j];
2453        for(i=0; i<newHeight; i++) {
2454          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2455          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2456        }
2457      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2458        for(i=0; i<BOARD_HEIGHT; i++)
2459          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2460            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2461              board[i][j];
2462      }
2463      board[HOLDINGS_SET] = 0;
2464      gameInfo.boardWidth  = newWidth;
2465      gameInfo.boardHeight = newHeight;
2466      gameInfo.holdingsWidth = newHoldingsWidth;
2467      gameInfo.variant = newVariant;
2468      InitDrawingSizes(-2, 0);
2469    } else gameInfo.variant = newVariant;
2470    CopyBoard(oldBoard, board);   // remember correctly formatted board
2471      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2472    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2473 }
2474
2475 static int loggedOn = FALSE;
2476
2477 /*-- Game start info cache: --*/
2478 int gs_gamenum;
2479 char gs_kind[MSG_SIZ];
2480 static char player1Name[128] = "";
2481 static char player2Name[128] = "";
2482 static char cont_seq[] = "\n\\   ";
2483 static int player1Rating = -1;
2484 static int player2Rating = -1;
2485 /*----------------------------*/
2486
2487 ColorClass curColor = ColorNormal;
2488 int suppressKibitz = 0;
2489
2490 // [HGM] seekgraph
2491 Boolean soughtPending = FALSE;
2492 Boolean seekGraphUp;
2493 #define MAX_SEEK_ADS 200
2494 #define SQUARE 0x80
2495 char *seekAdList[MAX_SEEK_ADS];
2496 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2497 float tcList[MAX_SEEK_ADS];
2498 char colorList[MAX_SEEK_ADS];
2499 int nrOfSeekAds = 0;
2500 int minRating = 1010, maxRating = 2800;
2501 int hMargin = 10, vMargin = 20, h, w;
2502 extern int squareSize, lineGap;
2503
2504 void
2505 PlotSeekAd (int i)
2506 {
2507         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2508         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2509         if(r < minRating+100 && r >=0 ) r = minRating+100;
2510         if(r > maxRating) r = maxRating;
2511         if(tc < 1.f) tc = 1.f;
2512         if(tc > 95.f) tc = 95.f;
2513         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2514         y = ((double)r - minRating)/(maxRating - minRating)
2515             * (h-vMargin-squareSize/8-1) + vMargin;
2516         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2517         if(strstr(seekAdList[i], " u ")) color = 1;
2518         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2519            !strstr(seekAdList[i], "bullet") &&
2520            !strstr(seekAdList[i], "blitz") &&
2521            !strstr(seekAdList[i], "standard") ) color = 2;
2522         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2523         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2524 }
2525
2526 void
2527 PlotSingleSeekAd (int i)
2528 {
2529         PlotSeekAd(i);
2530 }
2531
2532 void
2533 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2534 {
2535         char buf[MSG_SIZ], *ext = "";
2536         VariantClass v = StringToVariant(type);
2537         if(strstr(type, "wild")) {
2538             ext = type + 4; // append wild number
2539             if(v == VariantFischeRandom) type = "chess960"; else
2540             if(v == VariantLoadable) type = "setup"; else
2541             type = VariantName(v);
2542         }
2543         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2544         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2545             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2546             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2547             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2548             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2549             seekNrList[nrOfSeekAds] = nr;
2550             zList[nrOfSeekAds] = 0;
2551             seekAdList[nrOfSeekAds++] = StrSave(buf);
2552             if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2553         }
2554 }
2555
2556 void
2557 EraseSeekDot (int i)
2558 {
2559     int x = xList[i], y = yList[i], d=squareSize/4, k;
2560     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2561     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2562     // now replot every dot that overlapped
2563     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2564         int xx = xList[k], yy = yList[k];
2565         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2566             DrawSeekDot(xx, yy, colorList[k]);
2567     }
2568 }
2569
2570 void
2571 RemoveSeekAd (int nr)
2572 {
2573         int i;
2574         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2575             EraseSeekDot(i);
2576             if(seekAdList[i]) free(seekAdList[i]);
2577             seekAdList[i] = seekAdList[--nrOfSeekAds];
2578             seekNrList[i] = seekNrList[nrOfSeekAds];
2579             ratingList[i] = ratingList[nrOfSeekAds];
2580             colorList[i]  = colorList[nrOfSeekAds];
2581             tcList[i] = tcList[nrOfSeekAds];
2582             xList[i]  = xList[nrOfSeekAds];
2583             yList[i]  = yList[nrOfSeekAds];
2584             zList[i]  = zList[nrOfSeekAds];
2585             seekAdList[nrOfSeekAds] = NULL;
2586             break;
2587         }
2588 }
2589
2590 Boolean
2591 MatchSoughtLine (char *line)
2592 {
2593     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2594     int nr, base, inc, u=0; char dummy;
2595
2596     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2597        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2598        (u=1) &&
2599        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2600         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2601         // match: compact and save the line
2602         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2603         return TRUE;
2604     }
2605     return FALSE;
2606 }
2607
2608 int
2609 DrawSeekGraph ()
2610 {
2611     int i;
2612     if(!seekGraphUp) return FALSE;
2613     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2614     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2615
2616     DrawSeekBackground(0, 0, w, h);
2617     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2618     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2619     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2620         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2621         yy = h-1-yy;
2622         DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2623         if(i%500 == 0) {
2624             char buf[MSG_SIZ];
2625             snprintf(buf, MSG_SIZ, "%d", i);
2626             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2627         }
2628     }
2629     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2630     for(i=1; i<100; i+=(i<10?1:5)) {
2631         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2632         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2633         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2634             char buf[MSG_SIZ];
2635             snprintf(buf, MSG_SIZ, "%d", i);
2636             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2637         }
2638     }
2639     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2640     return TRUE;
2641 }
2642
2643 int
2644 SeekGraphClick (ClickType click, int x, int y, int moving)
2645 {
2646     static int lastDown = 0, displayed = 0, lastSecond;
2647     if(y < 0) return FALSE;
2648     if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2649         (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2650         if(!seekGraphUp) return FALSE;
2651         seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2652         DrawPosition(TRUE, NULL);
2653         return TRUE;
2654     }
2655     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2656         if(click == Release || moving) return FALSE;
2657         nrOfSeekAds = 0;
2658         soughtPending = TRUE;
2659         SendToICS(ics_prefix);
2660         SendToICS("sought\n"); // should this be "sought all"?
2661     } else { // issue challenge based on clicked ad
2662         int dist = 10000; int i, closest = 0, second = 0;
2663         for(i=0; i<nrOfSeekAds; i++) {
2664             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2665             if(d < dist) { dist = d; closest = i; }
2666             second += (d - zList[i] < 120); // count in-range ads
2667             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2668         }
2669         if(dist < 120) {
2670             char buf[MSG_SIZ];
2671             second = (second > 1);
2672             if(displayed != closest || second != lastSecond) {
2673                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2674                 lastSecond = second; displayed = closest;
2675             }
2676             if(click == Press) {
2677                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2678                 lastDown = closest;
2679                 return TRUE;
2680             } // on press 'hit', only show info
2681             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2682             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2683             SendToICS(ics_prefix);
2684             SendToICS(buf);
2685             return TRUE; // let incoming board of started game pop down the graph
2686         } else if(click == Release) { // release 'miss' is ignored
2687             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2688             if(moving == 2) { // right up-click
2689                 nrOfSeekAds = 0; // refresh graph
2690                 soughtPending = TRUE;
2691                 SendToICS(ics_prefix);
2692                 SendToICS("sought\n"); // should this be "sought all"?
2693             }
2694             return TRUE;
2695         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2696         // press miss or release hit 'pop down' seek graph
2697         seekGraphUp = FALSE;
2698         DrawPosition(TRUE, NULL);
2699     }
2700     return TRUE;
2701 }
2702
2703 void
2704 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2705 {
2706 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2707 #define STARTED_NONE 0
2708 #define STARTED_MOVES 1
2709 #define STARTED_BOARD 2
2710 #define STARTED_OBSERVE 3
2711 #define STARTED_HOLDINGS 4
2712 #define STARTED_CHATTER 5
2713 #define STARTED_COMMENT 6
2714 #define STARTED_MOVES_NOHIDE 7
2715
2716     static int started = STARTED_NONE;
2717     static char parse[20000];
2718     static int parse_pos = 0;
2719     static char buf[BUF_SIZE + 1];
2720     static int firstTime = TRUE, intfSet = FALSE;
2721     static ColorClass prevColor = ColorNormal;
2722     static int savingComment = FALSE;
2723     static int cmatch = 0; // continuation sequence match
2724     char *bp;
2725     char str[MSG_SIZ];
2726     int i, oldi;
2727     int buf_len;
2728     int next_out;
2729     int tkind;
2730     int backup;    /* [DM] For zippy color lines */
2731     char *p;
2732     char talker[MSG_SIZ]; // [HGM] chat
2733     int channel;
2734
2735     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2736
2737     if (appData.debugMode) {
2738       if (!error) {
2739         fprintf(debugFP, "<ICS: ");
2740         show_bytes(debugFP, data, count);
2741         fprintf(debugFP, "\n");
2742       }
2743     }
2744
2745     if (appData.debugMode) { int f = forwardMostMove;
2746         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2747                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2748                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2749     }
2750     if (count > 0) {
2751         /* If last read ended with a partial line that we couldn't parse,
2752            prepend it to the new read and try again. */
2753         if (leftover_len > 0) {
2754             for (i=0; i<leftover_len; i++)
2755               buf[i] = buf[leftover_start + i];
2756         }
2757
2758     /* copy new characters into the buffer */
2759     bp = buf + leftover_len;
2760     buf_len=leftover_len;
2761     for (i=0; i<count; i++)
2762     {
2763         // ignore these
2764         if (data[i] == '\r')
2765             continue;
2766
2767         // join lines split by ICS?
2768         if (!appData.noJoin)
2769         {
2770             /*
2771                 Joining just consists of finding matches against the
2772                 continuation sequence, and discarding that sequence
2773                 if found instead of copying it.  So, until a match
2774                 fails, there's nothing to do since it might be the
2775                 complete sequence, and thus, something we don't want
2776                 copied.
2777             */
2778             if (data[i] == cont_seq[cmatch])
2779             {
2780                 cmatch++;
2781                 if (cmatch == strlen(cont_seq))
2782                 {
2783                     cmatch = 0; // complete match.  just reset the counter
2784
2785                     /*
2786                         it's possible for the ICS to not include the space
2787                         at the end of the last word, making our [correct]
2788                         join operation fuse two separate words.  the server
2789                         does this when the space occurs at the width setting.
2790                     */
2791                     if (!buf_len || buf[buf_len-1] != ' ')
2792                     {
2793                         *bp++ = ' ';
2794                         buf_len++;
2795                     }
2796                 }
2797                 continue;
2798             }
2799             else if (cmatch)
2800             {
2801                 /*
2802                     match failed, so we have to copy what matched before
2803                     falling through and copying this character.  In reality,
2804                     this will only ever be just the newline character, but
2805                     it doesn't hurt to be precise.
2806                 */
2807                 strncpy(bp, cont_seq, cmatch);
2808                 bp += cmatch;
2809                 buf_len += cmatch;
2810                 cmatch = 0;
2811             }
2812         }
2813
2814         // copy this char
2815         *bp++ = data[i];
2816         buf_len++;
2817     }
2818
2819         buf[buf_len] = NULLCHAR;
2820 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2821         next_out = 0;
2822         leftover_start = 0;
2823
2824         i = 0;
2825         while (i < buf_len) {
2826             /* Deal with part of the TELNET option negotiation
2827                protocol.  We refuse to do anything beyond the
2828                defaults, except that we allow the WILL ECHO option,
2829                which ICS uses to turn off password echoing when we are
2830                directly connected to it.  We reject this option
2831                if localLineEditing mode is on (always on in xboard)
2832                and we are talking to port 23, which might be a real
2833                telnet server that will try to keep WILL ECHO on permanently.
2834              */
2835             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2836                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2837                 unsigned char option;
2838                 oldi = i;
2839                 switch ((unsigned char) buf[++i]) {
2840                   case TN_WILL:
2841                     if (appData.debugMode)
2842                       fprintf(debugFP, "\n<WILL ");
2843                     switch (option = (unsigned char) buf[++i]) {
2844                       case TN_ECHO:
2845                         if (appData.debugMode)
2846                           fprintf(debugFP, "ECHO ");
2847                         /* Reply only if this is a change, according
2848                            to the protocol rules. */
2849                         if (remoteEchoOption) break;
2850                         if (appData.localLineEditing &&
2851                             atoi(appData.icsPort) == TN_PORT) {
2852                             TelnetRequest(TN_DONT, TN_ECHO);
2853                         } else {
2854                             EchoOff();
2855                             TelnetRequest(TN_DO, TN_ECHO);
2856                             remoteEchoOption = TRUE;
2857                         }
2858                         break;
2859                       default:
2860                         if (appData.debugMode)
2861                           fprintf(debugFP, "%d ", option);
2862                         /* Whatever this is, we don't want it. */
2863                         TelnetRequest(TN_DONT, option);
2864                         break;
2865                     }
2866                     break;
2867                   case TN_WONT:
2868                     if (appData.debugMode)
2869                       fprintf(debugFP, "\n<WONT ");
2870                     switch (option = (unsigned char) buf[++i]) {
2871                       case TN_ECHO:
2872                         if (appData.debugMode)
2873                           fprintf(debugFP, "ECHO ");
2874                         /* Reply only if this is a change, according
2875                            to the protocol rules. */
2876                         if (!remoteEchoOption) break;
2877                         EchoOn();
2878                         TelnetRequest(TN_DONT, TN_ECHO);
2879                         remoteEchoOption = FALSE;
2880                         break;
2881                       default:
2882                         if (appData.debugMode)
2883                           fprintf(debugFP, "%d ", (unsigned char) option);
2884                         /* Whatever this is, it must already be turned
2885                            off, because we never agree to turn on
2886                            anything non-default, so according to the
2887                            protocol rules, we don't reply. */
2888                         break;
2889                     }
2890                     break;
2891                   case TN_DO:
2892                     if (appData.debugMode)
2893                       fprintf(debugFP, "\n<DO ");
2894                     switch (option = (unsigned char) buf[++i]) {
2895                       default:
2896                         /* Whatever this is, we refuse to do it. */
2897                         if (appData.debugMode)
2898                           fprintf(debugFP, "%d ", option);
2899                         TelnetRequest(TN_WONT, option);
2900                         break;
2901                     }
2902                     break;
2903                   case TN_DONT:
2904                     if (appData.debugMode)
2905                       fprintf(debugFP, "\n<DONT ");
2906                     switch (option = (unsigned char) buf[++i]) {
2907                       default:
2908                         if (appData.debugMode)
2909                           fprintf(debugFP, "%d ", option);
2910                         /* Whatever this is, we are already not doing
2911                            it, because we never agree to do anything
2912                            non-default, so according to the protocol
2913                            rules, we don't reply. */
2914                         break;
2915                     }
2916                     break;
2917                   case TN_IAC:
2918                     if (appData.debugMode)
2919                       fprintf(debugFP, "\n<IAC ");
2920                     /* Doubled IAC; pass it through */
2921                     i--;
2922                     break;
2923                   default:
2924                     if (appData.debugMode)
2925                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2926                     /* Drop all other telnet commands on the floor */
2927                     break;
2928                 }
2929                 if (oldi > next_out)
2930                   SendToPlayer(&buf[next_out], oldi - next_out);
2931                 if (++i > next_out)
2932                   next_out = i;
2933                 continue;
2934             }
2935
2936             /* OK, this at least will *usually* work */
2937             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2938                 loggedOn = TRUE;
2939             }
2940
2941             if (loggedOn && !intfSet) {
2942                 if (ics_type == ICS_ICC) {
2943                   snprintf(str, MSG_SIZ,
2944                           "/set-quietly interface %s\n/set-quietly style 12\n",
2945                           programVersion);
2946                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2947                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2948                 } else if (ics_type == ICS_CHESSNET) {
2949                   snprintf(str, MSG_SIZ, "/style 12\n");
2950                 } else {
2951                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2952                   strcat(str, programVersion);
2953                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2954                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2955                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2956 #ifdef WIN32
2957                   strcat(str, "$iset nohighlight 1\n");
2958 #endif
2959                   strcat(str, "$iset lock 1\n$style 12\n");
2960                 }
2961                 SendToICS(str);
2962                 NotifyFrontendLogin();
2963                 intfSet = TRUE;
2964             }
2965
2966             if (started == STARTED_COMMENT) {
2967                 /* Accumulate characters in comment */
2968                 parse[parse_pos++] = buf[i];
2969                 if (buf[i] == '\n') {
2970                     parse[parse_pos] = NULLCHAR;
2971                     if(chattingPartner>=0) {
2972                         char mess[MSG_SIZ];
2973                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2974                         OutputChatMessage(chattingPartner, mess);
2975                         chattingPartner = -1;
2976                         next_out = i+1; // [HGM] suppress printing in ICS window
2977                     } else
2978                     if(!suppressKibitz) // [HGM] kibitz
2979                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2980                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2981                         int nrDigit = 0, nrAlph = 0, j;
2982                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2983                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2984                         parse[parse_pos] = NULLCHAR;
2985                         // try to be smart: if it does not look like search info, it should go to
2986                         // ICS interaction window after all, not to engine-output window.
2987                         for(j=0; j<parse_pos; j++) { // count letters and digits
2988                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2989                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2990                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2991                         }
2992                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2993                             int depth=0; float score;
2994                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2995                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2996                                 pvInfoList[forwardMostMove-1].depth = depth;
2997                                 pvInfoList[forwardMostMove-1].score = 100*score;
2998                             }
2999                             OutputKibitz(suppressKibitz, parse);
3000                         } else {
3001                             char tmp[MSG_SIZ];
3002                             if(gameMode == IcsObserving) // restore original ICS messages
3003                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3004                             else
3005                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3006                             SendToPlayer(tmp, strlen(tmp));
3007                         }
3008                         next_out = i+1; // [HGM] suppress printing in ICS window
3009                     }
3010                     started = STARTED_NONE;
3011                 } else {
3012                     /* Don't match patterns against characters in comment */
3013                     i++;
3014                     continue;
3015                 }
3016             }
3017             if (started == STARTED_CHATTER) {
3018                 if (buf[i] != '\n') {
3019                     /* Don't match patterns against characters in chatter */
3020                     i++;
3021                     continue;
3022                 }
3023                 started = STARTED_NONE;
3024                 if(suppressKibitz) next_out = i+1;
3025             }
3026
3027             /* Kludge to deal with rcmd protocol */
3028             if (firstTime && looking_at(buf, &i, "\001*")) {
3029                 DisplayFatalError(&buf[1], 0, 1);
3030                 continue;
3031             } else {
3032                 firstTime = FALSE;
3033             }
3034
3035             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3036                 ics_type = ICS_ICC;
3037                 ics_prefix = "/";
3038                 if (appData.debugMode)
3039                   fprintf(debugFP, "ics_type %d\n", ics_type);
3040                 continue;
3041             }
3042             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3043                 ics_type = ICS_FICS;
3044                 ics_prefix = "$";
3045                 if (appData.debugMode)
3046                   fprintf(debugFP, "ics_type %d\n", ics_type);
3047                 continue;
3048             }
3049             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3050                 ics_type = ICS_CHESSNET;
3051                 ics_prefix = "/";
3052                 if (appData.debugMode)
3053                   fprintf(debugFP, "ics_type %d\n", ics_type);
3054                 continue;
3055             }
3056
3057             if (!loggedOn &&
3058                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3059                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3060                  looking_at(buf, &i, "will be \"*\""))) {
3061               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3062               continue;
3063             }
3064
3065             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3066               char buf[MSG_SIZ];
3067               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3068               DisplayIcsInteractionTitle(buf);
3069               have_set_title = TRUE;
3070             }
3071
3072             /* skip finger notes */
3073             if (started == STARTED_NONE &&
3074                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3075                  (buf[i] == '1' && buf[i+1] == '0')) &&
3076                 buf[i+2] == ':' && buf[i+3] == ' ') {
3077               started = STARTED_CHATTER;
3078               i += 3;
3079               continue;
3080             }
3081
3082             oldi = i;
3083             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3084             if(appData.seekGraph) {
3085                 if(soughtPending && MatchSoughtLine(buf+i)) {
3086                     i = strstr(buf+i, "rated") - buf;
3087                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3088                     next_out = leftover_start = i;
3089                     started = STARTED_CHATTER;
3090                     suppressKibitz = TRUE;
3091                     continue;
3092                 }
3093                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3094                         && looking_at(buf, &i, "* ads displayed")) {
3095                     soughtPending = FALSE;
3096                     seekGraphUp = TRUE;
3097                     DrawSeekGraph();
3098                     continue;
3099                 }
3100                 if(appData.autoRefresh) {
3101                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3102                         int s = (ics_type == ICS_ICC); // ICC format differs
3103                         if(seekGraphUp)
3104                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3105                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3106                         looking_at(buf, &i, "*% "); // eat prompt
3107                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3108                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3109                         next_out = i; // suppress
3110                         continue;
3111                     }
3112                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3113                         char *p = star_match[0];
3114                         while(*p) {
3115                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3116                             while(*p && *p++ != ' '); // next
3117                         }
3118                         looking_at(buf, &i, "*% "); // eat prompt
3119                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3120                         next_out = i;
3121                         continue;
3122                     }
3123                 }
3124             }
3125
3126             /* skip formula vars */
3127             if (started == STARTED_NONE &&
3128                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3129               started = STARTED_CHATTER;
3130               i += 3;
3131               continue;
3132             }
3133
3134             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3135             if (appData.autoKibitz && started == STARTED_NONE &&
3136                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3137                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3138                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3139                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3140                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3141                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3142                         suppressKibitz = TRUE;
3143                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3144                         next_out = i;
3145                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3146                                 && (gameMode == IcsPlayingWhite)) ||
3147                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3148                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3149                             started = STARTED_CHATTER; // own kibitz we simply discard
3150                         else {
3151                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3152                             parse_pos = 0; parse[0] = NULLCHAR;
3153                             savingComment = TRUE;
3154                             suppressKibitz = gameMode != IcsObserving ? 2 :
3155                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3156                         }
3157                         continue;
3158                 } else
3159                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3160                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3161                          && atoi(star_match[0])) {
3162                     // suppress the acknowledgements of our own autoKibitz
3163                     char *p;
3164                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3165                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3166                     SendToPlayer(star_match[0], strlen(star_match[0]));
3167                     if(looking_at(buf, &i, "*% ")) // eat prompt
3168                         suppressKibitz = FALSE;
3169                     next_out = i;
3170                     continue;
3171                 }
3172             } // [HGM] kibitz: end of patch
3173
3174             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3175
3176             // [HGM] chat: intercept tells by users for which we have an open chat window
3177             channel = -1;
3178             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3179                                            looking_at(buf, &i, "* whispers:") ||
3180                                            looking_at(buf, &i, "* kibitzes:") ||
3181                                            looking_at(buf, &i, "* shouts:") ||
3182                                            looking_at(buf, &i, "* c-shouts:") ||
3183                                            looking_at(buf, &i, "--> * ") ||
3184                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3185                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3186                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3187                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3188                 int p;
3189                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3190                 chattingPartner = -1;
3191
3192                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3193                 for(p=0; p<MAX_CHAT; p++) {
3194                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3195                     talker[0] = '['; strcat(talker, "] ");
3196                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3197                     chattingPartner = p; break;
3198                     }
3199                 } else
3200                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3201                 for(p=0; p<MAX_CHAT; p++) {
3202                     if(!strcmp("kibitzes", chatPartner[p])) {
3203                         talker[0] = '['; strcat(talker, "] ");
3204                         chattingPartner = p; break;
3205                     }
3206                 } else
3207                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3208                 for(p=0; p<MAX_CHAT; p++) {
3209                     if(!strcmp("whispers", chatPartner[p])) {
3210                         talker[0] = '['; strcat(talker, "] ");
3211                         chattingPartner = p; break;
3212                     }
3213                 } else
3214                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3215                   if(buf[i-8] == '-' && buf[i-3] == 't')
3216                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3217                     if(!strcmp("c-shouts", chatPartner[p])) {
3218                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3219                         chattingPartner = p; break;
3220                     }
3221                   }
3222                   if(chattingPartner < 0)
3223                   for(p=0; p<MAX_CHAT; p++) {
3224                     if(!strcmp("shouts", chatPartner[p])) {
3225                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3226                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3227                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3228                         chattingPartner = p; break;
3229                     }
3230                   }
3231                 }
3232                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3233                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3234                     talker[0] = 0; Colorize(ColorTell, FALSE);
3235                     chattingPartner = p; break;
3236                 }
3237                 if(chattingPartner<0) i = oldi; else {
3238                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3239                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3240                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3241                     started = STARTED_COMMENT;
3242                     parse_pos = 0; parse[0] = NULLCHAR;
3243                     savingComment = 3 + chattingPartner; // counts as TRUE
3244                     suppressKibitz = TRUE;
3245                     continue;
3246                 }
3247             } // [HGM] chat: end of patch
3248
3249           backup = i;
3250             if (appData.zippyTalk || appData.zippyPlay) {
3251                 /* [DM] Backup address for color zippy lines */
3252 #if ZIPPY
3253                if (loggedOn == TRUE)
3254                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3255                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3256 #endif
3257             } // [DM] 'else { ' deleted
3258                 if (
3259                     /* Regular tells and says */
3260                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3261                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3262                     looking_at(buf, &i, "* says: ") ||
3263                     /* Don't color "message" or "messages" output */
3264                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3265                     looking_at(buf, &i, "*. * at *:*: ") ||
3266                     looking_at(buf, &i, "--* (*:*): ") ||
3267                     /* Message notifications (same color as tells) */
3268                     looking_at(buf, &i, "* has left a message ") ||
3269                     looking_at(buf, &i, "* just sent you a message:\n") ||
3270                     /* Whispers and kibitzes */
3271                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3272                     looking_at(buf, &i, "* kibitzes: ") ||
3273                     /* Channel tells */
3274                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3275
3276                   if (tkind == 1 && strchr(star_match[0], ':')) {
3277                       /* Avoid "tells you:" spoofs in channels */
3278                      tkind = 3;
3279                   }
3280                   if (star_match[0][0] == NULLCHAR ||
3281                       strchr(star_match[0], ' ') ||
3282                       (tkind == 3 && strchr(star_match[1], ' '))) {
3283                     /* Reject bogus matches */
3284                     i = oldi;
3285                   } else {
3286                     if (appData.colorize) {
3287                       if (oldi > next_out) {
3288                         SendToPlayer(&buf[next_out], oldi - next_out);
3289                         next_out = oldi;
3290                       }
3291                       switch (tkind) {
3292                       case 1:
3293                         Colorize(ColorTell, FALSE);
3294                         curColor = ColorTell;
3295                         break;
3296                       case 2:
3297                         Colorize(ColorKibitz, FALSE);
3298                         curColor = ColorKibitz;
3299                         break;
3300                       case 3:
3301                         p = strrchr(star_match[1], '(');
3302                         if (p == NULL) {
3303                           p = star_match[1];
3304                         } else {
3305                           p++;
3306                         }
3307                         if (atoi(p) == 1) {
3308                           Colorize(ColorChannel1, FALSE);
3309                           curColor = ColorChannel1;
3310                         } else {
3311                           Colorize(ColorChannel, FALSE);
3312                           curColor = ColorChannel;
3313                         }
3314                         break;
3315                       case 5:
3316                         curColor = ColorNormal;
3317                         break;
3318                       }
3319                     }
3320                     if (started == STARTED_NONE && appData.autoComment &&
3321                         (gameMode == IcsObserving ||
3322                          gameMode == IcsPlayingWhite ||
3323                          gameMode == IcsPlayingBlack)) {
3324                       parse_pos = i - oldi;
3325                       memcpy(parse, &buf[oldi], parse_pos);
3326                       parse[parse_pos] = NULLCHAR;
3327                       started = STARTED_COMMENT;
3328                       savingComment = TRUE;
3329                     } else {
3330                       started = STARTED_CHATTER;
3331                       savingComment = FALSE;
3332                     }
3333                     loggedOn = TRUE;
3334                     continue;
3335                   }
3336                 }
3337
3338                 if (looking_at(buf, &i, "* s-shouts: ") ||
3339                     looking_at(buf, &i, "* c-shouts: ")) {
3340                     if (appData.colorize) {
3341                         if (oldi > next_out) {
3342                             SendToPlayer(&buf[next_out], oldi - next_out);
3343                             next_out = oldi;
3344                         }
3345                         Colorize(ColorSShout, FALSE);
3346                         curColor = ColorSShout;
3347                     }
3348                     loggedOn = TRUE;
3349                     started = STARTED_CHATTER;
3350                     continue;
3351                 }
3352
3353                 if (looking_at(buf, &i, "--->")) {
3354                     loggedOn = TRUE;
3355                     continue;
3356                 }
3357
3358                 if (looking_at(buf, &i, "* shouts: ") ||
3359                     looking_at(buf, &i, "--> ")) {
3360                     if (appData.colorize) {
3361                         if (oldi > next_out) {
3362                             SendToPlayer(&buf[next_out], oldi - next_out);
3363                             next_out = oldi;
3364                         }
3365                         Colorize(ColorShout, FALSE);
3366                         curColor = ColorShout;
3367                     }
3368                     loggedOn = TRUE;
3369                     started = STARTED_CHATTER;
3370                     continue;
3371                 }
3372
3373                 if (looking_at( buf, &i, "Challenge:")) {
3374                     if (appData.colorize) {
3375                         if (oldi > next_out) {
3376                             SendToPlayer(&buf[next_out], oldi - next_out);
3377                             next_out = oldi;
3378                         }
3379                         Colorize(ColorChallenge, FALSE);
3380                         curColor = ColorChallenge;
3381                     }
3382                     loggedOn = TRUE;
3383                     continue;
3384                 }
3385
3386                 if (looking_at(buf, &i, "* offers you") ||
3387                     looking_at(buf, &i, "* offers to be") ||
3388                     looking_at(buf, &i, "* would like to") ||
3389                     looking_at(buf, &i, "* requests to") ||
3390                     looking_at(buf, &i, "Your opponent offers") ||
3391                     looking_at(buf, &i, "Your opponent requests")) {
3392
3393                     if (appData.colorize) {
3394                         if (oldi > next_out) {
3395                             SendToPlayer(&buf[next_out], oldi - next_out);
3396                             next_out = oldi;
3397                         }
3398                         Colorize(ColorRequest, FALSE);
3399                         curColor = ColorRequest;
3400                     }
3401                     continue;
3402                 }
3403
3404                 if (looking_at(buf, &i, "* (*) seeking")) {
3405                     if (appData.colorize) {
3406                         if (oldi > next_out) {
3407                             SendToPlayer(&buf[next_out], oldi - next_out);
3408                             next_out = oldi;
3409                         }
3410                         Colorize(ColorSeek, FALSE);
3411                         curColor = ColorSeek;
3412                     }
3413                     continue;
3414             }
3415
3416           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3417
3418             if (looking_at(buf, &i, "\\   ")) {
3419                 if (prevColor != ColorNormal) {
3420                     if (oldi > next_out) {
3421                         SendToPlayer(&buf[next_out], oldi - next_out);
3422                         next_out = oldi;
3423                     }
3424                     Colorize(prevColor, TRUE);
3425                     curColor = prevColor;
3426                 }
3427                 if (savingComment) {
3428                     parse_pos = i - oldi;
3429                     memcpy(parse, &buf[oldi], parse_pos);
3430                     parse[parse_pos] = NULLCHAR;
3431                     started = STARTED_COMMENT;
3432                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3433                         chattingPartner = savingComment - 3; // kludge to remember the box
3434                 } else {
3435                     started = STARTED_CHATTER;
3436                 }
3437                 continue;
3438             }
3439
3440             if (looking_at(buf, &i, "Black Strength :") ||
3441                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3442                 looking_at(buf, &i, "<10>") ||
3443                 looking_at(buf, &i, "#@#")) {
3444                 /* Wrong board style */
3445                 loggedOn = TRUE;
3446                 SendToICS(ics_prefix);
3447                 SendToICS("set style 12\n");
3448                 SendToICS(ics_prefix);
3449                 SendToICS("refresh\n");
3450                 continue;
3451             }
3452
3453             if (looking_at(buf, &i, "login:")) {
3454               if (!have_sent_ICS_logon) {
3455                 if(ICSInitScript())
3456                   have_sent_ICS_logon = 1;
3457                 else // no init script was found
3458                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3459               } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3460                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3461               }
3462                 continue;
3463             }
3464
3465             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3466                 (looking_at(buf, &i, "\n<12> ") ||
3467                  looking_at(buf, &i, "<12> "))) {
3468                 loggedOn = TRUE;
3469                 if (oldi > next_out) {
3470                     SendToPlayer(&buf[next_out], oldi - next_out);
3471                 }
3472                 next_out = i;
3473                 started = STARTED_BOARD;
3474                 parse_pos = 0;
3475                 continue;
3476             }
3477
3478             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3479                 looking_at(buf, &i, "<b1> ")) {
3480                 if (oldi > next_out) {
3481                     SendToPlayer(&buf[next_out], oldi - next_out);
3482                 }
3483                 next_out = i;
3484                 started = STARTED_HOLDINGS;
3485                 parse_pos = 0;
3486                 continue;
3487             }
3488
3489             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3490                 loggedOn = TRUE;
3491                 /* Header for a move list -- first line */
3492
3493                 switch (ics_getting_history) {
3494                   case H_FALSE:
3495                     switch (gameMode) {
3496                       case IcsIdle:
3497                       case BeginningOfGame:
3498                         /* User typed "moves" or "oldmoves" while we
3499                            were idle.  Pretend we asked for these
3500                            moves and soak them up so user can step
3501                            through them and/or save them.
3502                            */
3503                         Reset(FALSE, TRUE);
3504                         gameMode = IcsObserving;
3505                         ModeHighlight();
3506                         ics_gamenum = -1;
3507                         ics_getting_history = H_GOT_UNREQ_HEADER;
3508                         break;
3509                       case EditGame: /*?*/
3510                       case EditPosition: /*?*/
3511                         /* Should above feature work in these modes too? */
3512                         /* For now it doesn't */
3513                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3514                         break;
3515                       default:
3516                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3517                         break;
3518                     }
3519                     break;
3520                   case H_REQUESTED:
3521                     /* Is this the right one? */
3522                     if (gameInfo.white && gameInfo.black &&
3523                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3524                         strcmp(gameInfo.black, star_match[2]) == 0) {
3525                         /* All is well */
3526                         ics_getting_history = H_GOT_REQ_HEADER;
3527                     }
3528                     break;
3529                   case H_GOT_REQ_HEADER:
3530                   case H_GOT_UNREQ_HEADER:
3531                   case H_GOT_UNWANTED_HEADER:
3532                   case H_GETTING_MOVES:
3533                     /* Should not happen */
3534                     DisplayError(_("Error gathering move list: two headers"), 0);
3535                     ics_getting_history = H_FALSE;
3536                     break;
3537                 }
3538
3539                 /* Save player ratings into gameInfo if needed */
3540                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3541                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3542                     (gameInfo.whiteRating == -1 ||
3543                      gameInfo.blackRating == -1)) {
3544
3545                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3546                     gameInfo.blackRating = string_to_rating(star_match[3]);
3547                     if (appData.debugMode)
3548                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3549                               gameInfo.whiteRating, gameInfo.blackRating);
3550                 }
3551                 continue;
3552             }
3553
3554             if (looking_at(buf, &i,
3555               "* * match, initial time: * minute*, increment: * second")) {
3556                 /* Header for a move list -- second line */
3557                 /* Initial board will follow if this is a wild game */
3558                 if (gameInfo.event != NULL) free(gameInfo.event);
3559                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3560                 gameInfo.event = StrSave(str);
3561                 /* [HGM] we switched variant. Translate boards if needed. */
3562                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3563                 continue;
3564             }
3565
3566             if (looking_at(buf, &i, "Move  ")) {
3567                 /* Beginning of a move list */
3568                 switch (ics_getting_history) {
3569                   case H_FALSE:
3570                     /* Normally should not happen */
3571                     /* Maybe user hit reset while we were parsing */
3572                     break;
3573                   case H_REQUESTED:
3574                     /* Happens if we are ignoring a move list that is not
3575                      * the one we just requested.  Common if the user
3576                      * tries to observe two games without turning off
3577                      * getMoveList */
3578                     break;
3579                   case H_GETTING_MOVES:
3580                     /* Should not happen */
3581                     DisplayError(_("Error gathering move list: nested"), 0);
3582                     ics_getting_history = H_FALSE;
3583                     break;
3584                   case H_GOT_REQ_HEADER:
3585                     ics_getting_history = H_GETTING_MOVES;
3586                     started = STARTED_MOVES;
3587                     parse_pos = 0;
3588                     if (oldi > next_out) {
3589                         SendToPlayer(&buf[next_out], oldi - next_out);
3590                     }
3591                     break;
3592                   case H_GOT_UNREQ_HEADER:
3593                     ics_getting_history = H_GETTING_MOVES;
3594                     started = STARTED_MOVES_NOHIDE;
3595                     parse_pos = 0;
3596                     break;
3597                   case H_GOT_UNWANTED_HEADER:
3598                     ics_getting_history = H_FALSE;
3599                     break;
3600                 }
3601                 continue;
3602             }
3603
3604             if (looking_at(buf, &i, "% ") ||
3605                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3606                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3607                 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3608                     soughtPending = FALSE;
3609                     seekGraphUp = TRUE;
3610                     DrawSeekGraph();
3611                 }
3612                 if(suppressKibitz) next_out = i;
3613                 savingComment = FALSE;
3614                 suppressKibitz = 0;
3615                 switch (started) {
3616                   case STARTED_MOVES:
3617                   case STARTED_MOVES_NOHIDE:
3618                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3619                     parse[parse_pos + i - oldi] = NULLCHAR;
3620                     ParseGameHistory(parse);
3621 #if ZIPPY
3622                     if (appData.zippyPlay && first.initDone) {
3623                         FeedMovesToProgram(&first, forwardMostMove);
3624                         if (gameMode == IcsPlayingWhite) {
3625                             if (WhiteOnMove(forwardMostMove)) {
3626                                 if (first.sendTime) {
3627                                   if (first.useColors) {
3628                                     SendToProgram("black\n", &first);
3629                                   }
3630                                   SendTimeRemaining(&first, TRUE);
3631                                 }
3632                                 if (first.useColors) {
3633                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3634                                 }
3635                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3636                                 first.maybeThinking = TRUE;
3637                             } else {
3638                                 if (first.usePlayother) {
3639                                   if (first.sendTime) {
3640                                     SendTimeRemaining(&first, TRUE);
3641                                   }
3642                                   SendToProgram("playother\n", &first);
3643                                   firstMove = FALSE;
3644                                 } else {
3645                                   firstMove = TRUE;
3646                                 }
3647                             }
3648                         } else if (gameMode == IcsPlayingBlack) {
3649                             if (!WhiteOnMove(forwardMostMove)) {
3650                                 if (first.sendTime) {
3651                                   if (first.useColors) {
3652                                     SendToProgram("white\n", &first);
3653                                   }
3654                                   SendTimeRemaining(&first, FALSE);
3655                                 }
3656                                 if (first.useColors) {
3657                                   SendToProgram("black\n", &first);
3658                                 }
3659                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3660                                 first.maybeThinking = TRUE;
3661                             } else {
3662                                 if (first.usePlayother) {
3663                                   if (first.sendTime) {
3664                                     SendTimeRemaining(&first, FALSE);
3665                                   }
3666                                   SendToProgram("playother\n", &first);
3667                                   firstMove = FALSE;
3668                                 } else {
3669                                   firstMove = TRUE;
3670                                 }
3671                             }
3672                         }
3673                     }
3674 #endif
3675                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3676                         /* Moves came from oldmoves or moves command
3677                            while we weren't doing anything else.
3678                            */
3679                         currentMove = forwardMostMove;
3680                         ClearHighlights();/*!!could figure this out*/
3681                         flipView = appData.flipView;
3682                         DrawPosition(TRUE, boards[currentMove]);
3683                         DisplayBothClocks();
3684                         snprintf(str, MSG_SIZ, "%s %s %s",
3685                                 gameInfo.white, _("vs."),  gameInfo.black);
3686                         DisplayTitle(str);
3687                         gameMode = IcsIdle;
3688                     } else {
3689                         /* Moves were history of an active game */
3690                         if (gameInfo.resultDetails != NULL) {
3691                             free(gameInfo.resultDetails);
3692                             gameInfo.resultDetails = NULL;
3693                         }
3694                     }
3695                     HistorySet(parseList, backwardMostMove,
3696                                forwardMostMove, currentMove-1);
3697                     DisplayMove(currentMove - 1);
3698                     if (started == STARTED_MOVES) next_out = i;
3699                     started = STARTED_NONE;
3700                     ics_getting_history = H_FALSE;
3701                     break;
3702
3703                   case STARTED_OBSERVE:
3704                     started = STARTED_NONE;
3705                     SendToICS(ics_prefix);
3706                     SendToICS("refresh\n");
3707                     break;
3708
3709                   default:
3710                     break;
3711                 }
3712                 if(bookHit) { // [HGM] book: simulate book reply
3713                     static char bookMove[MSG_SIZ]; // a bit generous?
3714
3715                     programStats.nodes = programStats.depth = programStats.time =
3716                     programStats.score = programStats.got_only_move = 0;
3717                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3718
3719                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3720                     strcat(bookMove, bookHit);
3721                     HandleMachineMove(bookMove, &first);
3722                 }
3723                 continue;
3724             }
3725
3726             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3727                  started == STARTED_HOLDINGS ||
3728                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3729                 /* Accumulate characters in move list or board */
3730                 parse[parse_pos++] = buf[i];
3731             }
3732
3733             /* Start of game messages.  Mostly we detect start of game
3734                when the first board image arrives.  On some versions
3735                of the ICS, though, we need to do a "refresh" after starting
3736                to observe in order to get the current board right away. */
3737             if (looking_at(buf, &i, "Adding game * to observation list")) {
3738                 started = STARTED_OBSERVE;
3739                 continue;
3740             }
3741
3742             /* Handle auto-observe */
3743             if (appData.autoObserve &&
3744                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3745                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3746                 char *player;
3747                 /* Choose the player that was highlighted, if any. */
3748                 if (star_match[0][0] == '\033' ||
3749                     star_match[1][0] != '\033') {
3750                     player = star_match[0];
3751                 } else {
3752                     player = star_match[2];
3753                 }
3754                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3755                         ics_prefix, StripHighlightAndTitle(player));
3756                 SendToICS(str);
3757
3758                 /* Save ratings from notify string */
3759                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3760                 player1Rating = string_to_rating(star_match[1]);
3761                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3762                 player2Rating = string_to_rating(star_match[3]);
3763
3764                 if (appData.debugMode)
3765                   fprintf(debugFP,
3766                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3767                           player1Name, player1Rating,
3768                           player2Name, player2Rating);
3769
3770                 continue;
3771             }
3772
3773             /* Deal with automatic examine mode after a game,
3774                and with IcsObserving -> IcsExamining transition */
3775             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3776                 looking_at(buf, &i, "has made you an examiner of game *")) {
3777
3778                 int gamenum = atoi(star_match[0]);
3779                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3780                     gamenum == ics_gamenum) {
3781                     /* We were already playing or observing this game;
3782                        no need to refetch history */
3783                     gameMode = IcsExamining;
3784                     if (pausing) {
3785                         pauseExamForwardMostMove = forwardMostMove;
3786                     } else if (currentMove < forwardMostMove) {
3787                         ForwardInner(forwardMostMove);
3788                     }
3789                 } else {
3790                     /* I don't think this case really can happen */
3791                     SendToICS(ics_prefix);
3792                     SendToICS("refresh\n");
3793                 }
3794                 continue;
3795             }
3796
3797             /* Error messages */
3798 //          if (ics_user_moved) {
3799             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3800                 if (looking_at(buf, &i, "Illegal move") ||
3801                     looking_at(buf, &i, "Not a legal move") ||
3802                     looking_at(buf, &i, "Your king is in check") ||
3803                     looking_at(buf, &i, "It isn't your turn") ||
3804                     looking_at(buf, &i, "It is not your move")) {
3805                     /* Illegal move */
3806                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3807                         currentMove = forwardMostMove-1;
3808                         DisplayMove(currentMove - 1); /* before DMError */
3809                         DrawPosition(FALSE, boards[currentMove]);
3810                         SwitchClocks(forwardMostMove-1); // [HGM] race
3811                         DisplayBothClocks();
3812                     }
3813                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3814                     ics_user_moved = 0;
3815                     continue;
3816                 }
3817             }
3818
3819             if (looking_at(buf, &i, "still have time") ||
3820                 looking_at(buf, &i, "not out of time") ||
3821                 looking_at(buf, &i, "either player is out of time") ||
3822                 looking_at(buf, &i, "has timeseal; checking")) {
3823                 /* We must have called his flag a little too soon */
3824                 whiteFlag = blackFlag = FALSE;
3825                 continue;
3826             }
3827
3828             if (looking_at(buf, &i, "added * seconds to") ||
3829                 looking_at(buf, &i, "seconds were added to")) {
3830                 /* Update the clocks */
3831                 SendToICS(ics_prefix);
3832                 SendToICS("refresh\n");
3833                 continue;
3834             }
3835
3836             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3837                 ics_clock_paused = TRUE;
3838                 StopClocks();
3839                 continue;
3840             }
3841
3842             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3843                 ics_clock_paused = FALSE;
3844                 StartClocks();
3845                 continue;
3846             }
3847
3848             /* Grab player ratings from the Creating: message.
3849                Note we have to check for the special case when
3850                the ICS inserts things like [white] or [black]. */
3851             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3852                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3853                 /* star_matches:
3854                    0    player 1 name (not necessarily white)
3855                    1    player 1 rating
3856                    2    empty, white, or black (IGNORED)
3857                    3    player 2 name (not necessarily black)
3858                    4    player 2 rating
3859
3860                    The names/ratings are sorted out when the game
3861                    actually starts (below).
3862                 */
3863                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3864                 player1Rating = string_to_rating(star_match[1]);
3865                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3866                 player2Rating = string_to_rating(star_match[4]);
3867
3868                 if (appData.debugMode)
3869                   fprintf(debugFP,
3870                           "Ratings from 'Creating:' %s %d, %s %d\n",
3871                           player1Name, player1Rating,
3872                           player2Name, player2Rating);
3873
3874                 continue;
3875             }
3876
3877             /* Improved generic start/end-of-game messages */
3878             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3879                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3880                 /* If tkind == 0: */
3881                 /* star_match[0] is the game number */
3882                 /*           [1] is the white player's name */
3883                 /*           [2] is the black player's name */
3884                 /* For end-of-game: */
3885                 /*           [3] is the reason for the game end */
3886                 /*           [4] is a PGN end game-token, preceded by " " */
3887                 /* For start-of-game: */
3888                 /*           [3] begins with "Creating" or "Continuing" */
3889                 /*           [4] is " *" or empty (don't care). */
3890                 int gamenum = atoi(star_match[0]);
3891                 char *whitename, *blackname, *why, *endtoken;
3892                 ChessMove endtype = EndOfFile;
3893
3894                 if (tkind == 0) {
3895                   whitename = star_match[1];
3896                   blackname = star_match[2];
3897                   why = star_match[3];
3898                   endtoken = star_match[4];
3899                 } else {
3900                   whitename = star_match[1];
3901                   blackname = star_match[3];
3902                   why = star_match[5];
3903                   endtoken = star_match[6];
3904                 }
3905
3906                 /* Game start messages */
3907                 if (strncmp(why, "Creating ", 9) == 0 ||
3908                     strncmp(why, "Continuing ", 11) == 0) {
3909                     gs_gamenum = gamenum;
3910                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3911                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
3912                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3913 #if ZIPPY
3914                     if (appData.zippyPlay) {
3915                         ZippyGameStart(whitename, blackname);
3916                     }
3917 #endif /*ZIPPY*/
3918                     partnerBoardValid = FALSE; // [HGM] bughouse
3919                     continue;
3920                 }
3921
3922                 /* Game end messages */
3923                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3924                     ics_gamenum != gamenum) {
3925                     continue;
3926                 }
3927                 while (endtoken[0] == ' ') endtoken++;
3928                 switch (endtoken[0]) {
3929                   case '*':
3930                   default:
3931                     endtype = GameUnfinished;
3932                     break;
3933                   case '0':
3934                     endtype = BlackWins;
3935                     break;
3936                   case '1':
3937                     if (endtoken[1] == '/')
3938                       endtype = GameIsDrawn;
3939                     else
3940                       endtype = WhiteWins;
3941                     break;
3942                 }
3943                 GameEnds(endtype, why, GE_ICS);
3944 #if ZIPPY
3945                 if (appData.zippyPlay && first.initDone) {
3946                     ZippyGameEnd(endtype, why);
3947                     if (first.pr == NoProc) {
3948                       /* Start the next process early so that we'll
3949                          be ready for the next challenge */
3950                       StartChessProgram(&first);
3951                     }
3952                     /* Send "new" early, in case this command takes
3953                        a long time to finish, so that we'll be ready
3954                        for the next challenge. */
3955                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3956                     Reset(TRUE, TRUE);
3957                 }
3958 #endif /*ZIPPY*/
3959                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3960                 continue;
3961             }
3962
3963             if (looking_at(buf, &i, "Removing game * from observation") ||
3964                 looking_at(buf, &i, "no longer observing game *") ||
3965                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3966                 if (gameMode == IcsObserving &&
3967                     atoi(star_match[0]) == ics_gamenum)
3968                   {
3969                       /* icsEngineAnalyze */
3970                       if (appData.icsEngineAnalyze) {
3971                             ExitAnalyzeMode();
3972                             ModeHighlight();
3973                       }
3974                       StopClocks();
3975                       gameMode = IcsIdle;
3976                       ics_gamenum = -1;
3977                       ics_user_moved = FALSE;
3978                   }
3979                 continue;
3980             }
3981
3982             if (looking_at(buf, &i, "no longer examining game *")) {
3983                 if (gameMode == IcsExamining &&
3984                     atoi(star_match[0]) == ics_gamenum)
3985                   {
3986                       gameMode = IcsIdle;
3987                       ics_gamenum = -1;
3988                       ics_user_moved = FALSE;
3989                   }
3990                 continue;
3991             }
3992
3993             /* Advance leftover_start past any newlines we find,
3994                so only partial lines can get reparsed */
3995             if (looking_at(buf, &i, "\n")) {
3996                 prevColor = curColor;
3997                 if (curColor != ColorNormal) {
3998                     if (oldi > next_out) {
3999                         SendToPlayer(&buf[next_out], oldi - next_out);
4000                         next_out = oldi;
4001                     }
4002                     Colorize(ColorNormal, FALSE);
4003                     curColor = ColorNormal;
4004                 }
4005                 if (started == STARTED_BOARD) {
4006                     started = STARTED_NONE;
4007                     parse[parse_pos] = NULLCHAR;
4008                     ParseBoard12(parse);
4009                     ics_user_moved = 0;
4010
4011                     /* Send premove here */
4012                     if (appData.premove) {
4013                       char str[MSG_SIZ];
4014                       if (currentMove == 0 &&
4015                           gameMode == IcsPlayingWhite &&
4016                           appData.premoveWhite) {
4017                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
4018                         if (appData.debugMode)
4019                           fprintf(debugFP, "Sending premove:\n");
4020                         SendToICS(str);
4021                       } else if (currentMove == 1 &&
4022                                  gameMode == IcsPlayingBlack &&
4023                                  appData.premoveBlack) {
4024                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
4025                         if (appData.debugMode)
4026                           fprintf(debugFP, "Sending premove:\n");
4027                         SendToICS(str);
4028                       } else if (gotPremove) {
4029                         gotPremove = 0;
4030                         ClearPremoveHighlights();
4031                         if (appData.debugMode)
4032                           fprintf(debugFP, "Sending premove:\n");
4033                           UserMoveEvent(premoveFromX, premoveFromY,
4034                                         premoveToX, premoveToY,
4035                                         premovePromoChar);
4036                       }
4037                     }
4038
4039                     /* Usually suppress following prompt */
4040                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4041                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4042                         if (looking_at(buf, &i, "*% ")) {
4043                             savingComment = FALSE;
4044                             suppressKibitz = 0;
4045                         }
4046                     }
4047                     next_out = i;
4048                 } else if (started == STARTED_HOLDINGS) {
4049                     int gamenum;
4050                     char new_piece[MSG_SIZ];
4051                     started = STARTED_NONE;
4052                     parse[parse_pos] = NULLCHAR;
4053                     if (appData.debugMode)
4054                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4055                                                         parse, currentMove);
4056                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4057                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4058                         if (gameInfo.variant == VariantNormal) {
4059                           /* [HGM] We seem to switch variant during a game!
4060                            * Presumably no holdings were displayed, so we have
4061                            * to move the position two files to the right to
4062                            * create room for them!
4063                            */
4064                           VariantClass newVariant;
4065                           switch(gameInfo.boardWidth) { // base guess on board width
4066                                 case 9:  newVariant = VariantShogi; break;
4067                                 case 10: newVariant = VariantGreat; break;
4068                                 default: newVariant = VariantCrazyhouse; break;
4069                           }
4070                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4071                           /* Get a move list just to see the header, which
4072                              will tell us whether this is really bug or zh */
4073                           if (ics_getting_history == H_FALSE) {
4074                             ics_getting_history = H_REQUESTED;
4075                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4076                             SendToICS(str);
4077                           }
4078                         }
4079                         new_piece[0] = NULLCHAR;
4080                         sscanf(parse, "game %d white [%s black [%s <- %s",
4081                                &gamenum, white_holding, black_holding,
4082                                new_piece);
4083                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4084                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4085                         /* [HGM] copy holdings to board holdings area */
4086                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4087                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4088                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4089 #if ZIPPY
4090                         if (appData.zippyPlay && first.initDone) {
4091                             ZippyHoldings(white_holding, black_holding,
4092                                           new_piece);
4093                         }
4094 #endif /*ZIPPY*/
4095                         if (tinyLayout || smallLayout) {
4096                             char wh[16], bh[16];
4097                             PackHolding(wh, white_holding);
4098                             PackHolding(bh, black_holding);
4099                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4100                                     gameInfo.white, gameInfo.black);
4101                         } else {
4102                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4103                                     gameInfo.white, white_holding, _("vs."),
4104                                     gameInfo.black, black_holding);
4105                         }
4106                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4107                         DrawPosition(FALSE, boards[currentMove]);
4108                         DisplayTitle(str);
4109                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4110                         sscanf(parse, "game %d white [%s black [%s <- %s",
4111                                &gamenum, white_holding, black_holding,
4112                                new_piece);
4113                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4114                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4115                         /* [HGM] copy holdings to partner-board holdings area */
4116                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4117                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4118                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4119                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4120                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4121                       }
4122                     }
4123                     /* Suppress following prompt */
4124                     if (looking_at(buf, &i, "*% ")) {
4125                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4126                         savingComment = FALSE;
4127                         suppressKibitz = 0;
4128                     }
4129                     next_out = i;
4130                 }
4131                 continue;
4132             }
4133
4134             i++;                /* skip unparsed character and loop back */
4135         }
4136
4137         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4138 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4139 //          SendToPlayer(&buf[next_out], i - next_out);
4140             started != STARTED_HOLDINGS && leftover_start > next_out) {
4141             SendToPlayer(&buf[next_out], leftover_start - next_out);
4142             next_out = i;
4143         }
4144
4145         leftover_len = buf_len - leftover_start;
4146         /* if buffer ends with something we couldn't parse,
4147            reparse it after appending the next read */
4148
4149     } else if (count == 0) {
4150         RemoveInputSource(isr);
4151         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4152     } else {
4153         DisplayFatalError(_("Error reading from ICS"), error, 1);
4154     }
4155 }
4156
4157
4158 /* Board style 12 looks like this:
4159
4160    <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
4161
4162  * The "<12> " is stripped before it gets to this routine.  The two
4163  * trailing 0's (flip state and clock ticking) are later addition, and
4164  * some chess servers may not have them, or may have only the first.
4165  * Additional trailing fields may be added in the future.
4166  */
4167
4168 #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"
4169
4170 #define RELATION_OBSERVING_PLAYED    0
4171 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4172 #define RELATION_PLAYING_MYMOVE      1
4173 #define RELATION_PLAYING_NOTMYMOVE  -1
4174 #define RELATION_EXAMINING           2
4175 #define RELATION_ISOLATED_BOARD     -3
4176 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4177
4178 void
4179 ParseBoard12 (char *string)
4180 {
4181     GameMode newGameMode;
4182     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
4183     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
4184     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4185     char to_play, board_chars[200];
4186     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4187     char black[32], white[32];
4188     Board board;
4189     int prevMove = currentMove;
4190     int ticking = 2;
4191     ChessMove moveType;
4192     int fromX, fromY, toX, toY;
4193     char promoChar;
4194     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4195     char *bookHit = NULL; // [HGM] book
4196     Boolean weird = FALSE, reqFlag = FALSE;
4197
4198     fromX = fromY = toX = toY = -1;
4199
4200     newGame = FALSE;
4201
4202     if (appData.debugMode)
4203       fprintf(debugFP, _("Parsing board: %s\n"), string);
4204
4205     move_str[0] = NULLCHAR;
4206     elapsed_time[0] = NULLCHAR;
4207     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4208         int  i = 0, j;
4209         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4210             if(string[i] == ' ') { ranks++; files = 0; }
4211             else files++;
4212             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4213             i++;
4214         }
4215         for(j = 0; j <i; j++) board_chars[j] = string[j];
4216         board_chars[i] = '\0';
4217         string += i + 1;
4218     }
4219     n = sscanf(string, PATTERN, &to_play, &double_push,
4220                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4221                &gamenum, white, black, &relation, &basetime, &increment,
4222                &white_stren, &black_stren, &white_time, &black_time,
4223                &moveNum, str, elapsed_time, move_str, &ics_flip,
4224                &ticking);
4225
4226     if (n < 21) {
4227         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4228         DisplayError(str, 0);
4229         return;
4230     }
4231
4232     /* Convert the move number to internal form */
4233     moveNum = (moveNum - 1) * 2;
4234     if (to_play == 'B') moveNum++;
4235     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4236       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4237                         0, 1);
4238       return;
4239     }
4240
4241     switch (relation) {
4242       case RELATION_OBSERVING_PLAYED:
4243       case RELATION_OBSERVING_STATIC:
4244         if (gamenum == -1) {
4245             /* Old ICC buglet */
4246             relation = RELATION_OBSERVING_STATIC;
4247         }
4248         newGameMode = IcsObserving;
4249         break;
4250       case RELATION_PLAYING_MYMOVE:
4251       case RELATION_PLAYING_NOTMYMOVE:
4252         newGameMode =
4253           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4254             IcsPlayingWhite : IcsPlayingBlack;
4255         soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4256         break;
4257       case RELATION_EXAMINING:
4258         newGameMode = IcsExamining;
4259         break;
4260       case RELATION_ISOLATED_BOARD:
4261       default:
4262         /* Just display this board.  If user was doing something else,
4263            we will forget about it until the next board comes. */
4264         newGameMode = IcsIdle;
4265         break;
4266       case RELATION_STARTING_POSITION:
4267         newGameMode = gameMode;
4268         break;
4269     }
4270
4271     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4272         gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4273          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4274       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4275       int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4276       static int lastBgGame = -1;
4277       char *toSqr;
4278       for (k = 0; k < ranks; k++) {
4279         for (j = 0; j < files; j++)
4280           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4281         if(gameInfo.holdingsWidth > 1) {
4282              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4283              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4284         }
4285       }
4286       CopyBoard(partnerBoard, board);
4287       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4288         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4289         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4290       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4291       if(toSqr = strchr(str, '-')) {
4292         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4293         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4294       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4295       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4296       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4297       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4298       if(twoBoards) {
4299           DisplayWhiteClock(white_time*fac, to_play == 'W');
4300           DisplayBlackClock(black_time*fac, to_play != 'W');
4301           activePartner = to_play;
4302           if(gamenum != lastBgGame) {
4303               char buf[MSG_SIZ];
4304               snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4305               DisplayTitle(buf);
4306           }
4307           lastBgGame = gamenum;
4308           activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4309                       partnerUp = 0; flipView = !flipView; } // [HGM] dual
4310       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4311                  (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4312       DisplayMessage(partnerStatus, "");
4313         partnerBoardValid = TRUE;
4314       return;
4315     }
4316
4317     if(appData.dualBoard && appData.bgObserve) {
4318         if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4319             SendToICS(ics_prefix), SendToICS("pobserve\n");
4320         else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4321             char buf[MSG_SIZ];
4322             snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4323             SendToICS(buf);
4324         }
4325     }
4326
4327     /* Modify behavior for initial board display on move listing
4328        of wild games.
4329        */
4330     switch (ics_getting_history) {
4331       case H_FALSE:
4332       case H_REQUESTED:
4333         break;
4334       case H_GOT_REQ_HEADER:
4335       case H_GOT_UNREQ_HEADER:
4336         /* This is the initial position of the current game */
4337         gamenum = ics_gamenum;
4338         moveNum = 0;            /* old ICS bug workaround */
4339         if (to_play == 'B') {
4340           startedFromSetupPosition = TRUE;
4341           blackPlaysFirst = TRUE;
4342           moveNum = 1;
4343           if (forwardMostMove == 0) forwardMostMove = 1;
4344           if (backwardMostMove == 0) backwardMostMove = 1;
4345           if (currentMove == 0) currentMove = 1;
4346         }
4347         newGameMode = gameMode;
4348         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4349         break;
4350       case H_GOT_UNWANTED_HEADER:
4351         /* This is an initial board that we don't want */
4352         return;
4353       case H_GETTING_MOVES:
4354         /* Should not happen */
4355         DisplayError(_("Error gathering move list: extra board"), 0);
4356         ics_getting_history = H_FALSE;
4357         return;
4358     }
4359
4360    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4361                                         move_str[1] == '@' && !gameInfo.holdingsWidth ||
4362                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4363      /* [HGM] We seem to have switched variant unexpectedly
4364       * Try to guess new variant from board size
4365       */
4366           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4367           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4368           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4369           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4370           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4371           if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4372           if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4373           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4374           /* Get a move list just to see the header, which
4375              will tell us whether this is really bug or zh */
4376           if (ics_getting_history == H_FALSE) {
4377             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4378             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4379             SendToICS(str);
4380           }
4381     }
4382
4383     /* Take action if this is the first board of a new game, or of a
4384        different game than is currently being displayed.  */
4385     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4386         relation == RELATION_ISOLATED_BOARD) {
4387
4388         /* Forget the old game and get the history (if any) of the new one */
4389         if (gameMode != BeginningOfGame) {
4390           Reset(TRUE, TRUE);
4391         }
4392         newGame = TRUE;
4393         if (appData.autoRaiseBoard) BoardToTop();
4394         prevMove = -3;
4395         if (gamenum == -1) {
4396             newGameMode = IcsIdle;
4397         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4398                    appData.getMoveList && !reqFlag) {
4399             /* Need to get game history */
4400             ics_getting_history = H_REQUESTED;
4401             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4402             SendToICS(str);
4403         }
4404
4405         /* Initially flip the board to have black on the bottom if playing
4406            black or if the ICS flip flag is set, but let the user change
4407            it with the Flip View button. */
4408         flipView = appData.autoFlipView ?
4409           (newGameMode == IcsPlayingBlack) || ics_flip :
4410           appData.flipView;
4411
4412         /* Done with values from previous mode; copy in new ones */
4413         gameMode = newGameMode;
4414         ModeHighlight();
4415         ics_gamenum = gamenum;
4416         if (gamenum == gs_gamenum) {
4417             int klen = strlen(gs_kind);
4418             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4419             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4420             gameInfo.event = StrSave(str);
4421         } else {
4422             gameInfo.event = StrSave("ICS game");
4423         }
4424         gameInfo.site = StrSave(appData.icsHost);
4425         gameInfo.date = PGNDate();
4426         gameInfo.round = StrSave("-");
4427         gameInfo.white = StrSave(white);
4428         gameInfo.black = StrSave(black);
4429         timeControl = basetime * 60 * 1000;
4430         timeControl_2 = 0;
4431         timeIncrement = increment * 1000;
4432         movesPerSession = 0;
4433         gameInfo.timeControl = TimeControlTagValue();
4434         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4435   if (appData.debugMode) {
4436     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4437     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4438     setbuf(debugFP, NULL);
4439   }
4440
4441         gameInfo.outOfBook = NULL;
4442
4443         /* Do we have the ratings? */
4444         if (strcmp(player1Name, white) == 0 &&
4445             strcmp(player2Name, black) == 0) {
4446             if (appData.debugMode)
4447               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4448                       player1Rating, player2Rating);
4449             gameInfo.whiteRating = player1Rating;
4450             gameInfo.blackRating = player2Rating;
4451         } else if (strcmp(player2Name, white) == 0 &&
4452                    strcmp(player1Name, black) == 0) {
4453             if (appData.debugMode)
4454               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4455                       player2Rating, player1Rating);
4456             gameInfo.whiteRating = player2Rating;
4457             gameInfo.blackRating = player1Rating;
4458         }
4459         player1Name[0] = player2Name[0] = NULLCHAR;
4460
4461         /* Silence shouts if requested */
4462         if (appData.quietPlay &&
4463             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4464             SendToICS(ics_prefix);
4465             SendToICS("set shout 0\n");
4466         }
4467     }
4468
4469     /* Deal with midgame name changes */
4470     if (!newGame) {
4471         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4472             if (gameInfo.white) free(gameInfo.white);
4473             gameInfo.white = StrSave(white);
4474         }
4475         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4476             if (gameInfo.black) free(gameInfo.black);
4477             gameInfo.black = StrSave(black);
4478         }
4479     }
4480
4481     /* Throw away game result if anything actually changes in examine mode */
4482     if (gameMode == IcsExamining && !newGame) {
4483         gameInfo.result = GameUnfinished;
4484         if (gameInfo.resultDetails != NULL) {
4485             free(gameInfo.resultDetails);
4486             gameInfo.resultDetails = NULL;
4487         }
4488     }
4489
4490     /* In pausing && IcsExamining mode, we ignore boards coming
4491        in if they are in a different variation than we are. */
4492     if (pauseExamInvalid) return;
4493     if (pausing && gameMode == IcsExamining) {
4494         if (moveNum <= pauseExamForwardMostMove) {
4495             pauseExamInvalid = TRUE;
4496             forwardMostMove = pauseExamForwardMostMove;
4497             return;
4498         }
4499     }
4500
4501   if (appData.debugMode) {
4502     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4503   }
4504     /* Parse the board */
4505     for (k = 0; k < ranks; k++) {
4506       for (j = 0; j < files; j++)
4507         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4508       if(gameInfo.holdingsWidth > 1) {
4509            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4510            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4511       }
4512     }
4513     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4514       board[5][BOARD_RGHT+1] = WhiteAngel;
4515       board[6][BOARD_RGHT+1] = WhiteMarshall;
4516       board[1][0] = BlackMarshall;
4517       board[2][0] = BlackAngel;
4518       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4519     }
4520     CopyBoard(boards[moveNum], board);
4521     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4522     if (moveNum == 0) {
4523         startedFromSetupPosition =
4524           !CompareBoards(board, initialPosition);
4525         if(startedFromSetupPosition)
4526             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4527     }
4528
4529     /* [HGM] Set castling rights. Take the outermost Rooks,
4530        to make it also work for FRC opening positions. Note that board12
4531        is really defective for later FRC positions, as it has no way to
4532        indicate which Rook can castle if they are on the same side of King.
4533        For the initial position we grant rights to the outermost Rooks,
4534        and remember thos rights, and we then copy them on positions
4535        later in an FRC game. This means WB might not recognize castlings with
4536        Rooks that have moved back to their original position as illegal,
4537        but in ICS mode that is not its job anyway.
4538     */
4539     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4540     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4541
4542         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4543             if(board[0][i] == WhiteRook) j = i;
4544         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4545         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4546             if(board[0][i] == WhiteRook) j = i;
4547         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4548         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4549             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4550         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4551         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4552             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4553         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4554
4555         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4556         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4557         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4558             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4559         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4560             if(board[BOARD_HEIGHT-1][k] == bKing)
4561                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4562         if(gameInfo.variant == VariantTwoKings) {
4563             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4564             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4565             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4566         }
4567     } else { int r;
4568         r = boards[moveNum][CASTLING][0] = initialRights[0];
4569         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4570         r = boards[moveNum][CASTLING][1] = initialRights[1];
4571         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4572         r = boards[moveNum][CASTLING][3] = initialRights[3];
4573         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4574         r = boards[moveNum][CASTLING][4] = initialRights[4];
4575         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4576         /* wildcastle kludge: always assume King has rights */
4577         r = boards[moveNum][CASTLING][2] = initialRights[2];
4578         r = boards[moveNum][CASTLING][5] = initialRights[5];
4579     }
4580     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4581     boards[moveNum][EP_STATUS] = EP_NONE;
4582     if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4583     if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4584     if(double_push !=  -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4585
4586
4587     if (ics_getting_history == H_GOT_REQ_HEADER ||
4588         ics_getting_history == H_GOT_UNREQ_HEADER) {
4589         /* This was an initial position from a move list, not
4590            the current position */
4591         return;
4592     }
4593
4594     /* Update currentMove and known move number limits */
4595     newMove = newGame || moveNum > forwardMostMove;
4596
4597     if (newGame) {
4598         forwardMostMove = backwardMostMove = currentMove = moveNum;
4599         if (gameMode == IcsExamining && moveNum == 0) {
4600           /* Workaround for ICS limitation: we are not told the wild
4601              type when starting to examine a game.  But if we ask for
4602              the move list, the move list header will tell us */
4603             ics_getting_history = H_REQUESTED;
4604             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4605             SendToICS(str);
4606         }
4607     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4608                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4609 #if ZIPPY
4610         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4611         /* [HGM] applied this also to an engine that is silently watching        */
4612         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4613             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4614             gameInfo.variant == currentlyInitializedVariant) {
4615           takeback = forwardMostMove - moveNum;
4616           for (i = 0; i < takeback; i++) {
4617             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4618             SendToProgram("undo\n", &first);
4619           }
4620         }
4621 #endif
4622
4623         forwardMostMove = moveNum;
4624         if (!pausing || currentMove > forwardMostMove)
4625           currentMove = forwardMostMove;
4626     } else {
4627         /* New part of history that is not contiguous with old part */
4628         if (pausing && gameMode == IcsExamining) {
4629             pauseExamInvalid = TRUE;
4630             forwardMostMove = pauseExamForwardMostMove;
4631             return;
4632         }
4633         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4634 #if ZIPPY
4635             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4636                 // [HGM] when we will receive the move list we now request, it will be
4637                 // fed to the engine from the first move on. So if the engine is not
4638                 // in the initial position now, bring it there.
4639                 InitChessProgram(&first, 0);
4640             }
4641 #endif
4642             ics_getting_history = H_REQUESTED;
4643             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4644             SendToICS(str);
4645         }
4646         forwardMostMove = backwardMostMove = currentMove = moveNum;
4647     }
4648
4649     /* Update the clocks */
4650     if (strchr(elapsed_time, '.')) {
4651       /* Time is in ms */
4652       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4653       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4654     } else {
4655       /* Time is in seconds */
4656       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4657       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4658     }
4659
4660
4661 #if ZIPPY
4662     if (appData.zippyPlay && newGame &&
4663         gameMode != IcsObserving && gameMode != IcsIdle &&
4664         gameMode != IcsExamining)
4665       ZippyFirstBoard(moveNum, basetime, increment);
4666 #endif
4667
4668     /* Put the move on the move list, first converting
4669        to canonical algebraic form. */
4670     if (moveNum > 0) {
4671   if (appData.debugMode) {
4672     if (appData.debugMode) { int f = forwardMostMove;
4673         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4674                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4675                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4676     }
4677     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4678     fprintf(debugFP, "moveNum = %d\n", moveNum);
4679     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4680     setbuf(debugFP, NULL);
4681   }
4682         if (moveNum <= backwardMostMove) {
4683             /* We don't know what the board looked like before
4684                this move.  Punt. */
4685           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4686             strcat(parseList[moveNum - 1], " ");
4687             strcat(parseList[moveNum - 1], elapsed_time);
4688             moveList[moveNum - 1][0] = NULLCHAR;
4689         } else if (strcmp(move_str, "none") == 0) {
4690             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4691             /* Again, we don't know what the board looked like;
4692                this is really the start of the game. */
4693             parseList[moveNum - 1][0] = NULLCHAR;
4694             moveList[moveNum - 1][0] = NULLCHAR;
4695             backwardMostMove = moveNum;
4696             startedFromSetupPosition = TRUE;
4697             fromX = fromY = toX = toY = -1;
4698         } else {
4699           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4700           //                 So we parse the long-algebraic move string in stead of the SAN move
4701           int valid; char buf[MSG_SIZ], *prom;
4702
4703           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4704                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4705           // str looks something like "Q/a1-a2"; kill the slash
4706           if(str[1] == '/')
4707             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4708           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4709           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4710                 strcat(buf, prom); // long move lacks promo specification!
4711           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4712                 if(appData.debugMode)
4713                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4714                 safeStrCpy(move_str, buf, MSG_SIZ);
4715           }
4716           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4717                                 &fromX, &fromY, &toX, &toY, &promoChar)
4718                || ParseOneMove(buf, moveNum - 1, &moveType,
4719                                 &fromX, &fromY, &toX, &toY, &promoChar);
4720           // end of long SAN patch
4721           if (valid) {
4722             (void) CoordsToAlgebraic(boards[moveNum - 1],
4723                                      PosFlags(moveNum - 1),
4724                                      fromY, fromX, toY, toX, promoChar,
4725                                      parseList[moveNum-1]);
4726             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4727               case MT_NONE:
4728               case MT_STALEMATE:
4729               default:
4730                 break;
4731               case MT_CHECK:
4732                 if(gameInfo.variant != VariantShogi)
4733                     strcat(parseList[moveNum - 1], "+");
4734                 break;
4735               case MT_CHECKMATE:
4736               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4737                 strcat(parseList[moveNum - 1], "#");
4738                 break;
4739             }
4740             strcat(parseList[moveNum - 1], " ");
4741             strcat(parseList[moveNum - 1], elapsed_time);
4742             /* currentMoveString is set as a side-effect of ParseOneMove */
4743             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4744             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4745             strcat(moveList[moveNum - 1], "\n");
4746
4747             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4748                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4749               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4750                 ChessSquare old, new = boards[moveNum][k][j];
4751                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4752                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4753                   if(old == new) continue;
4754                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4755                   else if(new == WhiteWazir || new == BlackWazir) {
4756                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4757                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4758                       else boards[moveNum][k][j] = old; // preserve type of Gold
4759                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4760                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4761               }
4762           } else {
4763             /* Move from ICS was illegal!?  Punt. */
4764             if (appData.debugMode) {
4765               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4766               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4767             }
4768             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4769             strcat(parseList[moveNum - 1], " ");
4770             strcat(parseList[moveNum - 1], elapsed_time);
4771             moveList[moveNum - 1][0] = NULLCHAR;
4772             fromX = fromY = toX = toY = -1;
4773           }
4774         }
4775   if (appData.debugMode) {
4776     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4777     setbuf(debugFP, NULL);
4778   }
4779
4780 #if ZIPPY
4781         /* Send move to chess program (BEFORE animating it). */
4782         if (appData.zippyPlay && !newGame && newMove &&
4783            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4784
4785             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4786                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4787                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4788                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4789                             move_str);
4790                     DisplayError(str, 0);
4791                 } else {
4792                     if (first.sendTime) {
4793                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4794                     }
4795                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4796                     if (firstMove && !bookHit) {
4797                         firstMove = FALSE;
4798                         if (first.useColors) {
4799                           SendToProgram(gameMode == IcsPlayingWhite ?
4800                                         "white\ngo\n" :
4801                                         "black\ngo\n", &first);
4802                         } else {
4803                           SendToProgram("go\n", &first);
4804                         }
4805                         first.maybeThinking = TRUE;
4806                     }
4807                 }
4808             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4809               if (moveList[moveNum - 1][0] == NULLCHAR) {
4810                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4811                 DisplayError(str, 0);
4812               } else {
4813                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4814                 SendMoveToProgram(moveNum - 1, &first);
4815               }
4816             }
4817         }
4818 #endif
4819     }
4820
4821     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4822         /* If move comes from a remote source, animate it.  If it
4823            isn't remote, it will have already been animated. */
4824         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4825             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4826         }
4827         if (!pausing && appData.highlightLastMove) {
4828             SetHighlights(fromX, fromY, toX, toY);
4829         }
4830     }
4831
4832     /* Start the clocks */
4833     whiteFlag = blackFlag = FALSE;
4834     appData.clockMode = !(basetime == 0 && increment == 0);
4835     if (ticking == 0) {
4836       ics_clock_paused = TRUE;
4837       StopClocks();
4838     } else if (ticking == 1) {
4839       ics_clock_paused = FALSE;
4840     }
4841     if (gameMode == IcsIdle ||
4842         relation == RELATION_OBSERVING_STATIC ||
4843         relation == RELATION_EXAMINING ||
4844         ics_clock_paused)
4845       DisplayBothClocks();
4846     else
4847       StartClocks();
4848
4849     /* Display opponents and material strengths */
4850     if (gameInfo.variant != VariantBughouse &&
4851         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4852         if (tinyLayout || smallLayout) {
4853             if(gameInfo.variant == VariantNormal)
4854               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4855                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4856                     basetime, increment);
4857             else
4858               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4859                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4860                     basetime, increment, (int) gameInfo.variant);
4861         } else {
4862             if(gameInfo.variant == VariantNormal)
4863               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4864                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4865                     basetime, increment);
4866             else
4867               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
4868                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4869                     basetime, increment, VariantName(gameInfo.variant));
4870         }
4871         DisplayTitle(str);
4872   if (appData.debugMode) {
4873     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4874   }
4875     }
4876
4877
4878     /* Display the board */
4879     if (!pausing && !appData.noGUI) {
4880
4881       if (appData.premove)
4882           if (!gotPremove ||
4883              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4884              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4885               ClearPremoveHighlights();
4886
4887       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4888         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4889       DrawPosition(j, boards[currentMove]);
4890
4891       DisplayMove(moveNum - 1);
4892       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4893             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4894               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4895         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4896       }
4897     }
4898
4899     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4900 #if ZIPPY
4901     if(bookHit) { // [HGM] book: simulate book reply
4902         static char bookMove[MSG_SIZ]; // a bit generous?
4903
4904         programStats.nodes = programStats.depth = programStats.time =
4905         programStats.score = programStats.got_only_move = 0;
4906         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4907
4908         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4909         strcat(bookMove, bookHit);
4910         HandleMachineMove(bookMove, &first);
4911     }
4912 #endif
4913 }
4914
4915 void
4916 GetMoveListEvent ()
4917 {
4918     char buf[MSG_SIZ];
4919     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4920         ics_getting_history = H_REQUESTED;
4921         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4922         SendToICS(buf);
4923     }
4924 }
4925
4926 void
4927 SendToBoth (char *msg)
4928 {   // to make it easy to keep two engines in step in dual analysis
4929     SendToProgram(msg, &first);
4930     if(second.analyzing) SendToProgram(msg, &second);
4931 }
4932
4933 void
4934 AnalysisPeriodicEvent (int force)
4935 {
4936     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4937          && !force) || !appData.periodicUpdates)
4938       return;
4939
4940     /* Send . command to Crafty to collect stats */
4941     SendToBoth(".\n");
4942
4943     /* Don't send another until we get a response (this makes
4944        us stop sending to old Crafty's which don't understand
4945        the "." command (sending illegal cmds resets node count & time,
4946        which looks bad)) */
4947     programStats.ok_to_send = 0;
4948 }
4949
4950 void
4951 ics_update_width (int new_width)
4952 {
4953         ics_printf("set width %d\n", new_width);
4954 }
4955
4956 void
4957 SendMoveToProgram (int moveNum, ChessProgramState *cps)
4958 {
4959     char buf[MSG_SIZ];
4960
4961     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
4962         // null move in variant where engine does not understand it (for analysis purposes)
4963         SendBoard(cps, moveNum + 1); // send position after move in stead.
4964         return;
4965     }
4966     if (cps->useUsermove) {
4967       SendToProgram("usermove ", cps);
4968     }
4969     if (cps->useSAN) {
4970       char *space;
4971       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4972         int len = space - parseList[moveNum];
4973         memcpy(buf, parseList[moveNum], len);
4974         buf[len++] = '\n';
4975         buf[len] = NULLCHAR;
4976       } else {
4977         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4978       }
4979       SendToProgram(buf, cps);
4980     } else {
4981       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4982         AlphaRank(moveList[moveNum], 4);
4983         SendToProgram(moveList[moveNum], cps);
4984         AlphaRank(moveList[moveNum], 4); // and back
4985       } else
4986       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4987        * the engine. It would be nice to have a better way to identify castle
4988        * moves here. */
4989       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4990                                                                          && cps->useOOCastle) {
4991         int fromX = moveList[moveNum][0] - AAA;
4992         int fromY = moveList[moveNum][1] - ONE;
4993         int toX = moveList[moveNum][2] - AAA;
4994         int toY = moveList[moveNum][3] - ONE;
4995         if((boards[moveNum][fromY][fromX] == WhiteKing
4996             && boards[moveNum][toY][toX] == WhiteRook)
4997            || (boards[moveNum][fromY][fromX] == BlackKing
4998                && boards[moveNum][toY][toX] == BlackRook)) {
4999           if(toX > fromX) SendToProgram("O-O\n", cps);
5000           else SendToProgram("O-O-O\n", cps);
5001         }
5002         else SendToProgram(moveList[moveNum], cps);
5003       } else
5004       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5005         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5006           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5007           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5008                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5009         } else
5010           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5011                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5012         SendToProgram(buf, cps);
5013       }
5014       else SendToProgram(moveList[moveNum], cps);
5015       /* End of additions by Tord */
5016     }
5017
5018     /* [HGM] setting up the opening has brought engine in force mode! */
5019     /*       Send 'go' if we are in a mode where machine should play. */
5020     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5021         (gameMode == TwoMachinesPlay   ||
5022 #if ZIPPY
5023          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
5024 #endif
5025          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5026         SendToProgram("go\n", cps);
5027   if (appData.debugMode) {
5028     fprintf(debugFP, "(extra)\n");
5029   }
5030     }
5031     setboardSpoiledMachineBlack = 0;
5032 }
5033
5034 void
5035 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5036 {
5037     char user_move[MSG_SIZ];
5038     char suffix[4];
5039
5040     if(gameInfo.variant == VariantSChess && promoChar) {
5041         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5042         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5043     } else suffix[0] = NULLCHAR;
5044
5045     switch (moveType) {
5046       default:
5047         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5048                 (int)moveType, fromX, fromY, toX, toY);
5049         DisplayError(user_move + strlen("say "), 0);
5050         break;
5051       case WhiteKingSideCastle:
5052       case BlackKingSideCastle:
5053       case WhiteQueenSideCastleWild:
5054       case BlackQueenSideCastleWild:
5055       /* PUSH Fabien */
5056       case WhiteHSideCastleFR:
5057       case BlackHSideCastleFR:
5058       /* POP Fabien */
5059         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5060         break;
5061       case WhiteQueenSideCastle:
5062       case BlackQueenSideCastle:
5063       case WhiteKingSideCastleWild:
5064       case BlackKingSideCastleWild:
5065       /* PUSH Fabien */
5066       case WhiteASideCastleFR:
5067       case BlackASideCastleFR:
5068       /* POP Fabien */
5069         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5070         break;
5071       case WhiteNonPromotion:
5072       case BlackNonPromotion:
5073         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5074         break;
5075       case WhitePromotion:
5076       case BlackPromotion:
5077         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
5078           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5079                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5080                 PieceToChar(WhiteFerz));
5081         else if(gameInfo.variant == VariantGreat)
5082           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5083                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5084                 PieceToChar(WhiteMan));
5085         else
5086           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5087                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5088                 promoChar);
5089         break;
5090       case WhiteDrop:
5091       case BlackDrop:
5092       drop:
5093         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5094                  ToUpper(PieceToChar((ChessSquare) fromX)),
5095                  AAA + toX, ONE + toY);
5096         break;
5097       case IllegalMove:  /* could be a variant we don't quite understand */
5098         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5099       case NormalMove:
5100       case WhiteCapturesEnPassant:
5101       case BlackCapturesEnPassant:
5102         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5103                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5104         break;
5105     }
5106     SendToICS(user_move);
5107     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5108         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5109 }
5110
5111 void
5112 UploadGameEvent ()
5113 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5114     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5115     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5116     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5117       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5118       return;
5119     }
5120     if(gameMode != IcsExamining) { // is this ever not the case?
5121         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5122
5123         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5124           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5125         } else { // on FICS we must first go to general examine mode
5126           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5127         }
5128         if(gameInfo.variant != VariantNormal) {
5129             // try figure out wild number, as xboard names are not always valid on ICS
5130             for(i=1; i<=36; i++) {
5131               snprintf(buf, MSG_SIZ, "wild/%d", i);
5132                 if(StringToVariant(buf) == gameInfo.variant) break;
5133             }
5134             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5135             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5136             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5137         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5138         SendToICS(ics_prefix);
5139         SendToICS(buf);
5140         if(startedFromSetupPosition || backwardMostMove != 0) {
5141           fen = PositionToFEN(backwardMostMove, NULL);
5142           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5143             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5144             SendToICS(buf);
5145           } else { // FICS: everything has to set by separate bsetup commands
5146             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5147             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5148             SendToICS(buf);
5149             if(!WhiteOnMove(backwardMostMove)) {
5150                 SendToICS("bsetup tomove black\n");
5151             }
5152             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5153             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5154             SendToICS(buf);
5155             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5156             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5157             SendToICS(buf);
5158             i = boards[backwardMostMove][EP_STATUS];
5159             if(i >= 0) { // set e.p.
5160               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5161                 SendToICS(buf);
5162             }
5163             bsetup++;
5164           }
5165         }
5166       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5167             SendToICS("bsetup done\n"); // switch to normal examining.
5168     }
5169     for(i = backwardMostMove; i<last; i++) {
5170         char buf[20];
5171         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5172         if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5173             int len = strlen(moveList[i]);
5174             snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5175             if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5176         }
5177         SendToICS(buf);
5178     }
5179     SendToICS(ics_prefix);
5180     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5181 }
5182
5183 void
5184 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5185 {
5186     if (rf == DROP_RANK) {
5187       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5188       sprintf(move, "%c@%c%c\n",
5189                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5190     } else {
5191         if (promoChar == 'x' || promoChar == NULLCHAR) {
5192           sprintf(move, "%c%c%c%c\n",
5193                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5194         } else {
5195             sprintf(move, "%c%c%c%c%c\n",
5196                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5197         }
5198     }
5199 }
5200
5201 void
5202 ProcessICSInitScript (FILE *f)
5203 {
5204     char buf[MSG_SIZ];
5205
5206     while (fgets(buf, MSG_SIZ, f)) {
5207         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5208     }
5209
5210     fclose(f);
5211 }
5212
5213
5214 static int lastX, lastY, selectFlag, dragging;
5215
5216 void
5217 Sweep (int step)
5218 {
5219     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5220     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5221     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5222     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5223     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5224     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5225     do {
5226         promoSweep -= step;
5227         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5228         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5229         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5230         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5231         if(!step) step = -1;
5232     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5233             appData.testLegality && (promoSweep == king ||
5234             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5235     if(toX >= 0) {
5236         int victim = boards[currentMove][toY][toX];
5237         boards[currentMove][toY][toX] = promoSweep;
5238         DrawPosition(FALSE, boards[currentMove]);
5239         boards[currentMove][toY][toX] = victim;
5240     } else
5241     ChangeDragPiece(promoSweep);
5242 }
5243
5244 int
5245 PromoScroll (int x, int y)
5246 {
5247   int step = 0;
5248
5249   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5250   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5251   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5252   if(!step) return FALSE;
5253   lastX = x; lastY = y;
5254   if((promoSweep < BlackPawn) == flipView) step = -step;
5255   if(step > 0) selectFlag = 1;
5256   if(!selectFlag) Sweep(step);
5257   return FALSE;
5258 }
5259
5260 void
5261 NextPiece (int step)
5262 {
5263     ChessSquare piece = boards[currentMove][toY][toX];
5264     do {
5265         pieceSweep -= step;
5266         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5267         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5268         if(!step) step = -1;
5269     } while(PieceToChar(pieceSweep) == '.');
5270     boards[currentMove][toY][toX] = pieceSweep;
5271     DrawPosition(FALSE, boards[currentMove]);
5272     boards[currentMove][toY][toX] = piece;
5273 }
5274 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5275 void
5276 AlphaRank (char *move, int n)
5277 {
5278 //    char *p = move, c; int x, y;
5279
5280     if (appData.debugMode) {
5281         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5282     }
5283
5284     if(move[1]=='*' &&
5285        move[2]>='0' && move[2]<='9' &&
5286        move[3]>='a' && move[3]<='x'    ) {
5287         move[1] = '@';
5288         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5289         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5290     } else
5291     if(move[0]>='0' && move[0]<='9' &&
5292        move[1]>='a' && move[1]<='x' &&
5293        move[2]>='0' && move[2]<='9' &&
5294        move[3]>='a' && move[3]<='x'    ) {
5295         /* input move, Shogi -> normal */
5296         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5297         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5298         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5299         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5300     } else
5301     if(move[1]=='@' &&
5302        move[3]>='0' && move[3]<='9' &&
5303        move[2]>='a' && move[2]<='x'    ) {
5304         move[1] = '*';
5305         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5306         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5307     } else
5308     if(
5309        move[0]>='a' && move[0]<='x' &&
5310        move[3]>='0' && move[3]<='9' &&
5311        move[2]>='a' && move[2]<='x'    ) {
5312          /* output move, normal -> Shogi */
5313         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5314         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5315         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5316         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5317         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5318     }
5319     if (appData.debugMode) {
5320         fprintf(debugFP, "   out = '%s'\n", move);
5321     }
5322 }
5323
5324 char yy_textstr[8000];
5325
5326 /* Parser for moves from gnuchess, ICS, or user typein box */
5327 Boolean
5328 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5329 {
5330     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5331
5332     switch (*moveType) {
5333       case WhitePromotion:
5334       case BlackPromotion:
5335       case WhiteNonPromotion:
5336       case BlackNonPromotion:
5337       case NormalMove:
5338       case WhiteCapturesEnPassant:
5339       case BlackCapturesEnPassant:
5340       case WhiteKingSideCastle:
5341       case WhiteQueenSideCastle:
5342       case BlackKingSideCastle:
5343       case BlackQueenSideCastle:
5344       case WhiteKingSideCastleWild:
5345       case WhiteQueenSideCastleWild:
5346       case BlackKingSideCastleWild:
5347       case BlackQueenSideCastleWild:
5348       /* Code added by Tord: */
5349       case WhiteHSideCastleFR:
5350       case WhiteASideCastleFR:
5351       case BlackHSideCastleFR:
5352       case BlackASideCastleFR:
5353       /* End of code added by Tord */
5354       case IllegalMove:         /* bug or odd chess variant */
5355         *fromX = currentMoveString[0] - AAA;
5356         *fromY = currentMoveString[1] - ONE;
5357         *toX = currentMoveString[2] - AAA;
5358         *toY = currentMoveString[3] - ONE;
5359         *promoChar = currentMoveString[4];
5360         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5361             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5362     if (appData.debugMode) {
5363         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5364     }
5365             *fromX = *fromY = *toX = *toY = 0;
5366             return FALSE;
5367         }
5368         if (appData.testLegality) {
5369           return (*moveType != IllegalMove);
5370         } else {
5371           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5372                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5373         }
5374
5375       case WhiteDrop:
5376       case BlackDrop:
5377         *fromX = *moveType == WhiteDrop ?
5378           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5379           (int) CharToPiece(ToLower(currentMoveString[0]));
5380         *fromY = DROP_RANK;
5381         *toX = currentMoveString[2] - AAA;
5382         *toY = currentMoveString[3] - ONE;
5383         *promoChar = NULLCHAR;
5384         return TRUE;
5385
5386       case AmbiguousMove:
5387       case ImpossibleMove:
5388       case EndOfFile:
5389       case ElapsedTime:
5390       case Comment:
5391       case PGNTag:
5392       case NAG:
5393       case WhiteWins:
5394       case BlackWins:
5395       case GameIsDrawn:
5396       default:
5397     if (appData.debugMode) {
5398         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5399     }
5400         /* bug? */
5401         *fromX = *fromY = *toX = *toY = 0;
5402         *promoChar = NULLCHAR;
5403         return FALSE;
5404     }
5405 }
5406
5407 Boolean pushed = FALSE;
5408 char *lastParseAttempt;
5409
5410 void
5411 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5412 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5413   int fromX, fromY, toX, toY; char promoChar;
5414   ChessMove moveType;
5415   Boolean valid;
5416   int nr = 0;
5417
5418   lastParseAttempt = pv; if(!*pv) return;    // turns out we crash when we parse an empty PV
5419   if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5420     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5421     pushed = TRUE;
5422   }
5423   endPV = forwardMostMove;
5424   do {
5425     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5426     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5427     lastParseAttempt = pv;
5428     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5429     if(!valid && nr == 0 &&
5430        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5431         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5432         // Hande case where played move is different from leading PV move
5433         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5434         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5435         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5436         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5437           endPV += 2; // if position different, keep this
5438           moveList[endPV-1][0] = fromX + AAA;
5439           moveList[endPV-1][1] = fromY + ONE;
5440           moveList[endPV-1][2] = toX + AAA;
5441           moveList[endPV-1][3] = toY + ONE;
5442           parseList[endPV-1][0] = NULLCHAR;
5443           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5444         }
5445       }
5446     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5447     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5448     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5449     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5450         valid++; // allow comments in PV
5451         continue;
5452     }
5453     nr++;
5454     if(endPV+1 > framePtr) break; // no space, truncate
5455     if(!valid) break;
5456     endPV++;
5457     CopyBoard(boards[endPV], boards[endPV-1]);
5458     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5459     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5460     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5461     CoordsToAlgebraic(boards[endPV - 1],
5462                              PosFlags(endPV - 1),
5463                              fromY, fromX, toY, toX, promoChar,
5464                              parseList[endPV - 1]);
5465   } while(valid);
5466   if(atEnd == 2) return; // used hidden, for PV conversion
5467   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5468   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5469   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5470                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5471   DrawPosition(TRUE, boards[currentMove]);
5472 }
5473
5474 int
5475 MultiPV (ChessProgramState *cps)
5476 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5477         int i;
5478         for(i=0; i<cps->nrOptions; i++)
5479             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5480                 return i;
5481         return -1;
5482 }
5483
5484 Boolean
5485 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5486 {
5487         int startPV, multi, lineStart, origIndex = index;
5488         char *p, buf2[MSG_SIZ];
5489         ChessProgramState *cps = (pane ? &second : &first);
5490
5491         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5492         lastX = x; lastY = y;
5493         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5494         lineStart = startPV = index;
5495         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5496         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5497         index = startPV;
5498         do{ while(buf[index] && buf[index] != '\n') index++;
5499         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5500         buf[index] = 0;
5501         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(cps)) >= 0) {
5502                 int n = cps->option[multi].value;
5503                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5504                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5505                 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5506                 cps->option[multi].value = n;
5507                 *start = *end = 0;
5508                 return FALSE;
5509         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5510                 ExcludeClick(origIndex - lineStart);
5511                 return FALSE;
5512         }
5513         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5514         *start = startPV; *end = index-1;
5515         return TRUE;
5516 }
5517
5518 char *
5519 PvToSAN (char *pv)
5520 {
5521         static char buf[10*MSG_SIZ];
5522         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5523         *buf = NULLCHAR;
5524         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5525         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5526         for(i = forwardMostMove; i<endPV; i++){
5527             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5528             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5529             k += strlen(buf+k);
5530         }
5531         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5532         if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5533         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5534         endPV = savedEnd;
5535         return buf;
5536 }
5537
5538 Boolean
5539 LoadPV (int x, int y)
5540 { // called on right mouse click to load PV
5541   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5542   lastX = x; lastY = y;
5543   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5544   return TRUE;
5545 }
5546
5547 void
5548 UnLoadPV ()
5549 {
5550   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5551   if(endPV < 0) return;
5552   if(appData.autoCopyPV) CopyFENToClipboard();
5553   endPV = -1;
5554   if(gameMode == AnalyzeMode && currentMove > forwardMostMove) {
5555         Boolean saveAnimate = appData.animate;
5556         if(pushed) {
5557             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5558                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5559             } else storedGames--; // abandon shelved tail of original game
5560         }
5561         pushed = FALSE;
5562         forwardMostMove = currentMove;
5563         currentMove = oldFMM;
5564         appData.animate = FALSE;
5565         ToNrEvent(forwardMostMove);
5566         appData.animate = saveAnimate;
5567   }
5568   currentMove = forwardMostMove;
5569   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5570   ClearPremoveHighlights();
5571   DrawPosition(TRUE, boards[currentMove]);
5572 }
5573
5574 void
5575 MovePV (int x, int y, int h)
5576 { // step through PV based on mouse coordinates (called on mouse move)
5577   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5578
5579   // we must somehow check if right button is still down (might be released off board!)
5580   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5581   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5582   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5583   if(!step) return;
5584   lastX = x; lastY = y;
5585
5586   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5587   if(endPV < 0) return;
5588   if(y < margin) step = 1; else
5589   if(y > h - margin) step = -1;
5590   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5591   currentMove += step;
5592   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5593   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5594                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5595   DrawPosition(FALSE, boards[currentMove]);
5596 }
5597
5598
5599 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5600 // All positions will have equal probability, but the current method will not provide a unique
5601 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5602 #define DARK 1
5603 #define LITE 2
5604 #define ANY 3
5605
5606 int squaresLeft[4];
5607 int piecesLeft[(int)BlackPawn];
5608 int seed, nrOfShuffles;
5609
5610 void
5611 GetPositionNumber ()
5612 {       // sets global variable seed
5613         int i;
5614
5615         seed = appData.defaultFrcPosition;
5616         if(seed < 0) { // randomize based on time for negative FRC position numbers
5617                 for(i=0; i<50; i++) seed += random();
5618                 seed = random() ^ random() >> 8 ^ random() << 8;
5619                 if(seed<0) seed = -seed;
5620         }
5621 }
5622
5623 int
5624 put (Board board, int pieceType, int rank, int n, int shade)
5625 // put the piece on the (n-1)-th empty squares of the given shade
5626 {
5627         int i;
5628
5629         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5630                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5631                         board[rank][i] = (ChessSquare) pieceType;
5632                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5633                         squaresLeft[ANY]--;
5634                         piecesLeft[pieceType]--;
5635                         return i;
5636                 }
5637         }
5638         return -1;
5639 }
5640
5641
5642 void
5643 AddOnePiece (Board board, int pieceType, int rank, int shade)
5644 // calculate where the next piece goes, (any empty square), and put it there
5645 {
5646         int i;
5647
5648         i = seed % squaresLeft[shade];
5649         nrOfShuffles *= squaresLeft[shade];
5650         seed /= squaresLeft[shade];
5651         put(board, pieceType, rank, i, shade);
5652 }
5653
5654 void
5655 AddTwoPieces (Board board, int pieceType, int rank)
5656 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5657 {
5658         int i, n=squaresLeft[ANY], j=n-1, k;
5659
5660         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5661         i = seed % k;  // pick one
5662         nrOfShuffles *= k;
5663         seed /= k;
5664         while(i >= j) i -= j--;
5665         j = n - 1 - j; i += j;
5666         put(board, pieceType, rank, j, ANY);
5667         put(board, pieceType, rank, i, ANY);
5668 }
5669
5670 void
5671 SetUpShuffle (Board board, int number)
5672 {
5673         int i, p, first=1;
5674
5675         GetPositionNumber(); nrOfShuffles = 1;
5676
5677         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5678         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5679         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5680
5681         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5682
5683         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5684             p = (int) board[0][i];
5685             if(p < (int) BlackPawn) piecesLeft[p] ++;
5686             board[0][i] = EmptySquare;
5687         }
5688
5689         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5690             // shuffles restricted to allow normal castling put KRR first
5691             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5692                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5693             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5694                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5695             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5696                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5697             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5698                 put(board, WhiteRook, 0, 0, ANY);
5699             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5700         }
5701
5702         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5703             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5704             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5705                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5706                 while(piecesLeft[p] >= 2) {
5707                     AddOnePiece(board, p, 0, LITE);
5708                     AddOnePiece(board, p, 0, DARK);
5709                 }
5710                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5711             }
5712
5713         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5714             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5715             // but we leave King and Rooks for last, to possibly obey FRC restriction
5716             if(p == (int)WhiteRook) continue;
5717             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5718             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5719         }
5720
5721         // now everything is placed, except perhaps King (Unicorn) and Rooks
5722
5723         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5724             // Last King gets castling rights
5725             while(piecesLeft[(int)WhiteUnicorn]) {
5726                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5727                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5728             }
5729
5730             while(piecesLeft[(int)WhiteKing]) {
5731                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5732                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5733             }
5734
5735
5736         } else {
5737             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5738             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5739         }
5740
5741         // Only Rooks can be left; simply place them all
5742         while(piecesLeft[(int)WhiteRook]) {
5743                 i = put(board, WhiteRook, 0, 0, ANY);
5744                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5745                         if(first) {
5746                                 first=0;
5747                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5748                         }
5749                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5750                 }
5751         }
5752         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5753             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5754         }
5755
5756         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5757 }
5758
5759 int
5760 SetCharTable (char *table, const char * map)
5761 /* [HGM] moved here from winboard.c because of its general usefulness */
5762 /*       Basically a safe strcpy that uses the last character as King */
5763 {
5764     int result = FALSE; int NrPieces;
5765
5766     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5767                     && NrPieces >= 12 && !(NrPieces&1)) {
5768         int i; /* [HGM] Accept even length from 12 to 34 */
5769
5770         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5771         for( i=0; i<NrPieces/2-1; i++ ) {
5772             table[i] = map[i];
5773             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5774         }
5775         table[(int) WhiteKing]  = map[NrPieces/2-1];
5776         table[(int) BlackKing]  = map[NrPieces-1];
5777
5778         result = TRUE;
5779     }
5780
5781     return result;
5782 }
5783
5784 void
5785 Prelude (Board board)
5786 {       // [HGM] superchess: random selection of exo-pieces
5787         int i, j, k; ChessSquare p;
5788         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5789
5790         GetPositionNumber(); // use FRC position number
5791
5792         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5793             SetCharTable(pieceToChar, appData.pieceToCharTable);
5794             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5795                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5796         }
5797
5798         j = seed%4;                 seed /= 4;
5799         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5800         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5801         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5802         j = seed%3 + (seed%3 >= j); seed /= 3;
5803         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5804         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5805         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5806         j = seed%3;                 seed /= 3;
5807         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5808         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5809         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5810         j = seed%2 + (seed%2 >= j); seed /= 2;
5811         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5812         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5813         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5814         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5815         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5816         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5817         put(board, exoPieces[0],    0, 0, ANY);
5818         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5819 }
5820
5821 void
5822 InitPosition (int redraw)
5823 {
5824     ChessSquare (* pieces)[BOARD_FILES];
5825     int i, j, pawnRow, overrule,
5826     oldx = gameInfo.boardWidth,
5827     oldy = gameInfo.boardHeight,
5828     oldh = gameInfo.holdingsWidth;
5829     static int oldv;
5830
5831     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5832
5833     /* [AS] Initialize pv info list [HGM] and game status */
5834     {
5835         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5836             pvInfoList[i].depth = 0;
5837             boards[i][EP_STATUS] = EP_NONE;
5838             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5839         }
5840
5841         initialRulePlies = 0; /* 50-move counter start */
5842
5843         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5844         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5845     }
5846
5847
5848     /* [HGM] logic here is completely changed. In stead of full positions */
5849     /* the initialized data only consist of the two backranks. The switch */
5850     /* selects which one we will use, which is than copied to the Board   */
5851     /* initialPosition, which for the rest is initialized by Pawns and    */
5852     /* empty squares. This initial position is then copied to boards[0],  */
5853     /* possibly after shuffling, so that it remains available.            */
5854
5855     gameInfo.holdingsWidth = 0; /* default board sizes */
5856     gameInfo.boardWidth    = 8;
5857     gameInfo.boardHeight   = 8;
5858     gameInfo.holdingsSize  = 0;
5859     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5860     for(i=0; i<BOARD_FILES-2; i++)
5861       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5862     initialPosition[EP_STATUS] = EP_NONE;
5863     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5864     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5865          SetCharTable(pieceNickName, appData.pieceNickNames);
5866     else SetCharTable(pieceNickName, "............");
5867     pieces = FIDEArray;
5868
5869     switch (gameInfo.variant) {
5870     case VariantFischeRandom:
5871       shuffleOpenings = TRUE;
5872     default:
5873       break;
5874     case VariantShatranj:
5875       pieces = ShatranjArray;
5876       nrCastlingRights = 0;
5877       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5878       break;
5879     case VariantMakruk:
5880       pieces = makrukArray;
5881       nrCastlingRights = 0;
5882       startedFromSetupPosition = TRUE;
5883       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5884       break;
5885     case VariantTwoKings:
5886       pieces = twoKingsArray;
5887       break;
5888     case VariantGrand:
5889       pieces = GrandArray;
5890       nrCastlingRights = 0;
5891       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5892       gameInfo.boardWidth = 10;
5893       gameInfo.boardHeight = 10;
5894       gameInfo.holdingsSize = 7;
5895       break;
5896     case VariantCapaRandom:
5897       shuffleOpenings = TRUE;
5898     case VariantCapablanca:
5899       pieces = CapablancaArray;
5900       gameInfo.boardWidth = 10;
5901       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5902       break;
5903     case VariantGothic:
5904       pieces = GothicArray;
5905       gameInfo.boardWidth = 10;
5906       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5907       break;
5908     case VariantSChess:
5909       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5910       gameInfo.holdingsSize = 7;
5911       for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
5912       break;
5913     case VariantJanus:
5914       pieces = JanusArray;
5915       gameInfo.boardWidth = 10;
5916       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5917       nrCastlingRights = 6;
5918         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5919         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5920         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5921         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5922         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5923         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5924       break;
5925     case VariantFalcon:
5926       pieces = FalconArray;
5927       gameInfo.boardWidth = 10;
5928       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5929       break;
5930     case VariantXiangqi:
5931       pieces = XiangqiArray;
5932       gameInfo.boardWidth  = 9;
5933       gameInfo.boardHeight = 10;
5934       nrCastlingRights = 0;
5935       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5936       break;
5937     case VariantShogi:
5938       pieces = ShogiArray;
5939       gameInfo.boardWidth  = 9;
5940       gameInfo.boardHeight = 9;
5941       gameInfo.holdingsSize = 7;
5942       nrCastlingRights = 0;
5943       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5944       break;
5945     case VariantCourier:
5946       pieces = CourierArray;
5947       gameInfo.boardWidth  = 12;
5948       nrCastlingRights = 0;
5949       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5950       break;
5951     case VariantKnightmate:
5952       pieces = KnightmateArray;
5953       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5954       break;
5955     case VariantSpartan:
5956       pieces = SpartanArray;
5957       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5958       break;
5959     case VariantFairy:
5960       pieces = fairyArray;
5961       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5962       break;
5963     case VariantGreat:
5964       pieces = GreatArray;
5965       gameInfo.boardWidth = 10;
5966       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5967       gameInfo.holdingsSize = 8;
5968       break;
5969     case VariantSuper:
5970       pieces = FIDEArray;
5971       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5972       gameInfo.holdingsSize = 8;
5973       startedFromSetupPosition = TRUE;
5974       break;
5975     case VariantCrazyhouse:
5976     case VariantBughouse:
5977       pieces = FIDEArray;
5978       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5979       gameInfo.holdingsSize = 5;
5980       break;
5981     case VariantWildCastle:
5982       pieces = FIDEArray;
5983       /* !!?shuffle with kings guaranteed to be on d or e file */
5984       shuffleOpenings = 1;
5985       break;
5986     case VariantNoCastle:
5987       pieces = FIDEArray;
5988       nrCastlingRights = 0;
5989       /* !!?unconstrained back-rank shuffle */
5990       shuffleOpenings = 1;
5991       break;
5992     }
5993
5994     overrule = 0;
5995     if(appData.NrFiles >= 0) {
5996         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5997         gameInfo.boardWidth = appData.NrFiles;
5998     }
5999     if(appData.NrRanks >= 0) {
6000         gameInfo.boardHeight = appData.NrRanks;
6001     }
6002     if(appData.holdingsSize >= 0) {
6003         i = appData.holdingsSize;
6004         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6005         gameInfo.holdingsSize = i;
6006     }
6007     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6008     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6009         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6010
6011     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6012     if(pawnRow < 1) pawnRow = 1;
6013     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) pawnRow = 2;
6014
6015     /* User pieceToChar list overrules defaults */
6016     if(appData.pieceToCharTable != NULL)
6017         SetCharTable(pieceToChar, appData.pieceToCharTable);
6018
6019     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6020
6021         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6022             s = (ChessSquare) 0; /* account holding counts in guard band */
6023         for( i=0; i<BOARD_HEIGHT; i++ )
6024             initialPosition[i][j] = s;
6025
6026         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6027         initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
6028         initialPosition[pawnRow][j] = WhitePawn;
6029         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6030         if(gameInfo.variant == VariantXiangqi) {
6031             if(j&1) {
6032                 initialPosition[pawnRow][j] =
6033                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6034                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6035                    initialPosition[2][j] = WhiteCannon;
6036                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6037                 }
6038             }
6039         }
6040         if(gameInfo.variant == VariantGrand) {
6041             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6042                initialPosition[0][j] = WhiteRook;
6043                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6044             }
6045         }
6046         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] =  pieces[1][j-gameInfo.holdingsWidth];
6047     }
6048     if( (gameInfo.variant == VariantShogi) && !overrule ) {
6049
6050             j=BOARD_LEFT+1;
6051             initialPosition[1][j] = WhiteBishop;
6052             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6053             j=BOARD_RGHT-2;
6054             initialPosition[1][j] = WhiteRook;
6055             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6056     }
6057
6058     if( nrCastlingRights == -1) {
6059         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6060         /*       This sets default castling rights from none to normal corners   */
6061         /* Variants with other castling rights must set them themselves above    */
6062         nrCastlingRights = 6;
6063
6064         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6065         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6066         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6067         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6068         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6069         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6070      }
6071
6072      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6073      if(gameInfo.variant == VariantGreat) { // promotion commoners
6074         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6075         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6076         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6077         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6078      }
6079      if( gameInfo.variant == VariantSChess ) {
6080       initialPosition[1][0] = BlackMarshall;
6081       initialPosition[2][0] = BlackAngel;
6082       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6083       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6084       initialPosition[1][1] = initialPosition[2][1] = 
6085       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6086      }
6087   if (appData.debugMode) {
6088     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6089   }
6090     if(shuffleOpenings) {
6091         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6092         startedFromSetupPosition = TRUE;
6093     }
6094     if(startedFromPositionFile) {
6095       /* [HGM] loadPos: use PositionFile for every new game */
6096       CopyBoard(initialPosition, filePosition);
6097       for(i=0; i<nrCastlingRights; i++)
6098           initialRights[i] = filePosition[CASTLING][i];
6099       startedFromSetupPosition = TRUE;
6100     }
6101
6102     CopyBoard(boards[0], initialPosition);
6103
6104     if(oldx != gameInfo.boardWidth ||
6105        oldy != gameInfo.boardHeight ||
6106        oldv != gameInfo.variant ||
6107        oldh != gameInfo.holdingsWidth
6108                                          )
6109             InitDrawingSizes(-2 ,0);
6110
6111     oldv = gameInfo.variant;
6112     if (redraw)
6113       DrawPosition(TRUE, boards[currentMove]);
6114 }
6115
6116 void
6117 SendBoard (ChessProgramState *cps, int moveNum)
6118 {
6119     char message[MSG_SIZ];
6120
6121     if (cps->useSetboard) {
6122       char* fen = PositionToFEN(moveNum, cps->fenOverride);
6123       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6124       SendToProgram(message, cps);
6125       free(fen);
6126
6127     } else {
6128       ChessSquare *bp;
6129       int i, j, left=0, right=BOARD_WIDTH;
6130       /* Kludge to set black to move, avoiding the troublesome and now
6131        * deprecated "black" command.
6132        */
6133       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6134         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6135
6136       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6137
6138       SendToProgram("edit\n", cps);
6139       SendToProgram("#\n", cps);
6140       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6141         bp = &boards[moveNum][i][left];
6142         for (j = left; j < right; j++, bp++) {
6143           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6144           if ((int) *bp < (int) BlackPawn) {
6145             if(j == BOARD_RGHT+1)
6146                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6147             else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6148             if(message[0] == '+' || message[0] == '~') {
6149               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6150                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6151                         AAA + j, ONE + i);
6152             }
6153             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6154                 message[1] = BOARD_RGHT   - 1 - j + '1';
6155                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6156             }
6157             SendToProgram(message, cps);
6158           }
6159         }
6160       }
6161
6162       SendToProgram("c\n", cps);
6163       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6164         bp = &boards[moveNum][i][left];
6165         for (j = left; j < right; j++, bp++) {
6166           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6167           if (((int) *bp != (int) EmptySquare)
6168               && ((int) *bp >= (int) BlackPawn)) {
6169             if(j == BOARD_LEFT-2)
6170                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6171             else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6172                     AAA + j, ONE + i);
6173             if(message[0] == '+' || message[0] == '~') {
6174               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6175                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6176                         AAA + j, ONE + i);
6177             }
6178             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6179                 message[1] = BOARD_RGHT   - 1 - j + '1';
6180                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6181             }
6182             SendToProgram(message, cps);
6183           }
6184         }
6185       }
6186
6187       SendToProgram(".\n", cps);
6188     }
6189     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6190 }
6191
6192 char exclusionHeader[MSG_SIZ];
6193 int exCnt, excludePtr;
6194 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6195 static Exclusion excluTab[200];
6196 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6197
6198 static void
6199 WriteMap (int s)
6200 {
6201     int j;
6202     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6203     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6204 }
6205
6206 static void
6207 ClearMap ()
6208 {
6209     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6210     excludePtr = 24; exCnt = 0;
6211     WriteMap(0);
6212 }
6213
6214 static void
6215 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6216 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6217     char buf[2*MOVE_LEN], *p;
6218     Exclusion *e = excluTab;
6219     int i;
6220     for(i=0; i<exCnt; i++)
6221         if(e[i].ff == fromX && e[i].fr == fromY &&
6222            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6223     if(i == exCnt) { // was not in exclude list; add it
6224         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6225         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6226             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6227             return; // abort
6228         }
6229         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6230         excludePtr++; e[i].mark = excludePtr++;
6231         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6232         exCnt++;
6233     }
6234     exclusionHeader[e[i].mark] = state;
6235 }
6236
6237 static int
6238 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6239 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6240     char buf[MSG_SIZ];
6241     int j, k;
6242     ChessMove moveType;
6243     if((signed char)promoChar == -1) { // kludge to indicate best move
6244         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6245             return 1; // if unparsable, abort
6246     }
6247     // update exclusion map (resolving toggle by consulting existing state)
6248     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6249     j = k%8; k >>= 3;
6250     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6251     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6252          excludeMap[k] |=   1<<j;
6253     else excludeMap[k] &= ~(1<<j);
6254     // update header
6255     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6256     // inform engine
6257     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6258     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6259     SendToBoth(buf);
6260     return (state == '+');
6261 }
6262
6263 static void
6264 ExcludeClick (int index)
6265 {
6266     int i, j;
6267     Exclusion *e = excluTab;
6268     if(index < 25) { // none, best or tail clicked
6269         if(index < 13) { // none: include all
6270             WriteMap(0); // clear map
6271             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6272             SendToBoth("include all\n"); // and inform engine
6273         } else if(index > 18) { // tail
6274             if(exclusionHeader[19] == '-') { // tail was excluded
6275                 SendToBoth("include all\n");
6276                 WriteMap(0); // clear map completely
6277                 // now re-exclude selected moves
6278                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6279                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6280             } else { // tail was included or in mixed state
6281                 SendToBoth("exclude all\n");
6282                 WriteMap(0xFF); // fill map completely
6283                 // now re-include selected moves
6284                 j = 0; // count them
6285                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6286                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6287                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6288             }
6289         } else { // best
6290             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6291         }
6292     } else {
6293         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6294             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6295             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6296             break;
6297         }
6298     }
6299 }
6300
6301 ChessSquare
6302 DefaultPromoChoice (int white)
6303 {
6304     ChessSquare result;
6305     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
6306         result = WhiteFerz; // no choice
6307     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6308         result= WhiteKing; // in Suicide Q is the last thing we want
6309     else if(gameInfo.variant == VariantSpartan)
6310         result = white ? WhiteQueen : WhiteAngel;
6311     else result = WhiteQueen;
6312     if(!white) result = WHITE_TO_BLACK result;
6313     return result;
6314 }
6315
6316 static int autoQueen; // [HGM] oneclick
6317
6318 int
6319 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6320 {
6321     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6322     /* [HGM] add Shogi promotions */
6323     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6324     ChessSquare piece;
6325     ChessMove moveType;
6326     Boolean premove;
6327
6328     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6329     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6330
6331     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6332       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6333         return FALSE;
6334
6335     piece = boards[currentMove][fromY][fromX];
6336     if(gameInfo.variant == VariantShogi) {
6337         promotionZoneSize = BOARD_HEIGHT/3;
6338         highestPromotingPiece = (int)WhiteFerz;
6339     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6340         promotionZoneSize = 3;
6341     }
6342
6343     // Treat Lance as Pawn when it is not representing Amazon
6344     if(gameInfo.variant != VariantSuper) {
6345         if(piece == WhiteLance) piece = WhitePawn; else
6346         if(piece == BlackLance) piece = BlackPawn;
6347     }
6348
6349     // next weed out all moves that do not touch the promotion zone at all
6350     if((int)piece >= BlackPawn) {
6351         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6352              return FALSE;
6353         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6354     } else {
6355         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6356            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6357     }
6358
6359     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6360
6361     // weed out mandatory Shogi promotions
6362     if(gameInfo.variant == VariantShogi) {
6363         if(piece >= BlackPawn) {
6364             if(toY == 0 && piece == BlackPawn ||
6365                toY == 0 && piece == BlackQueen ||
6366                toY <= 1 && piece == BlackKnight) {
6367                 *promoChoice = '+';
6368                 return FALSE;
6369             }
6370         } else {
6371             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6372                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6373                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6374                 *promoChoice = '+';
6375                 return FALSE;
6376             }
6377         }
6378     }
6379
6380     // weed out obviously illegal Pawn moves
6381     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6382         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6383         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6384         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6385         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6386         // note we are not allowed to test for valid (non-)capture, due to premove
6387     }
6388
6389     // we either have a choice what to promote to, or (in Shogi) whether to promote
6390     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6391         *promoChoice = PieceToChar(BlackFerz);  // no choice
6392         return FALSE;
6393     }
6394     // no sense asking what we must promote to if it is going to explode...
6395     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6396         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6397         return FALSE;
6398     }
6399     // give caller the default choice even if we will not make it
6400     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6401     if(gameInfo.variant == VariantShogi) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6402     if(        sweepSelect && gameInfo.variant != VariantGreat
6403                            && gameInfo.variant != VariantGrand
6404                            && gameInfo.variant != VariantSuper) return FALSE;
6405     if(autoQueen) return FALSE; // predetermined
6406
6407     // suppress promotion popup on illegal moves that are not premoves
6408     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6409               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6410     if(appData.testLegality && !premove) {
6411         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6412                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6413         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6414             return FALSE;
6415     }
6416
6417     return TRUE;
6418 }
6419
6420 int
6421 InPalace (int row, int column)
6422 {   /* [HGM] for Xiangqi */
6423     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6424          column < (BOARD_WIDTH + 4)/2 &&
6425          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6426     return FALSE;
6427 }
6428
6429 int
6430 PieceForSquare (int x, int y)
6431 {
6432   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6433      return -1;
6434   else
6435      return boards[currentMove][y][x];
6436 }
6437
6438 int
6439 OKToStartUserMove (int x, int y)
6440 {
6441     ChessSquare from_piece;
6442     int white_piece;
6443
6444     if (matchMode) return FALSE;
6445     if (gameMode == EditPosition) return TRUE;
6446
6447     if (x >= 0 && y >= 0)
6448       from_piece = boards[currentMove][y][x];
6449     else
6450       from_piece = EmptySquare;
6451
6452     if (from_piece == EmptySquare) return FALSE;
6453
6454     white_piece = (int)from_piece >= (int)WhitePawn &&
6455       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6456
6457     switch (gameMode) {
6458       case AnalyzeFile:
6459       case TwoMachinesPlay:
6460       case EndOfGame:
6461         return FALSE;
6462
6463       case IcsObserving:
6464       case IcsIdle:
6465         return FALSE;
6466
6467       case MachinePlaysWhite:
6468       case IcsPlayingBlack:
6469         if (appData.zippyPlay) return FALSE;
6470         if (white_piece) {
6471             DisplayMoveError(_("You are playing Black"));
6472             return FALSE;
6473         }
6474         break;
6475
6476       case MachinePlaysBlack:
6477       case IcsPlayingWhite:
6478         if (appData.zippyPlay) return FALSE;
6479         if (!white_piece) {
6480             DisplayMoveError(_("You are playing White"));
6481             return FALSE;
6482         }
6483         break;
6484
6485       case PlayFromGameFile:
6486             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6487       case EditGame:
6488         if (!white_piece && WhiteOnMove(currentMove)) {
6489             DisplayMoveError(_("It is White's turn"));
6490             return FALSE;
6491         }
6492         if (white_piece && !WhiteOnMove(currentMove)) {
6493             DisplayMoveError(_("It is Black's turn"));
6494             return FALSE;
6495         }
6496         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6497             /* Editing correspondence game history */
6498             /* Could disallow this or prompt for confirmation */
6499             cmailOldMove = -1;
6500         }
6501         break;
6502
6503       case BeginningOfGame:
6504         if (appData.icsActive) return FALSE;
6505         if (!appData.noChessProgram) {
6506             if (!white_piece) {
6507                 DisplayMoveError(_("You are playing White"));
6508                 return FALSE;
6509             }
6510         }
6511         break;
6512
6513       case Training:
6514         if (!white_piece && WhiteOnMove(currentMove)) {
6515             DisplayMoveError(_("It is White's turn"));
6516             return FALSE;
6517         }
6518         if (white_piece && !WhiteOnMove(currentMove)) {
6519             DisplayMoveError(_("It is Black's turn"));
6520             return FALSE;
6521         }
6522         break;
6523
6524       default:
6525       case IcsExamining:
6526         break;
6527     }
6528     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6529         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6530         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6531         && gameMode != AnalyzeFile && gameMode != Training) {
6532         DisplayMoveError(_("Displayed position is not current"));
6533         return FALSE;
6534     }
6535     return TRUE;
6536 }
6537
6538 Boolean
6539 OnlyMove (int *x, int *y, Boolean captures) 
6540 {
6541     DisambiguateClosure cl;
6542     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6543     switch(gameMode) {
6544       case MachinePlaysBlack:
6545       case IcsPlayingWhite:
6546       case BeginningOfGame:
6547         if(!WhiteOnMove(currentMove)) return FALSE;
6548         break;
6549       case MachinePlaysWhite:
6550       case IcsPlayingBlack:
6551         if(WhiteOnMove(currentMove)) return FALSE;
6552         break;
6553       case EditGame:
6554         break;
6555       default:
6556         return FALSE;
6557     }
6558     cl.pieceIn = EmptySquare;
6559     cl.rfIn = *y;
6560     cl.ffIn = *x;
6561     cl.rtIn = -1;
6562     cl.ftIn = -1;
6563     cl.promoCharIn = NULLCHAR;
6564     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6565     if( cl.kind == NormalMove ||
6566         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6567         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6568         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6569       fromX = cl.ff;
6570       fromY = cl.rf;
6571       *x = cl.ft;
6572       *y = cl.rt;
6573       return TRUE;
6574     }
6575     if(cl.kind != ImpossibleMove) return FALSE;
6576     cl.pieceIn = EmptySquare;
6577     cl.rfIn = -1;
6578     cl.ffIn = -1;
6579     cl.rtIn = *y;
6580     cl.ftIn = *x;
6581     cl.promoCharIn = NULLCHAR;
6582     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6583     if( cl.kind == NormalMove ||
6584         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6585         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6586         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6587       fromX = cl.ff;
6588       fromY = cl.rf;
6589       *x = cl.ft;
6590       *y = cl.rt;
6591       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6592       return TRUE;
6593     }
6594     return FALSE;
6595 }
6596
6597 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6598 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6599 int lastLoadGameUseList = FALSE;
6600 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6601 ChessMove lastLoadGameStart = EndOfFile;
6602 int doubleClick;
6603
6604 void
6605 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6606 {
6607     ChessMove moveType;
6608     ChessSquare pup;
6609     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6610
6611     /* Check if the user is playing in turn.  This is complicated because we
6612        let the user "pick up" a piece before it is his turn.  So the piece he
6613        tried to pick up may have been captured by the time he puts it down!
6614        Therefore we use the color the user is supposed to be playing in this
6615        test, not the color of the piece that is currently on the starting
6616        square---except in EditGame mode, where the user is playing both
6617        sides; fortunately there the capture race can't happen.  (It can
6618        now happen in IcsExamining mode, but that's just too bad.  The user
6619        will get a somewhat confusing message in that case.)
6620        */
6621
6622     switch (gameMode) {
6623       case AnalyzeFile:
6624       case TwoMachinesPlay:
6625       case EndOfGame:
6626       case IcsObserving:
6627       case IcsIdle:
6628         /* We switched into a game mode where moves are not accepted,
6629            perhaps while the mouse button was down. */
6630         return;
6631
6632       case MachinePlaysWhite:
6633         /* User is moving for Black */
6634         if (WhiteOnMove(currentMove)) {
6635             DisplayMoveError(_("It is White's turn"));
6636             return;
6637         }
6638         break;
6639
6640       case MachinePlaysBlack:
6641         /* User is moving for White */
6642         if (!WhiteOnMove(currentMove)) {
6643             DisplayMoveError(_("It is Black's turn"));
6644             return;
6645         }
6646         break;
6647
6648       case PlayFromGameFile:
6649             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6650       case EditGame:
6651       case IcsExamining:
6652       case BeginningOfGame:
6653       case AnalyzeMode:
6654       case Training:
6655         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6656         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6657             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6658             /* User is moving for Black */
6659             if (WhiteOnMove(currentMove)) {
6660                 DisplayMoveError(_("It is White's turn"));
6661                 return;
6662             }
6663         } else {
6664             /* User is moving for White */
6665             if (!WhiteOnMove(currentMove)) {
6666                 DisplayMoveError(_("It is Black's turn"));
6667                 return;
6668             }
6669         }
6670         break;
6671
6672       case IcsPlayingBlack:
6673         /* User is moving for Black */
6674         if (WhiteOnMove(currentMove)) {
6675             if (!appData.premove) {
6676                 DisplayMoveError(_("It is White's turn"));
6677             } else if (toX >= 0 && toY >= 0) {
6678                 premoveToX = toX;
6679                 premoveToY = toY;
6680                 premoveFromX = fromX;
6681                 premoveFromY = fromY;
6682                 premovePromoChar = promoChar;
6683                 gotPremove = 1;
6684                 if (appData.debugMode)
6685                     fprintf(debugFP, "Got premove: fromX %d,"
6686                             "fromY %d, toX %d, toY %d\n",
6687                             fromX, fromY, toX, toY);
6688             }
6689             return;
6690         }
6691         break;
6692
6693       case IcsPlayingWhite:
6694         /* User is moving for White */
6695         if (!WhiteOnMove(currentMove)) {
6696             if (!appData.premove) {
6697                 DisplayMoveError(_("It is Black's turn"));
6698             } else if (toX >= 0 && toY >= 0) {
6699                 premoveToX = toX;
6700                 premoveToY = toY;
6701                 premoveFromX = fromX;
6702                 premoveFromY = fromY;
6703                 premovePromoChar = promoChar;
6704                 gotPremove = 1;
6705                 if (appData.debugMode)
6706                     fprintf(debugFP, "Got premove: fromX %d,"
6707                             "fromY %d, toX %d, toY %d\n",
6708                             fromX, fromY, toX, toY);
6709             }
6710             return;
6711         }
6712         break;
6713
6714       default:
6715         break;
6716
6717       case EditPosition:
6718         /* EditPosition, empty square, or different color piece;
6719            click-click move is possible */
6720         if (toX == -2 || toY == -2) {
6721             boards[0][fromY][fromX] = EmptySquare;
6722             DrawPosition(FALSE, boards[currentMove]);
6723             return;
6724         } else if (toX >= 0 && toY >= 0) {
6725             boards[0][toY][toX] = boards[0][fromY][fromX];
6726             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6727                 if(boards[0][fromY][0] != EmptySquare) {
6728                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6729                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6730                 }
6731             } else
6732             if(fromX == BOARD_RGHT+1) {
6733                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6734                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6735                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6736                 }
6737             } else
6738             boards[0][fromY][fromX] = gatingPiece;
6739             DrawPosition(FALSE, boards[currentMove]);
6740             return;
6741         }
6742         return;
6743     }
6744
6745     if(toX < 0 || toY < 0) return;
6746     pup = boards[currentMove][toY][toX];
6747
6748     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6749     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6750          if( pup != EmptySquare ) return;
6751          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6752            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6753                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6754            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6755            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6756            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6757            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6758          fromY = DROP_RANK;
6759     }
6760
6761     /* [HGM] always test for legality, to get promotion info */
6762     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6763                                          fromY, fromX, toY, toX, promoChar);
6764
6765     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6766
6767     /* [HGM] but possibly ignore an IllegalMove result */
6768     if (appData.testLegality) {
6769         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6770             DisplayMoveError(_("Illegal move"));
6771             return;
6772         }
6773     }
6774
6775     if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
6776         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
6777              ClearPremoveHighlights(); // was included
6778         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
6779         return;
6780     }
6781
6782     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6783 }
6784
6785 /* Common tail of UserMoveEvent and DropMenuEvent */
6786 int
6787 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
6788 {
6789     char *bookHit = 0;
6790
6791     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6792         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6793         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6794         if(WhiteOnMove(currentMove)) {
6795             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6796         } else {
6797             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6798         }
6799     }
6800
6801     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6802        move type in caller when we know the move is a legal promotion */
6803     if(moveType == NormalMove && promoChar)
6804         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6805
6806     /* [HGM] <popupFix> The following if has been moved here from
6807        UserMoveEvent(). Because it seemed to belong here (why not allow
6808        piece drops in training games?), and because it can only be
6809        performed after it is known to what we promote. */
6810     if (gameMode == Training) {
6811       /* compare the move played on the board to the next move in the
6812        * game. If they match, display the move and the opponent's response.
6813        * If they don't match, display an error message.
6814        */
6815       int saveAnimate;
6816       Board testBoard;
6817       CopyBoard(testBoard, boards[currentMove]);
6818       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6819
6820       if (CompareBoards(testBoard, boards[currentMove+1])) {
6821         ForwardInner(currentMove+1);
6822
6823         /* Autoplay the opponent's response.
6824          * if appData.animate was TRUE when Training mode was entered,
6825          * the response will be animated.
6826          */
6827         saveAnimate = appData.animate;
6828         appData.animate = animateTraining;
6829         ForwardInner(currentMove+1);
6830         appData.animate = saveAnimate;
6831
6832         /* check for the end of the game */
6833         if (currentMove >= forwardMostMove) {
6834           gameMode = PlayFromGameFile;
6835           ModeHighlight();
6836           SetTrainingModeOff();
6837           DisplayInformation(_("End of game"));
6838         }
6839       } else {
6840         DisplayError(_("Incorrect move"), 0);
6841       }
6842       return 1;
6843     }
6844
6845   /* Ok, now we know that the move is good, so we can kill
6846      the previous line in Analysis Mode */
6847   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6848                                 && currentMove < forwardMostMove) {
6849     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6850     else forwardMostMove = currentMove;
6851   }
6852
6853   ClearMap();
6854
6855   /* If we need the chess program but it's dead, restart it */
6856   ResurrectChessProgram();
6857
6858   /* A user move restarts a paused game*/
6859   if (pausing)
6860     PauseEvent();
6861
6862   thinkOutput[0] = NULLCHAR;
6863
6864   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6865
6866   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6867     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6868     return 1;
6869   }
6870
6871   if (gameMode == BeginningOfGame) {
6872     if (appData.noChessProgram) {
6873       gameMode = EditGame;
6874       SetGameInfo();
6875     } else {
6876       char buf[MSG_SIZ];
6877       gameMode = MachinePlaysBlack;
6878       StartClocks();
6879       SetGameInfo();
6880       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
6881       DisplayTitle(buf);
6882       if (first.sendName) {
6883         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6884         SendToProgram(buf, &first);
6885       }
6886       StartClocks();
6887     }
6888     ModeHighlight();
6889   }
6890
6891   /* Relay move to ICS or chess engine */
6892   if (appData.icsActive) {
6893     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6894         gameMode == IcsExamining) {
6895       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6896         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6897         SendToICS("draw ");
6898         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6899       }
6900       // also send plain move, in case ICS does not understand atomic claims
6901       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6902       ics_user_moved = 1;
6903     }
6904   } else {
6905     if (first.sendTime && (gameMode == BeginningOfGame ||
6906                            gameMode == MachinePlaysWhite ||
6907                            gameMode == MachinePlaysBlack)) {
6908       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6909     }
6910     if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
6911          // [HGM] book: if program might be playing, let it use book
6912         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6913         first.maybeThinking = TRUE;
6914     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
6915         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
6916         SendBoard(&first, currentMove+1);
6917         if(second.analyzing) {
6918             if(!second.useSetboard) SendToProgram("undo\n", &second);
6919             SendBoard(&second, currentMove+1);
6920         }
6921     } else {
6922         SendMoveToProgram(forwardMostMove-1, &first);
6923         if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
6924     }
6925     if (currentMove == cmailOldMove + 1) {
6926       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6927     }
6928   }
6929
6930   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6931
6932   switch (gameMode) {
6933   case EditGame:
6934     if(appData.testLegality)
6935     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6936     case MT_NONE:
6937     case MT_CHECK:
6938       break;
6939     case MT_CHECKMATE:
6940     case MT_STAINMATE:
6941       if (WhiteOnMove(currentMove)) {
6942         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6943       } else {
6944         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6945       }
6946       break;
6947     case MT_STALEMATE:
6948       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6949       break;
6950     }
6951     break;
6952
6953   case MachinePlaysBlack:
6954   case MachinePlaysWhite:
6955     /* disable certain menu options while machine is thinking */
6956     SetMachineThinkingEnables();
6957     break;
6958
6959   default:
6960     break;
6961   }
6962
6963   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6964   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6965
6966   if(bookHit) { // [HGM] book: simulate book reply
6967         static char bookMove[MSG_SIZ]; // a bit generous?
6968
6969         programStats.nodes = programStats.depth = programStats.time =
6970         programStats.score = programStats.got_only_move = 0;
6971         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6972
6973         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6974         strcat(bookMove, bookHit);
6975         HandleMachineMove(bookMove, &first);
6976   }
6977   return 1;
6978 }
6979
6980 void
6981 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
6982 {
6983     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6984     Markers *m = (Markers *) closure;
6985     if(rf == fromY && ff == fromX)
6986         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6987                          || kind == WhiteCapturesEnPassant
6988                          || kind == BlackCapturesEnPassant);
6989     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6990 }
6991
6992 void
6993 MarkTargetSquares (int clear)
6994 {
6995   int x, y;
6996   if(clear) // no reason to ever suppress clearing
6997     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6998   if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
6999      !appData.testLegality || gameMode == EditPosition) return;
7000   if(!clear) {
7001     int capt = 0;
7002     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7003     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7004       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7005       if(capt)
7006       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7007     }
7008   }
7009   DrawPosition(FALSE, NULL);
7010 }
7011
7012 int
7013 Explode (Board board, int fromX, int fromY, int toX, int toY)
7014 {
7015     if(gameInfo.variant == VariantAtomic &&
7016        (board[toY][toX] != EmptySquare ||                     // capture?
7017         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
7018                          board[fromY][fromX] == BlackPawn   )
7019       )) {
7020         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7021         return TRUE;
7022     }
7023     return FALSE;
7024 }
7025
7026 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7027
7028 int
7029 CanPromote (ChessSquare piece, int y)
7030 {
7031         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7032         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7033         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
7034            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7035            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7036                                                   gameInfo.variant == VariantMakruk) return FALSE;
7037         return (piece == BlackPawn && y == 1 ||
7038                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
7039                 piece == BlackLance && y == 1 ||
7040                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
7041 }
7042
7043 void
7044 LeftClick (ClickType clickType, int xPix, int yPix)
7045 {
7046     int x, y;
7047     Boolean saveAnimate;
7048     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7049     char promoChoice = NULLCHAR;
7050     ChessSquare piece;
7051     static TimeMark lastClickTime, prevClickTime;
7052
7053     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7054
7055     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7056
7057     if (clickType == Press) ErrorPopDown();
7058
7059     x = EventToSquare(xPix, BOARD_WIDTH);
7060     y = EventToSquare(yPix, BOARD_HEIGHT);
7061     if (!flipView && y >= 0) {
7062         y = BOARD_HEIGHT - 1 - y;
7063     }
7064     if (flipView && x >= 0) {
7065         x = BOARD_WIDTH - 1 - x;
7066     }
7067
7068     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7069         defaultPromoChoice = promoSweep;
7070         promoSweep = EmptySquare;   // terminate sweep
7071         promoDefaultAltered = TRUE;
7072         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7073     }
7074
7075     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7076         if(clickType == Release) return; // ignore upclick of click-click destination
7077         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7078         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7079         if(gameInfo.holdingsWidth &&
7080                 (WhiteOnMove(currentMove)
7081                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7082                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7083             // click in right holdings, for determining promotion piece
7084             ChessSquare p = boards[currentMove][y][x];
7085             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7086             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7087             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7088                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7089                 fromX = fromY = -1;
7090                 return;
7091             }
7092         }
7093         DrawPosition(FALSE, boards[currentMove]);
7094         return;
7095     }
7096
7097     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7098     if(clickType == Press
7099             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7100               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7101               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7102         return;
7103
7104     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7105         // could be static click on premove from-square: abort premove
7106         gotPremove = 0;
7107         ClearPremoveHighlights();
7108     }
7109
7110     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7111         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7112
7113     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7114         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7115                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7116         defaultPromoChoice = DefaultPromoChoice(side);
7117     }
7118
7119     autoQueen = appData.alwaysPromoteToQueen;
7120
7121     if (fromX == -1) {
7122       int originalY = y;
7123       gatingPiece = EmptySquare;
7124       if (clickType != Press) {
7125         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7126             DragPieceEnd(xPix, yPix); dragging = 0;
7127             DrawPosition(FALSE, NULL);
7128         }
7129         return;
7130       }
7131       doubleClick = FALSE;
7132       if(gameMode == AnalyzeMode && pausing && first.excludeMoves) { // use pause state to exclude moves
7133         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7134       }
7135       fromX = x; fromY = y; toX = toY = -1;
7136       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7137          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7138          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7139             /* First square */
7140             if (OKToStartUserMove(fromX, fromY)) {
7141                 second = 0;
7142                 MarkTargetSquares(0);
7143                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7144                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7145                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7146                     promoSweep = defaultPromoChoice;
7147                     selectFlag = 0; lastX = xPix; lastY = yPix;
7148                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7149                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7150                 }
7151                 if (appData.highlightDragging) {
7152                     SetHighlights(fromX, fromY, -1, -1);
7153                 } else {
7154                     ClearHighlights();
7155                 }
7156             } else fromX = fromY = -1;
7157             return;
7158         }
7159     }
7160
7161     /* fromX != -1 */
7162     if (clickType == Press && gameMode != EditPosition) {
7163         ChessSquare fromP;
7164         ChessSquare toP;
7165         int frc;
7166
7167         // ignore off-board to clicks
7168         if(y < 0 || x < 0) return;
7169
7170         /* Check if clicking again on the same color piece */
7171         fromP = boards[currentMove][fromY][fromX];
7172         toP = boards[currentMove][y][x];
7173         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
7174         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
7175              WhitePawn <= toP && toP <= WhiteKing &&
7176              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7177              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7178             (BlackPawn <= fromP && fromP <= BlackKing &&
7179              BlackPawn <= toP && toP <= BlackKing &&
7180              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7181              !(fromP == BlackKing && toP == BlackRook && frc))) {
7182             /* Clicked again on same color piece -- changed his mind */
7183             second = (x == fromX && y == fromY);
7184             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7185                 second = FALSE; // first double-click rather than scond click
7186                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7187             }
7188             promoDefaultAltered = FALSE;
7189             MarkTargetSquares(1);
7190            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
7191             if (appData.highlightDragging) {
7192                 SetHighlights(x, y, -1, -1);
7193             } else {
7194                 ClearHighlights();
7195             }
7196             if (OKToStartUserMove(x, y)) {
7197                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7198                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7199                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7200                  gatingPiece = boards[currentMove][fromY][fromX];
7201                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7202                 fromX = x;
7203                 fromY = y; dragging = 1;
7204                 MarkTargetSquares(0);
7205                 DragPieceBegin(xPix, yPix, FALSE);
7206                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7207                     promoSweep = defaultPromoChoice;
7208                     selectFlag = 0; lastX = xPix; lastY = yPix;
7209                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7210                 }
7211             }
7212            }
7213            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7214            second = FALSE; 
7215         }
7216         // ignore clicks on holdings
7217         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7218     }
7219
7220     if (clickType == Release && x == fromX && y == fromY) {
7221         DragPieceEnd(xPix, yPix); dragging = 0;
7222         if(clearFlag) {
7223             // a deferred attempt to click-click move an empty square on top of a piece
7224             boards[currentMove][y][x] = EmptySquare;
7225             ClearHighlights();
7226             DrawPosition(FALSE, boards[currentMove]);
7227             fromX = fromY = -1; clearFlag = 0;
7228             return;
7229         }
7230         if (appData.animateDragging) {
7231             /* Undo animation damage if any */
7232             DrawPosition(FALSE, NULL);
7233         }
7234         if (second || sweepSelecting) {
7235             /* Second up/down in same square; just abort move */
7236             if(sweepSelecting) DrawPosition(FALSE, boards[currentMove]);
7237             second = sweepSelecting = 0;
7238             fromX = fromY = -1;
7239             gatingPiece = EmptySquare;
7240             ClearHighlights();
7241             gotPremove = 0;
7242             ClearPremoveHighlights();
7243         } else {
7244             /* First upclick in same square; start click-click mode */
7245             SetHighlights(x, y, -1, -1);
7246         }
7247         return;
7248     }
7249
7250     clearFlag = 0;
7251
7252     /* we now have a different from- and (possibly off-board) to-square */
7253     /* Completed move */
7254     if(!sweepSelecting) {
7255         toX = x;
7256         toY = y;
7257     } else sweepSelecting = 0; // this must be the up-click corresponding to the down-click that started the sweep
7258
7259     saveAnimate = appData.animate;
7260     if (clickType == Press) {
7261         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7262             // must be Edit Position mode with empty-square selected
7263             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7264             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7265             return;
7266         }
7267         if(HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7268           if(appData.sweepSelect) {
7269             ChessSquare piece = boards[currentMove][fromY][fromX];
7270             promoSweep = defaultPromoChoice;
7271             if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
7272             selectFlag = 0; lastX = xPix; lastY = yPix;
7273             Sweep(0); // Pawn that is going to promote: preview promotion piece
7274             sweepSelecting = 1;
7275             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7276             MarkTargetSquares(1);
7277           }
7278           return; // promo popup appears on up-click
7279         }
7280         /* Finish clickclick move */
7281         if (appData.animate || appData.highlightLastMove) {
7282             SetHighlights(fromX, fromY, toX, toY);
7283         } else {
7284             ClearHighlights();
7285         }
7286     } else {
7287 #if 0
7288 // [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
7289         /* Finish drag move */
7290         if (appData.highlightLastMove) {
7291             SetHighlights(fromX, fromY, toX, toY);
7292         } else {
7293             ClearHighlights();
7294         }
7295 #endif
7296         DragPieceEnd(xPix, yPix); dragging = 0;
7297         /* Don't animate move and drag both */
7298         appData.animate = FALSE;
7299     }
7300
7301     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7302     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7303         ChessSquare piece = boards[currentMove][fromY][fromX];
7304         if(gameMode == EditPosition && piece != EmptySquare &&
7305            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7306             int n;
7307
7308             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7309                 n = PieceToNumber(piece - (int)BlackPawn);
7310                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7311                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7312                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7313             } else
7314             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7315                 n = PieceToNumber(piece);
7316                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7317                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7318                 boards[currentMove][n][BOARD_WIDTH-2]++;
7319             }
7320             boards[currentMove][fromY][fromX] = EmptySquare;
7321         }
7322         ClearHighlights();
7323         fromX = fromY = -1;
7324         MarkTargetSquares(1);
7325         DrawPosition(TRUE, boards[currentMove]);
7326         return;
7327     }
7328
7329     // off-board moves should not be highlighted
7330     if(x < 0 || y < 0) ClearHighlights();
7331
7332     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7333
7334     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7335         SetHighlights(fromX, fromY, toX, toY);
7336         MarkTargetSquares(1);
7337         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7338             // [HGM] super: promotion to captured piece selected from holdings
7339             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7340             promotionChoice = TRUE;
7341             // kludge follows to temporarily execute move on display, without promoting yet
7342             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7343             boards[currentMove][toY][toX] = p;
7344             DrawPosition(FALSE, boards[currentMove]);
7345             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7346             boards[currentMove][toY][toX] = q;
7347             DisplayMessage("Click in holdings to choose piece", "");
7348             return;
7349         }
7350         PromotionPopUp();
7351     } else {
7352         int oldMove = currentMove;
7353         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7354         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7355         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7356         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7357            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7358             DrawPosition(TRUE, boards[currentMove]);
7359         MarkTargetSquares(1);
7360         fromX = fromY = -1;
7361     }
7362     appData.animate = saveAnimate;
7363     if (appData.animate || appData.animateDragging) {
7364         /* Undo animation damage if needed */
7365         DrawPosition(FALSE, NULL);
7366     }
7367 }
7368
7369 int
7370 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7371 {   // front-end-free part taken out of PieceMenuPopup
7372     int whichMenu; int xSqr, ySqr;
7373
7374     if(seekGraphUp) { // [HGM] seekgraph
7375         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7376         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7377         return -2;
7378     }
7379
7380     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7381          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7382         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7383         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7384         if(action == Press)   {
7385             originalFlip = flipView;
7386             flipView = !flipView; // temporarily flip board to see game from partners perspective
7387             DrawPosition(TRUE, partnerBoard);
7388             DisplayMessage(partnerStatus, "");
7389             partnerUp = TRUE;
7390         } else if(action == Release) {
7391             flipView = originalFlip;
7392             DrawPosition(TRUE, boards[currentMove]);
7393             partnerUp = FALSE;
7394         }
7395         return -2;
7396     }
7397
7398     xSqr = EventToSquare(x, BOARD_WIDTH);
7399     ySqr = EventToSquare(y, BOARD_HEIGHT);
7400     if (action == Release) {
7401         if(pieceSweep != EmptySquare) {
7402             EditPositionMenuEvent(pieceSweep, toX, toY);
7403             pieceSweep = EmptySquare;
7404         } else UnLoadPV(); // [HGM] pv
7405     }
7406     if (action != Press) return -2; // return code to be ignored
7407     switch (gameMode) {
7408       case IcsExamining:
7409         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7410       case EditPosition:
7411         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7412         if (xSqr < 0 || ySqr < 0) return -1;
7413         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7414         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7415         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7416         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7417         NextPiece(0);
7418         return 2; // grab
7419       case IcsObserving:
7420         if(!appData.icsEngineAnalyze) return -1;
7421       case IcsPlayingWhite:
7422       case IcsPlayingBlack:
7423         if(!appData.zippyPlay) goto noZip;
7424       case AnalyzeMode:
7425       case AnalyzeFile:
7426       case MachinePlaysWhite:
7427       case MachinePlaysBlack:
7428       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7429         if (!appData.dropMenu) {
7430           LoadPV(x, y);
7431           return 2; // flag front-end to grab mouse events
7432         }
7433         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7434            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7435       case EditGame:
7436       noZip:
7437         if (xSqr < 0 || ySqr < 0) return -1;
7438         if (!appData.dropMenu || appData.testLegality &&
7439             gameInfo.variant != VariantBughouse &&
7440             gameInfo.variant != VariantCrazyhouse) return -1;
7441         whichMenu = 1; // drop menu
7442         break;
7443       default:
7444         return -1;
7445     }
7446
7447     if (((*fromX = xSqr) < 0) ||
7448         ((*fromY = ySqr) < 0)) {
7449         *fromX = *fromY = -1;
7450         return -1;
7451     }
7452     if (flipView)
7453       *fromX = BOARD_WIDTH - 1 - *fromX;
7454     else
7455       *fromY = BOARD_HEIGHT - 1 - *fromY;
7456
7457     return whichMenu;
7458 }
7459
7460 void
7461 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7462 {
7463 //    char * hint = lastHint;
7464     FrontEndProgramStats stats;
7465
7466     stats.which = cps == &first ? 0 : 1;
7467     stats.depth = cpstats->depth;
7468     stats.nodes = cpstats->nodes;
7469     stats.score = cpstats->score;
7470     stats.time = cpstats->time;
7471     stats.pv = cpstats->movelist;
7472     stats.hint = lastHint;
7473     stats.an_move_index = 0;
7474     stats.an_move_count = 0;
7475
7476     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7477         stats.hint = cpstats->move_name;
7478         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7479         stats.an_move_count = cpstats->nr_moves;
7480     }
7481
7482     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
7483
7484     SetProgramStats( &stats );
7485 }
7486
7487 void
7488 ClearEngineOutputPane (int which)
7489 {
7490     static FrontEndProgramStats dummyStats;
7491     dummyStats.which = which;
7492     dummyStats.pv = "#";
7493     SetProgramStats( &dummyStats );
7494 }
7495
7496 #define MAXPLAYERS 500
7497
7498 char *
7499 TourneyStandings (int display)
7500 {
7501     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7502     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7503     char result, *p, *names[MAXPLAYERS];
7504
7505     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7506         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7507     names[0] = p = strdup(appData.participants);
7508     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7509
7510     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7511
7512     while(result = appData.results[nr]) {
7513         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7514         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7515         wScore = bScore = 0;
7516         switch(result) {
7517           case '+': wScore = 2; break;
7518           case '-': bScore = 2; break;
7519           case '=': wScore = bScore = 1; break;
7520           case ' ':
7521           case '*': return strdup("busy"); // tourney not finished
7522         }
7523         score[w] += wScore;
7524         score[b] += bScore;
7525         games[w]++;
7526         games[b]++;
7527         nr++;
7528     }
7529     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7530     for(w=0; w<nPlayers; w++) {
7531         bScore = -1;
7532         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7533         ranking[w] = b; points[w] = bScore; score[b] = -2;
7534     }
7535     p = malloc(nPlayers*34+1);
7536     for(w=0; w<nPlayers && w<display; w++)
7537         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7538     free(names[0]);
7539     return p;
7540 }
7541
7542 void
7543 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7544 {       // count all piece types
7545         int p, f, r;
7546         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7547         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7548         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7549                 p = board[r][f];
7550                 pCnt[p]++;
7551                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7552                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7553                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7554                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7555                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7556                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7557         }
7558 }
7559
7560 int
7561 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7562 {
7563         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7564         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7565
7566         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7567         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7568         if(myPawns == 2 && nMine == 3) // KPP
7569             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7570         if(myPawns == 1 && nMine == 2) // KP
7571             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7572         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7573             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7574         if(myPawns) return FALSE;
7575         if(pCnt[WhiteRook+side])
7576             return pCnt[BlackRook-side] ||
7577                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7578                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7579                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7580         if(pCnt[WhiteCannon+side]) {
7581             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7582             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7583         }
7584         if(pCnt[WhiteKnight+side])
7585             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7586         return FALSE;
7587 }
7588
7589 int
7590 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7591 {
7592         VariantClass v = gameInfo.variant;
7593
7594         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7595         if(v == VariantShatranj) return TRUE; // always winnable through baring
7596         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7597         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7598
7599         if(v == VariantXiangqi) {
7600                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7601
7602                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7603                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7604                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7605                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7606                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7607                 if(stale) // we have at least one last-rank P plus perhaps C
7608                     return majors // KPKX
7609                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7610                 else // KCA*E*
7611                     return pCnt[WhiteFerz+side] // KCAK
7612                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7613                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7614                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7615
7616         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7617                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7618
7619                 if(nMine == 1) return FALSE; // bare King
7620                 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
7621                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7622                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7623                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7624                 if(pCnt[WhiteKnight+side])
7625                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7626                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7627                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7628                 if(nBishops)
7629                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7630                 if(pCnt[WhiteAlfil+side])
7631                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7632                 if(pCnt[WhiteWazir+side])
7633                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7634         }
7635
7636         return TRUE;
7637 }
7638
7639 int
7640 CompareWithRights (Board b1, Board b2)
7641 {
7642     int rights = 0;
7643     if(!CompareBoards(b1, b2)) return FALSE;
7644     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7645     /* compare castling rights */
7646     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7647            rights++; /* King lost rights, while rook still had them */
7648     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7649         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7650            rights++; /* but at least one rook lost them */
7651     }
7652     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7653            rights++;
7654     if( b1[CASTLING][5] != NoRights ) {
7655         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7656            rights++;
7657     }
7658     return rights == 0;
7659 }
7660
7661 int
7662 Adjudicate (ChessProgramState *cps)
7663 {       // [HGM] some adjudications useful with buggy engines
7664         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7665         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7666         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7667         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7668         int k, count = 0; static int bare = 1;
7669         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7670         Boolean canAdjudicate = !appData.icsActive;
7671
7672         // most tests only when we understand the game, i.e. legality-checking on
7673             if( appData.testLegality )
7674             {   /* [HGM] Some more adjudications for obstinate engines */
7675                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7676                 static int moveCount = 6;
7677                 ChessMove result;
7678                 char *reason = NULL;
7679
7680                 /* Count what is on board. */
7681                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7682
7683                 /* Some material-based adjudications that have to be made before stalemate test */
7684                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7685                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7686                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7687                      if(canAdjudicate && appData.checkMates) {
7688                          if(engineOpponent)
7689                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7690                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7691                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7692                          return 1;
7693                      }
7694                 }
7695
7696                 /* Bare King in Shatranj (loses) or Losers (wins) */
7697                 if( nrW == 1 || nrB == 1) {
7698                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7699                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7700                      if(canAdjudicate && appData.checkMates) {
7701                          if(engineOpponent)
7702                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7703                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7704                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7705                          return 1;
7706                      }
7707                   } else
7708                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7709                   {    /* bare King */
7710                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7711                         if(canAdjudicate && appData.checkMates) {
7712                             /* but only adjudicate if adjudication enabled */
7713                             if(engineOpponent)
7714                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7715                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7716                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7717                             return 1;
7718                         }
7719                   }
7720                 } else bare = 1;
7721
7722
7723             // don't wait for engine to announce game end if we can judge ourselves
7724             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7725               case MT_CHECK:
7726                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7727                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7728                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7729                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7730                             checkCnt++;
7731                         if(checkCnt >= 2) {
7732                             reason = "Xboard adjudication: 3rd check";
7733                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7734                             break;
7735                         }
7736                     }
7737                 }
7738               case MT_NONE:
7739               default:
7740                 break;
7741               case MT_STALEMATE:
7742               case MT_STAINMATE:
7743                 reason = "Xboard adjudication: Stalemate";
7744                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7745                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7746                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7747                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7748                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7749                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7750                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7751                                                                         EP_CHECKMATE : EP_WINS);
7752                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7753                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7754                 }
7755                 break;
7756               case MT_CHECKMATE:
7757                 reason = "Xboard adjudication: Checkmate";
7758                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7759                 break;
7760             }
7761
7762                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7763                     case EP_STALEMATE:
7764                         result = GameIsDrawn; break;
7765                     case EP_CHECKMATE:
7766                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7767                     case EP_WINS:
7768                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7769                     default:
7770                         result = EndOfFile;
7771                 }
7772                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7773                     if(engineOpponent)
7774                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7775                     GameEnds( result, reason, GE_XBOARD );
7776                     return 1;
7777                 }
7778
7779                 /* Next absolutely insufficient mating material. */
7780                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7781                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7782                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7783
7784                      /* always flag draws, for judging claims */
7785                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7786
7787                      if(canAdjudicate && appData.materialDraws) {
7788                          /* but only adjudicate them if adjudication enabled */
7789                          if(engineOpponent) {
7790                            SendToProgram("force\n", engineOpponent); // suppress reply
7791                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7792                          }
7793                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7794                          return 1;
7795                      }
7796                 }
7797
7798                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7799                 if(gameInfo.variant == VariantXiangqi ?
7800                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7801                  : nrW + nrB == 4 &&
7802                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7803                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7804                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7805                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7806                    ) ) {
7807                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7808                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7809                           if(engineOpponent) {
7810                             SendToProgram("force\n", engineOpponent); // suppress reply
7811                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7812                           }
7813                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7814                           return 1;
7815                      }
7816                 } else moveCount = 6;
7817             }
7818
7819         // Repetition draws and 50-move rule can be applied independently of legality testing
7820
7821                 /* Check for rep-draws */
7822                 count = 0;
7823                 for(k = forwardMostMove-2;
7824                     k>=backwardMostMove && k>=forwardMostMove-100 &&
7825                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7826                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7827                     k-=2)
7828                 {   int rights=0;
7829                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7830                         /* compare castling rights */
7831                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7832                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7833                                 rights++; /* King lost rights, while rook still had them */
7834                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7835                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7836                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7837                                    rights++; /* but at least one rook lost them */
7838                         }
7839                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7840                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7841                                 rights++;
7842                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7843                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7844                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7845                                    rights++;
7846                         }
7847                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7848                             && appData.drawRepeats > 1) {
7849                              /* adjudicate after user-specified nr of repeats */
7850                              int result = GameIsDrawn;
7851                              char *details = "XBoard adjudication: repetition draw";
7852                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7853                                 // [HGM] xiangqi: check for forbidden perpetuals
7854                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7855                                 for(m=forwardMostMove; m>k; m-=2) {
7856                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7857                                         ourPerpetual = 0; // the current mover did not always check
7858                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7859                                         hisPerpetual = 0; // the opponent did not always check
7860                                 }
7861                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7862                                                                         ourPerpetual, hisPerpetual);
7863                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7864                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7865                                     details = "Xboard adjudication: perpetual checking";
7866                                 } else
7867                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7868                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7869                                 } else
7870                                 // Now check for perpetual chases
7871                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7872                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7873                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7874                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7875                                         static char resdet[MSG_SIZ];
7876                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7877                                         details = resdet;
7878                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
7879                                     } else
7880                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7881                                         break; // Abort repetition-checking loop.
7882                                 }
7883                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7884                              }
7885                              if(engineOpponent) {
7886                                SendToProgram("force\n", engineOpponent); // suppress reply
7887                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7888                              }
7889                              GameEnds( result, details, GE_XBOARD );
7890                              return 1;
7891                         }
7892                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7893                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7894                     }
7895                 }
7896
7897                 /* Now we test for 50-move draws. Determine ply count */
7898                 count = forwardMostMove;
7899                 /* look for last irreversble move */
7900                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7901                     count--;
7902                 /* if we hit starting position, add initial plies */
7903                 if( count == backwardMostMove )
7904                     count -= initialRulePlies;
7905                 count = forwardMostMove - count;
7906                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7907                         // adjust reversible move counter for checks in Xiangqi
7908                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7909                         if(i < backwardMostMove) i = backwardMostMove;
7910                         while(i <= forwardMostMove) {
7911                                 lastCheck = inCheck; // check evasion does not count
7912                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7913                                 if(inCheck || lastCheck) count--; // check does not count
7914                                 i++;
7915                         }
7916                 }
7917                 if( count >= 100)
7918                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7919                          /* this is used to judge if draw claims are legal */
7920                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7921                          if(engineOpponent) {
7922                            SendToProgram("force\n", engineOpponent); // suppress reply
7923                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7924                          }
7925                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7926                          return 1;
7927                 }
7928
7929                 /* if draw offer is pending, treat it as a draw claim
7930                  * when draw condition present, to allow engines a way to
7931                  * claim draws before making their move to avoid a race
7932                  * condition occurring after their move
7933                  */
7934                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7935                          char *p = NULL;
7936                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7937                              p = "Draw claim: 50-move rule";
7938                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7939                              p = "Draw claim: 3-fold repetition";
7940                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7941                              p = "Draw claim: insufficient mating material";
7942                          if( p != NULL && canAdjudicate) {
7943                              if(engineOpponent) {
7944                                SendToProgram("force\n", engineOpponent); // suppress reply
7945                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7946                              }
7947                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7948                              return 1;
7949                          }
7950                 }
7951
7952                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
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, "Xboard adjudication: long game", GE_XBOARD );
7958                     return 1;
7959                 }
7960         return 0;
7961 }
7962
7963 char *
7964 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
7965 {   // [HGM] book: this routine intercepts moves to simulate book replies
7966     char *bookHit = NULL;
7967
7968     //first determine if the incoming move brings opponent into his book
7969     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7970         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7971     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7972     if(bookHit != NULL && !cps->bookSuspend) {
7973         // make sure opponent is not going to reply after receiving move to book position
7974         SendToProgram("force\n", cps);
7975         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7976     }
7977     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7978     // now arrange restart after book miss
7979     if(bookHit) {
7980         // after a book hit we never send 'go', and the code after the call to this routine
7981         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7982         char buf[MSG_SIZ], *move = bookHit;
7983         if(cps->useSAN) {
7984             int fromX, fromY, toX, toY;
7985             char promoChar;
7986             ChessMove moveType;
7987             move = buf + 30;
7988             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
7989                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7990                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7991                                     PosFlags(forwardMostMove),
7992                                     fromY, fromX, toY, toX, promoChar, move);
7993             } else {
7994                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
7995                 bookHit = NULL;
7996             }
7997         }
7998         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
7999         SendToProgram(buf, cps);
8000         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8001     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8002         SendToProgram("go\n", cps);
8003         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8004     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8005         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8006             SendToProgram("go\n", cps);
8007         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8008     }
8009     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8010 }
8011
8012 int
8013 LoadError (char *errmess, ChessProgramState *cps)
8014 {   // unloads engine and switches back to -ncp mode if it was first
8015     if(cps->initDone) return FALSE;
8016     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8017     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8018     cps->pr = NoProc; 
8019     if(cps == &first) {
8020         appData.noChessProgram = TRUE;
8021         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8022         gameMode = BeginningOfGame; ModeHighlight();
8023         SetNCPMode();
8024     }
8025     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8026     DisplayMessage("", ""); // erase waiting message
8027     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8028     return TRUE;
8029 }
8030
8031 char *savedMessage;
8032 ChessProgramState *savedState;
8033 void
8034 DeferredBookMove (void)
8035 {
8036         if(savedState->lastPing != savedState->lastPong)
8037                     ScheduleDelayedEvent(DeferredBookMove, 10);
8038         else
8039         HandleMachineMove(savedMessage, savedState);
8040 }
8041
8042 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8043 static ChessProgramState *stalledEngine;
8044 static char stashedInputMove[MSG_SIZ];
8045
8046 void
8047 HandleMachineMove (char *message, ChessProgramState *cps)
8048 {
8049     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8050     char realname[MSG_SIZ];
8051     int fromX, fromY, toX, toY;
8052     ChessMove moveType;
8053     char promoChar;
8054     char *p, *pv=buf1;
8055     int machineWhite, oldError;
8056     char *bookHit;
8057
8058     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8059         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8060         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8061             DisplayError(_("Invalid pairing from pairing engine"), 0);
8062             return;
8063         }
8064         pairingReceived = 1;
8065         NextMatchGame();
8066         return; // Skim the pairing messages here.
8067     }
8068
8069     oldError = cps->userError; cps->userError = 0;
8070
8071 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8072     /*
8073      * Kludge to ignore BEL characters
8074      */
8075     while (*message == '\007') message++;
8076
8077     /*
8078      * [HGM] engine debug message: ignore lines starting with '#' character
8079      */
8080     if(cps->debug && *message == '#') return;
8081
8082     /*
8083      * Look for book output
8084      */
8085     if (cps == &first && bookRequested) {
8086         if (message[0] == '\t' || message[0] == ' ') {
8087             /* Part of the book output is here; append it */
8088             strcat(bookOutput, message);
8089             strcat(bookOutput, "  \n");
8090             return;
8091         } else if (bookOutput[0] != NULLCHAR) {
8092             /* All of book output has arrived; display it */
8093             char *p = bookOutput;
8094             while (*p != NULLCHAR) {
8095                 if (*p == '\t') *p = ' ';
8096                 p++;
8097             }
8098             DisplayInformation(bookOutput);
8099             bookRequested = FALSE;
8100             /* Fall through to parse the current output */
8101         }
8102     }
8103
8104     /*
8105      * Look for machine move.
8106      */
8107     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8108         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8109     {
8110         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8111             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8112             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8113             stalledEngine = cps;
8114             if(appData.ponderNextMove) { // bring both engines out of ponder
8115                 SendToProgram("easy\n", &first);
8116                 if(gameMode == TwoMachinesPlay) SendToProgram("easy\n", &second);
8117             }
8118             StopClocks();
8119             return;
8120         }
8121
8122         /* This method is only useful on engines that support ping */
8123         if (cps->lastPing != cps->lastPong) {
8124           if (gameMode == BeginningOfGame) {
8125             /* Extra move from before last new; ignore */
8126             if (appData.debugMode) {
8127                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8128             }
8129           } else {
8130             if (appData.debugMode) {
8131                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8132                         cps->which, gameMode);
8133             }
8134
8135             SendToProgram("undo\n", cps);
8136           }
8137           return;
8138         }
8139
8140         switch (gameMode) {
8141           case BeginningOfGame:
8142             /* Extra move from before last reset; ignore */
8143             if (appData.debugMode) {
8144                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8145             }
8146             return;
8147
8148           case EndOfGame:
8149           case IcsIdle:
8150           default:
8151             /* Extra move after we tried to stop.  The mode test is
8152                not a reliable way of detecting this problem, but it's
8153                the best we can do on engines that don't support ping.
8154             */
8155             if (appData.debugMode) {
8156                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8157                         cps->which, gameMode);
8158             }
8159             SendToProgram("undo\n", cps);
8160             return;
8161
8162           case MachinePlaysWhite:
8163           case IcsPlayingWhite:
8164             machineWhite = TRUE;
8165             break;
8166
8167           case MachinePlaysBlack:
8168           case IcsPlayingBlack:
8169             machineWhite = FALSE;
8170             break;
8171
8172           case TwoMachinesPlay:
8173             machineWhite = (cps->twoMachinesColor[0] == 'w');
8174             break;
8175         }
8176         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8177             if (appData.debugMode) {
8178                 fprintf(debugFP,
8179                         "Ignoring move out of turn by %s, gameMode %d"
8180                         ", forwardMost %d\n",
8181                         cps->which, gameMode, forwardMostMove);
8182             }
8183             return;
8184         }
8185
8186         if(cps->alphaRank) AlphaRank(machineMove, 4);
8187         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8188                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8189             /* Machine move could not be parsed; ignore it. */
8190           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8191                     machineMove, _(cps->which));
8192             DisplayError(buf1, 0);
8193             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
8194                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
8195             if (gameMode == TwoMachinesPlay) {
8196               GameEnds(machineWhite ? BlackWins : WhiteWins,
8197                        buf1, GE_XBOARD);
8198             }
8199             return;
8200         }
8201
8202         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8203         /* So we have to redo legality test with true e.p. status here,  */
8204         /* to make sure an illegal e.p. capture does not slip through,   */
8205         /* to cause a forfeit on a justified illegal-move complaint      */
8206         /* of the opponent.                                              */
8207         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8208            ChessMove moveType;
8209            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8210                              fromY, fromX, toY, toX, promoChar);
8211             if(moveType == IllegalMove) {
8212               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8213                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8214                 GameEnds(machineWhite ? BlackWins : WhiteWins,
8215                            buf1, GE_XBOARD);
8216                 return;
8217            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
8218            /* [HGM] Kludge to handle engines that send FRC-style castling
8219               when they shouldn't (like TSCP-Gothic) */
8220            switch(moveType) {
8221              case WhiteASideCastleFR:
8222              case BlackASideCastleFR:
8223                toX+=2;
8224                currentMoveString[2]++;
8225                break;
8226              case WhiteHSideCastleFR:
8227              case BlackHSideCastleFR:
8228                toX--;
8229                currentMoveString[2]--;
8230                break;
8231              default: ; // nothing to do, but suppresses warning of pedantic compilers
8232            }
8233         }
8234         hintRequested = FALSE;
8235         lastHint[0] = NULLCHAR;
8236         bookRequested = FALSE;
8237         /* Program may be pondering now */
8238         cps->maybeThinking = TRUE;
8239         if (cps->sendTime == 2) cps->sendTime = 1;
8240         if (cps->offeredDraw) cps->offeredDraw--;
8241
8242         /* [AS] Save move info*/
8243         pvInfoList[ forwardMostMove ].score = programStats.score;
8244         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8245         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8246
8247         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8248
8249         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8250         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8251             int count = 0;
8252
8253             while( count < adjudicateLossPlies ) {
8254                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8255
8256                 if( count & 1 ) {
8257                     score = -score; /* Flip score for winning side */
8258                 }
8259
8260                 if( score > adjudicateLossThreshold ) {
8261                     break;
8262                 }
8263
8264                 count++;
8265             }
8266
8267             if( count >= adjudicateLossPlies ) {
8268                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8269
8270                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8271                     "Xboard adjudication",
8272                     GE_XBOARD );
8273
8274                 return;
8275             }
8276         }
8277
8278         if(Adjudicate(cps)) {
8279             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8280             return; // [HGM] adjudicate: for all automatic game ends
8281         }
8282
8283 #if ZIPPY
8284         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8285             first.initDone) {
8286           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8287                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8288                 SendToICS("draw ");
8289                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8290           }
8291           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8292           ics_user_moved = 1;
8293           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8294                 char buf[3*MSG_SIZ];
8295
8296                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8297                         programStats.score / 100.,
8298                         programStats.depth,
8299                         programStats.time / 100.,
8300                         (unsigned int)programStats.nodes,
8301                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8302                         programStats.movelist);
8303                 SendToICS(buf);
8304 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8305           }
8306         }
8307 #endif
8308
8309         /* [AS] Clear stats for next move */
8310         ClearProgramStats();
8311         thinkOutput[0] = NULLCHAR;
8312         hiddenThinkOutputState = 0;
8313
8314         bookHit = NULL;
8315         if (gameMode == TwoMachinesPlay) {
8316             /* [HGM] relaying draw offers moved to after reception of move */
8317             /* and interpreting offer as claim if it brings draw condition */
8318             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8319                 SendToProgram("draw\n", cps->other);
8320             }
8321             if (cps->other->sendTime) {
8322                 SendTimeRemaining(cps->other,
8323                                   cps->other->twoMachinesColor[0] == 'w');
8324             }
8325             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8326             if (firstMove && !bookHit) {
8327                 firstMove = FALSE;
8328                 if (cps->other->useColors) {
8329                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8330                 }
8331                 SendToProgram("go\n", cps->other);
8332             }
8333             cps->other->maybeThinking = TRUE;
8334         }
8335
8336         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8337
8338         if (!pausing && appData.ringBellAfterMoves) {
8339             RingBell();
8340         }
8341
8342         /*
8343          * Reenable menu items that were disabled while
8344          * machine was thinking
8345          */
8346         if (gameMode != TwoMachinesPlay)
8347             SetUserThinkingEnables();
8348
8349         // [HGM] book: after book hit opponent has received move and is now in force mode
8350         // force the book reply into it, and then fake that it outputted this move by jumping
8351         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8352         if(bookHit) {
8353                 static char bookMove[MSG_SIZ]; // a bit generous?
8354
8355                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8356                 strcat(bookMove, bookHit);
8357                 message = bookMove;
8358                 cps = cps->other;
8359                 programStats.nodes = programStats.depth = programStats.time =
8360                 programStats.score = programStats.got_only_move = 0;
8361                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8362
8363                 if(cps->lastPing != cps->lastPong) {
8364                     savedMessage = message; // args for deferred call
8365                     savedState = cps;
8366                     ScheduleDelayedEvent(DeferredBookMove, 10);
8367                     return;
8368                 }
8369                 goto FakeBookMove;
8370         }
8371
8372         return;
8373     }
8374
8375     /* Set special modes for chess engines.  Later something general
8376      *  could be added here; for now there is just one kludge feature,
8377      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8378      *  when "xboard" is given as an interactive command.
8379      */
8380     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8381         cps->useSigint = FALSE;
8382         cps->useSigterm = FALSE;
8383     }
8384     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8385       ParseFeatures(message+8, cps);
8386       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8387     }
8388
8389     if ((!appData.testLegality || gameInfo.variant == VariantFairy) && 
8390                                         !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
8391       int dummy, s=6; char buf[MSG_SIZ];
8392       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8393       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8394       if(startedFromSetupPosition) return;
8395       ParseFEN(boards[0], &dummy, message+s);
8396       DrawPosition(TRUE, boards[0]);
8397       startedFromSetupPosition = TRUE;
8398       return;
8399     }
8400     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8401      * want this, I was asked to put it in, and obliged.
8402      */
8403     if (!strncmp(message, "setboard ", 9)) {
8404         Board initial_position;
8405
8406         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8407
8408         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8409             DisplayError(_("Bad FEN received from engine"), 0);
8410             return ;
8411         } else {
8412            Reset(TRUE, FALSE);
8413            CopyBoard(boards[0], initial_position);
8414            initialRulePlies = FENrulePlies;
8415            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8416            else gameMode = MachinePlaysBlack;
8417            DrawPosition(FALSE, boards[currentMove]);
8418         }
8419         return;
8420     }
8421
8422     /*
8423      * Look for communication commands
8424      */
8425     if (!strncmp(message, "telluser ", 9)) {
8426         if(message[9] == '\\' && message[10] == '\\')
8427             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8428         PlayTellSound();
8429         DisplayNote(message + 9);
8430         return;
8431     }
8432     if (!strncmp(message, "tellusererror ", 14)) {
8433         cps->userError = 1;
8434         if(message[14] == '\\' && message[15] == '\\')
8435             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8436         PlayTellSound();
8437         DisplayError(message + 14, 0);
8438         return;
8439     }
8440     if (!strncmp(message, "tellopponent ", 13)) {
8441       if (appData.icsActive) {
8442         if (loggedOn) {
8443           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8444           SendToICS(buf1);
8445         }
8446       } else {
8447         DisplayNote(message + 13);
8448       }
8449       return;
8450     }
8451     if (!strncmp(message, "tellothers ", 11)) {
8452       if (appData.icsActive) {
8453         if (loggedOn) {
8454           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8455           SendToICS(buf1);
8456         }
8457       }
8458       return;
8459     }
8460     if (!strncmp(message, "tellall ", 8)) {
8461       if (appData.icsActive) {
8462         if (loggedOn) {
8463           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8464           SendToICS(buf1);
8465         }
8466       } else {
8467         DisplayNote(message + 8);
8468       }
8469       return;
8470     }
8471     if (strncmp(message, "warning", 7) == 0) {
8472         /* Undocumented feature, use tellusererror in new code */
8473         DisplayError(message, 0);
8474         return;
8475     }
8476     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8477         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8478         strcat(realname, " query");
8479         AskQuestion(realname, buf2, buf1, cps->pr);
8480         return;
8481     }
8482     /* Commands from the engine directly to ICS.  We don't allow these to be
8483      *  sent until we are logged on. Crafty kibitzes have been known to
8484      *  interfere with the login process.
8485      */
8486     if (loggedOn) {
8487         if (!strncmp(message, "tellics ", 8)) {
8488             SendToICS(message + 8);
8489             SendToICS("\n");
8490             return;
8491         }
8492         if (!strncmp(message, "tellicsnoalias ", 15)) {
8493             SendToICS(ics_prefix);
8494             SendToICS(message + 15);
8495             SendToICS("\n");
8496             return;
8497         }
8498         /* The following are for backward compatibility only */
8499         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8500             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8501             SendToICS(ics_prefix);
8502             SendToICS(message);
8503             SendToICS("\n");
8504             return;
8505         }
8506     }
8507     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8508         return;
8509     }
8510     /*
8511      * If the move is illegal, cancel it and redraw the board.
8512      * Also deal with other error cases.  Matching is rather loose
8513      * here to accommodate engines written before the spec.
8514      */
8515     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8516         strncmp(message, "Error", 5) == 0) {
8517         if (StrStr(message, "name") ||
8518             StrStr(message, "rating") || StrStr(message, "?") ||
8519             StrStr(message, "result") || StrStr(message, "board") ||
8520             StrStr(message, "bk") || StrStr(message, "computer") ||
8521             StrStr(message, "variant") || StrStr(message, "hint") ||
8522             StrStr(message, "random") || StrStr(message, "depth") ||
8523             StrStr(message, "accepted")) {
8524             return;
8525         }
8526         if (StrStr(message, "protover")) {
8527           /* Program is responding to input, so it's apparently done
8528              initializing, and this error message indicates it is
8529              protocol version 1.  So we don't need to wait any longer
8530              for it to initialize and send feature commands. */
8531           FeatureDone(cps, 1);
8532           cps->protocolVersion = 1;
8533           return;
8534         }
8535         cps->maybeThinking = FALSE;
8536
8537         if (StrStr(message, "draw")) {
8538             /* Program doesn't have "draw" command */
8539             cps->sendDrawOffers = 0;
8540             return;
8541         }
8542         if (cps->sendTime != 1 &&
8543             (StrStr(message, "time") || StrStr(message, "otim"))) {
8544           /* Program apparently doesn't have "time" or "otim" command */
8545           cps->sendTime = 0;
8546           return;
8547         }
8548         if (StrStr(message, "analyze")) {
8549             cps->analysisSupport = FALSE;
8550             cps->analyzing = FALSE;
8551 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8552             EditGameEvent(); // [HGM] try to preserve loaded game
8553             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8554             DisplayError(buf2, 0);
8555             return;
8556         }
8557         if (StrStr(message, "(no matching move)st")) {
8558           /* Special kludge for GNU Chess 4 only */
8559           cps->stKludge = TRUE;
8560           SendTimeControl(cps, movesPerSession, timeControl,
8561                           timeIncrement, appData.searchDepth,
8562                           searchTime);
8563           return;
8564         }
8565         if (StrStr(message, "(no matching move)sd")) {
8566           /* Special kludge for GNU Chess 4 only */
8567           cps->sdKludge = TRUE;
8568           SendTimeControl(cps, movesPerSession, timeControl,
8569                           timeIncrement, appData.searchDepth,
8570                           searchTime);
8571           return;
8572         }
8573         if (!StrStr(message, "llegal")) {
8574             return;
8575         }
8576         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8577             gameMode == IcsIdle) return;
8578         if (forwardMostMove <= backwardMostMove) return;
8579         if (pausing) PauseEvent();
8580       if(appData.forceIllegal) {
8581             // [HGM] illegal: machine refused move; force position after move into it
8582           SendToProgram("force\n", cps);
8583           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8584                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8585                 // when black is to move, while there might be nothing on a2 or black
8586                 // might already have the move. So send the board as if white has the move.
8587                 // But first we must change the stm of the engine, as it refused the last move
8588                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8589                 if(WhiteOnMove(forwardMostMove)) {
8590                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8591                     SendBoard(cps, forwardMostMove); // kludgeless board
8592                 } else {
8593                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8594                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8595                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8596                 }
8597           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8598             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8599                  gameMode == TwoMachinesPlay)
8600               SendToProgram("go\n", cps);
8601             return;
8602       } else
8603         if (gameMode == PlayFromGameFile) {
8604             /* Stop reading this game file */
8605             gameMode = EditGame;
8606             ModeHighlight();
8607         }
8608         /* [HGM] illegal-move claim should forfeit game when Xboard */
8609         /* only passes fully legal moves                            */
8610         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8611             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8612                                 "False illegal-move claim", GE_XBOARD );
8613             return; // do not take back move we tested as valid
8614         }
8615         currentMove = forwardMostMove-1;
8616         DisplayMove(currentMove-1); /* before DisplayMoveError */
8617         SwitchClocks(forwardMostMove-1); // [HGM] race
8618         DisplayBothClocks();
8619         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8620                 parseList[currentMove], _(cps->which));
8621         DisplayMoveError(buf1);
8622         DrawPosition(FALSE, boards[currentMove]);
8623
8624         SetUserThinkingEnables();
8625         return;
8626     }
8627     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8628         /* Program has a broken "time" command that
8629            outputs a string not ending in newline.
8630            Don't use it. */
8631         cps->sendTime = 0;
8632     }
8633
8634     /*
8635      * If chess program startup fails, exit with an error message.
8636      * Attempts to recover here are futile. [HGM] Well, we try anyway
8637      */
8638     if ((StrStr(message, "unknown host") != NULL)
8639         || (StrStr(message, "No remote directory") != NULL)
8640         || (StrStr(message, "not found") != NULL)
8641         || (StrStr(message, "No such file") != NULL)
8642         || (StrStr(message, "can't alloc") != NULL)
8643         || (StrStr(message, "Permission denied") != NULL)) {
8644
8645         cps->maybeThinking = FALSE;
8646         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8647                 _(cps->which), cps->program, cps->host, message);
8648         RemoveInputSource(cps->isr);
8649         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8650             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
8651             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
8652         }
8653         return;
8654     }
8655
8656     /*
8657      * Look for hint output
8658      */
8659     if (sscanf(message, "Hint: %s", buf1) == 1) {
8660         if (cps == &first && hintRequested) {
8661             hintRequested = FALSE;
8662             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8663                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8664                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8665                                     PosFlags(forwardMostMove),
8666                                     fromY, fromX, toY, toX, promoChar, buf1);
8667                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8668                 DisplayInformation(buf2);
8669             } else {
8670                 /* Hint move could not be parsed!? */
8671               snprintf(buf2, sizeof(buf2),
8672                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8673                         buf1, _(cps->which));
8674                 DisplayError(buf2, 0);
8675             }
8676         } else {
8677           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8678         }
8679         return;
8680     }
8681
8682     /*
8683      * Ignore other messages if game is not in progress
8684      */
8685     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8686         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8687
8688     /*
8689      * look for win, lose, draw, or draw offer
8690      */
8691     if (strncmp(message, "1-0", 3) == 0) {
8692         char *p, *q, *r = "";
8693         p = strchr(message, '{');
8694         if (p) {
8695             q = strchr(p, '}');
8696             if (q) {
8697                 *q = NULLCHAR;
8698                 r = p + 1;
8699             }
8700         }
8701         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8702         return;
8703     } else if (strncmp(message, "0-1", 3) == 0) {
8704         char *p, *q, *r = "";
8705         p = strchr(message, '{');
8706         if (p) {
8707             q = strchr(p, '}');
8708             if (q) {
8709                 *q = NULLCHAR;
8710                 r = p + 1;
8711             }
8712         }
8713         /* Kludge for Arasan 4.1 bug */
8714         if (strcmp(r, "Black resigns") == 0) {
8715             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8716             return;
8717         }
8718         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8719         return;
8720     } else if (strncmp(message, "1/2", 3) == 0) {
8721         char *p, *q, *r = "";
8722         p = strchr(message, '{');
8723         if (p) {
8724             q = strchr(p, '}');
8725             if (q) {
8726                 *q = NULLCHAR;
8727                 r = p + 1;
8728             }
8729         }
8730
8731         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8732         return;
8733
8734     } else if (strncmp(message, "White resign", 12) == 0) {
8735         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8736         return;
8737     } else if (strncmp(message, "Black resign", 12) == 0) {
8738         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8739         return;
8740     } else if (strncmp(message, "White matches", 13) == 0 ||
8741                strncmp(message, "Black matches", 13) == 0   ) {
8742         /* [HGM] ignore GNUShogi noises */
8743         return;
8744     } else if (strncmp(message, "White", 5) == 0 &&
8745                message[5] != '(' &&
8746                StrStr(message, "Black") == NULL) {
8747         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8748         return;
8749     } else if (strncmp(message, "Black", 5) == 0 &&
8750                message[5] != '(') {
8751         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8752         return;
8753     } else if (strcmp(message, "resign") == 0 ||
8754                strcmp(message, "computer resigns") == 0) {
8755         switch (gameMode) {
8756           case MachinePlaysBlack:
8757           case IcsPlayingBlack:
8758             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8759             break;
8760           case MachinePlaysWhite:
8761           case IcsPlayingWhite:
8762             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8763             break;
8764           case TwoMachinesPlay:
8765             if (cps->twoMachinesColor[0] == 'w')
8766               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8767             else
8768               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8769             break;
8770           default:
8771             /* can't happen */
8772             break;
8773         }
8774         return;
8775     } else if (strncmp(message, "opponent mates", 14) == 0) {
8776         switch (gameMode) {
8777           case MachinePlaysBlack:
8778           case IcsPlayingBlack:
8779             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8780             break;
8781           case MachinePlaysWhite:
8782           case IcsPlayingWhite:
8783             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8784             break;
8785           case TwoMachinesPlay:
8786             if (cps->twoMachinesColor[0] == 'w')
8787               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8788             else
8789               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8790             break;
8791           default:
8792             /* can't happen */
8793             break;
8794         }
8795         return;
8796     } else if (strncmp(message, "computer mates", 14) == 0) {
8797         switch (gameMode) {
8798           case MachinePlaysBlack:
8799           case IcsPlayingBlack:
8800             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8801             break;
8802           case MachinePlaysWhite:
8803           case IcsPlayingWhite:
8804             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8805             break;
8806           case TwoMachinesPlay:
8807             if (cps->twoMachinesColor[0] == 'w')
8808               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8809             else
8810               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8811             break;
8812           default:
8813             /* can't happen */
8814             break;
8815         }
8816         return;
8817     } else if (strncmp(message, "checkmate", 9) == 0) {
8818         if (WhiteOnMove(forwardMostMove)) {
8819             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8820         } else {
8821             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8822         }
8823         return;
8824     } else if (strstr(message, "Draw") != NULL ||
8825                strstr(message, "game is a draw") != NULL) {
8826         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8827         return;
8828     } else if (strstr(message, "offer") != NULL &&
8829                strstr(message, "draw") != NULL) {
8830 #if ZIPPY
8831         if (appData.zippyPlay && first.initDone) {
8832             /* Relay offer to ICS */
8833             SendToICS(ics_prefix);
8834             SendToICS("draw\n");
8835         }
8836 #endif
8837         cps->offeredDraw = 2; /* valid until this engine moves twice */
8838         if (gameMode == TwoMachinesPlay) {
8839             if (cps->other->offeredDraw) {
8840                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8841             /* [HGM] in two-machine mode we delay relaying draw offer      */
8842             /* until after we also have move, to see if it is really claim */
8843             }
8844         } else if (gameMode == MachinePlaysWhite ||
8845                    gameMode == MachinePlaysBlack) {
8846           if (userOfferedDraw) {
8847             DisplayInformation(_("Machine accepts your draw offer"));
8848             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8849           } else {
8850             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8851           }
8852         }
8853     }
8854
8855
8856     /*
8857      * Look for thinking output
8858      */
8859     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8860           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8861                                 ) {
8862         int plylev, mvleft, mvtot, curscore, time;
8863         char mvname[MOVE_LEN];
8864         u64 nodes; // [DM]
8865         char plyext;
8866         int ignore = FALSE;
8867         int prefixHint = FALSE;
8868         mvname[0] = NULLCHAR;
8869
8870         switch (gameMode) {
8871           case MachinePlaysBlack:
8872           case IcsPlayingBlack:
8873             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8874             break;
8875           case MachinePlaysWhite:
8876           case IcsPlayingWhite:
8877             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8878             break;
8879           case AnalyzeMode:
8880           case AnalyzeFile:
8881             break;
8882           case IcsObserving: /* [DM] icsEngineAnalyze */
8883             if (!appData.icsEngineAnalyze) ignore = TRUE;
8884             break;
8885           case TwoMachinesPlay:
8886             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8887                 ignore = TRUE;
8888             }
8889             break;
8890           default:
8891             ignore = TRUE;
8892             break;
8893         }
8894
8895         if (!ignore) {
8896             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8897             buf1[0] = NULLCHAR;
8898             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8899                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8900
8901                 if (plyext != ' ' && plyext != '\t') {
8902                     time *= 100;
8903                 }
8904
8905                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8906                 if( cps->scoreIsAbsolute &&
8907                     ( gameMode == MachinePlaysBlack ||
8908                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8909                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8910                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8911                      !WhiteOnMove(currentMove)
8912                     ) )
8913                 {
8914                     curscore = -curscore;
8915                 }
8916
8917                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
8918
8919                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
8920                         char buf[MSG_SIZ];
8921                         FILE *f;
8922                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
8923                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
8924                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
8925                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
8926                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
8927                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
8928                                 fclose(f);
8929                         } else DisplayError(_("failed writing PV"), 0);
8930                 }
8931
8932                 tempStats.depth = plylev;
8933                 tempStats.nodes = nodes;
8934                 tempStats.time = time;
8935                 tempStats.score = curscore;
8936                 tempStats.got_only_move = 0;
8937
8938                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8939                         int ticklen;
8940
8941                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8942                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8943                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8944                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8945                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8946                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8947                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8948                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8949                 }
8950
8951                 /* Buffer overflow protection */
8952                 if (pv[0] != NULLCHAR) {
8953                     if (strlen(pv) >= sizeof(tempStats.movelist)
8954                         && appData.debugMode) {
8955                         fprintf(debugFP,
8956                                 "PV is too long; using the first %u bytes.\n",
8957                                 (unsigned) sizeof(tempStats.movelist) - 1);
8958                     }
8959
8960                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8961                 } else {
8962                     sprintf(tempStats.movelist, " no PV\n");
8963                 }
8964
8965                 if (tempStats.seen_stat) {
8966                     tempStats.ok_to_send = 1;
8967                 }
8968
8969                 if (strchr(tempStats.movelist, '(') != NULL) {
8970                     tempStats.line_is_book = 1;
8971                     tempStats.nr_moves = 0;
8972                     tempStats.moves_left = 0;
8973                 } else {
8974                     tempStats.line_is_book = 0;
8975                 }
8976
8977                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8978                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8979
8980                 SendProgramStatsToFrontend( cps, &tempStats );
8981
8982                 /*
8983                     [AS] Protect the thinkOutput buffer from overflow... this
8984                     is only useful if buf1 hasn't overflowed first!
8985                 */
8986                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8987                          plylev,
8988                          (gameMode == TwoMachinesPlay ?
8989                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8990                          ((double) curscore) / 100.0,
8991                          prefixHint ? lastHint : "",
8992                          prefixHint ? " " : "" );
8993
8994                 if( buf1[0] != NULLCHAR ) {
8995                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8996
8997                     if( strlen(pv) > max_len ) {
8998                         if( appData.debugMode) {
8999                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9000                         }
9001                         pv[max_len+1] = '\0';
9002                     }
9003
9004                     strcat( thinkOutput, pv);
9005                 }
9006
9007                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9008                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9009                     DisplayMove(currentMove - 1);
9010                 }
9011                 return;
9012
9013             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9014                 /* crafty (9.25+) says "(only move) <move>"
9015                  * if there is only 1 legal move
9016                  */
9017                 sscanf(p, "(only move) %s", buf1);
9018                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9019                 sprintf(programStats.movelist, "%s (only move)", buf1);
9020                 programStats.depth = 1;
9021                 programStats.nr_moves = 1;
9022                 programStats.moves_left = 1;
9023                 programStats.nodes = 1;
9024                 programStats.time = 1;
9025                 programStats.got_only_move = 1;
9026
9027                 /* Not really, but we also use this member to
9028                    mean "line isn't going to change" (Crafty
9029                    isn't searching, so stats won't change) */
9030                 programStats.line_is_book = 1;
9031
9032                 SendProgramStatsToFrontend( cps, &programStats );
9033
9034                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9035                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9036                     DisplayMove(currentMove - 1);
9037                 }
9038                 return;
9039             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9040                               &time, &nodes, &plylev, &mvleft,
9041                               &mvtot, mvname) >= 5) {
9042                 /* The stat01: line is from Crafty (9.29+) in response
9043                    to the "." command */
9044                 programStats.seen_stat = 1;
9045                 cps->maybeThinking = TRUE;
9046
9047                 if (programStats.got_only_move || !appData.periodicUpdates)
9048                   return;
9049
9050                 programStats.depth = plylev;
9051                 programStats.time = time;
9052                 programStats.nodes = nodes;
9053                 programStats.moves_left = mvleft;
9054                 programStats.nr_moves = mvtot;
9055                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9056                 programStats.ok_to_send = 1;
9057                 programStats.movelist[0] = '\0';
9058
9059                 SendProgramStatsToFrontend( cps, &programStats );
9060
9061                 return;
9062
9063             } else if (strncmp(message,"++",2) == 0) {
9064                 /* Crafty 9.29+ outputs this */
9065                 programStats.got_fail = 2;
9066                 return;
9067
9068             } else if (strncmp(message,"--",2) == 0) {
9069                 /* Crafty 9.29+ outputs this */
9070                 programStats.got_fail = 1;
9071                 return;
9072
9073             } else if (thinkOutput[0] != NULLCHAR &&
9074                        strncmp(message, "    ", 4) == 0) {
9075                 unsigned message_len;
9076
9077                 p = message;
9078                 while (*p && *p == ' ') p++;
9079
9080                 message_len = strlen( p );
9081
9082                 /* [AS] Avoid buffer overflow */
9083                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9084                     strcat(thinkOutput, " ");
9085                     strcat(thinkOutput, p);
9086                 }
9087
9088                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9089                     strcat(programStats.movelist, " ");
9090                     strcat(programStats.movelist, p);
9091                 }
9092
9093                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9094                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9095                     DisplayMove(currentMove - 1);
9096                 }
9097                 return;
9098             }
9099         }
9100         else {
9101             buf1[0] = NULLCHAR;
9102
9103             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9104                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9105             {
9106                 ChessProgramStats cpstats;
9107
9108                 if (plyext != ' ' && plyext != '\t') {
9109                     time *= 100;
9110                 }
9111
9112                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9113                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9114                     curscore = -curscore;
9115                 }
9116
9117                 cpstats.depth = plylev;
9118                 cpstats.nodes = nodes;
9119                 cpstats.time = time;
9120                 cpstats.score = curscore;
9121                 cpstats.got_only_move = 0;
9122                 cpstats.movelist[0] = '\0';
9123
9124                 if (buf1[0] != NULLCHAR) {
9125                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9126                 }
9127
9128                 cpstats.ok_to_send = 0;
9129                 cpstats.line_is_book = 0;
9130                 cpstats.nr_moves = 0;
9131                 cpstats.moves_left = 0;
9132
9133                 SendProgramStatsToFrontend( cps, &cpstats );
9134             }
9135         }
9136     }
9137 }
9138
9139
9140 /* Parse a game score from the character string "game", and
9141    record it as the history of the current game.  The game
9142    score is NOT assumed to start from the standard position.
9143    The display is not updated in any way.
9144    */
9145 void
9146 ParseGameHistory (char *game)
9147 {
9148     ChessMove moveType;
9149     int fromX, fromY, toX, toY, boardIndex;
9150     char promoChar;
9151     char *p, *q;
9152     char buf[MSG_SIZ];
9153
9154     if (appData.debugMode)
9155       fprintf(debugFP, "Parsing game history: %s\n", game);
9156
9157     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9158     gameInfo.site = StrSave(appData.icsHost);
9159     gameInfo.date = PGNDate();
9160     gameInfo.round = StrSave("-");
9161
9162     /* Parse out names of players */
9163     while (*game == ' ') game++;
9164     p = buf;
9165     while (*game != ' ') *p++ = *game++;
9166     *p = NULLCHAR;
9167     gameInfo.white = StrSave(buf);
9168     while (*game == ' ') game++;
9169     p = buf;
9170     while (*game != ' ' && *game != '\n') *p++ = *game++;
9171     *p = NULLCHAR;
9172     gameInfo.black = StrSave(buf);
9173
9174     /* Parse moves */
9175     boardIndex = blackPlaysFirst ? 1 : 0;
9176     yynewstr(game);
9177     for (;;) {
9178         yyboardindex = boardIndex;
9179         moveType = (ChessMove) Myylex();
9180         switch (moveType) {
9181           case IllegalMove:             /* maybe suicide chess, etc. */
9182   if (appData.debugMode) {
9183     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9184     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9185     setbuf(debugFP, NULL);
9186   }
9187           case WhitePromotion:
9188           case BlackPromotion:
9189           case WhiteNonPromotion:
9190           case BlackNonPromotion:
9191           case NormalMove:
9192           case WhiteCapturesEnPassant:
9193           case BlackCapturesEnPassant:
9194           case WhiteKingSideCastle:
9195           case WhiteQueenSideCastle:
9196           case BlackKingSideCastle:
9197           case BlackQueenSideCastle:
9198           case WhiteKingSideCastleWild:
9199           case WhiteQueenSideCastleWild:
9200           case BlackKingSideCastleWild:
9201           case BlackQueenSideCastleWild:
9202           /* PUSH Fabien */
9203           case WhiteHSideCastleFR:
9204           case WhiteASideCastleFR:
9205           case BlackHSideCastleFR:
9206           case BlackASideCastleFR:
9207           /* POP Fabien */
9208             fromX = currentMoveString[0] - AAA;
9209             fromY = currentMoveString[1] - ONE;
9210             toX = currentMoveString[2] - AAA;
9211             toY = currentMoveString[3] - ONE;
9212             promoChar = currentMoveString[4];
9213             break;
9214           case WhiteDrop:
9215           case BlackDrop:
9216             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9217             fromX = moveType == WhiteDrop ?
9218               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9219             (int) CharToPiece(ToLower(currentMoveString[0]));
9220             fromY = DROP_RANK;
9221             toX = currentMoveString[2] - AAA;
9222             toY = currentMoveString[3] - ONE;
9223             promoChar = NULLCHAR;
9224             break;
9225           case AmbiguousMove:
9226             /* bug? */
9227             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9228   if (appData.debugMode) {
9229     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9230     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9231     setbuf(debugFP, NULL);
9232   }
9233             DisplayError(buf, 0);
9234             return;
9235           case ImpossibleMove:
9236             /* bug? */
9237             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9238   if (appData.debugMode) {
9239     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9240     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9241     setbuf(debugFP, NULL);
9242   }
9243             DisplayError(buf, 0);
9244             return;
9245           case EndOfFile:
9246             if (boardIndex < backwardMostMove) {
9247                 /* Oops, gap.  How did that happen? */
9248                 DisplayError(_("Gap in move list"), 0);
9249                 return;
9250             }
9251             backwardMostMove =  blackPlaysFirst ? 1 : 0;
9252             if (boardIndex > forwardMostMove) {
9253                 forwardMostMove = boardIndex;
9254             }
9255             return;
9256           case ElapsedTime:
9257             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9258                 strcat(parseList[boardIndex-1], " ");
9259                 strcat(parseList[boardIndex-1], yy_text);
9260             }
9261             continue;
9262           case Comment:
9263           case PGNTag:
9264           case NAG:
9265           default:
9266             /* ignore */
9267             continue;
9268           case WhiteWins:
9269           case BlackWins:
9270           case GameIsDrawn:
9271           case GameUnfinished:
9272             if (gameMode == IcsExamining) {
9273                 if (boardIndex < backwardMostMove) {
9274                     /* Oops, gap.  How did that happen? */
9275                     return;
9276                 }
9277                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9278                 return;
9279             }
9280             gameInfo.result = moveType;
9281             p = strchr(yy_text, '{');
9282             if (p == NULL) p = strchr(yy_text, '(');
9283             if (p == NULL) {
9284                 p = yy_text;
9285                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9286             } else {
9287                 q = strchr(p, *p == '{' ? '}' : ')');
9288                 if (q != NULL) *q = NULLCHAR;
9289                 p++;
9290             }
9291             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9292             gameInfo.resultDetails = StrSave(p);
9293             continue;
9294         }
9295         if (boardIndex >= forwardMostMove &&
9296             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9297             backwardMostMove = blackPlaysFirst ? 1 : 0;
9298             return;
9299         }
9300         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9301                                  fromY, fromX, toY, toX, promoChar,
9302                                  parseList[boardIndex]);
9303         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9304         /* currentMoveString is set as a side-effect of yylex */
9305         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9306         strcat(moveList[boardIndex], "\n");
9307         boardIndex++;
9308         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9309         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9310           case MT_NONE:
9311           case MT_STALEMATE:
9312           default:
9313             break;
9314           case MT_CHECK:
9315             if(gameInfo.variant != VariantShogi)
9316                 strcat(parseList[boardIndex - 1], "+");
9317             break;
9318           case MT_CHECKMATE:
9319           case MT_STAINMATE:
9320             strcat(parseList[boardIndex - 1], "#");
9321             break;
9322         }
9323     }
9324 }
9325
9326
9327 /* Apply a move to the given board  */
9328 void
9329 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9330 {
9331   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9332   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9333
9334     /* [HGM] compute & store e.p. status and castling rights for new position */
9335     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9336
9337       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9338       oldEP = (signed char)board[EP_STATUS];
9339       board[EP_STATUS] = EP_NONE;
9340
9341   if (fromY == DROP_RANK) {
9342         /* must be first */
9343         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9344             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9345             return;
9346         }
9347         piece = board[toY][toX] = (ChessSquare) fromX;
9348   } else {
9349       int i;
9350
9351       if( board[toY][toX] != EmptySquare )
9352            board[EP_STATUS] = EP_CAPTURE;
9353
9354       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9355            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9356                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9357       } else
9358       if( board[fromY][fromX] == WhitePawn ) {
9359            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9360                board[EP_STATUS] = EP_PAWN_MOVE;
9361            if( toY-fromY==2) {
9362                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9363                         gameInfo.variant != VariantBerolina || toX < fromX)
9364                       board[EP_STATUS] = toX | berolina;
9365                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9366                         gameInfo.variant != VariantBerolina || toX > fromX)
9367                       board[EP_STATUS] = toX;
9368            }
9369       } else
9370       if( board[fromY][fromX] == BlackPawn ) {
9371            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9372                board[EP_STATUS] = EP_PAWN_MOVE;
9373            if( toY-fromY== -2) {
9374                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9375                         gameInfo.variant != VariantBerolina || toX < fromX)
9376                       board[EP_STATUS] = toX | berolina;
9377                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9378                         gameInfo.variant != VariantBerolina || toX > fromX)
9379                       board[EP_STATUS] = toX;
9380            }
9381        }
9382
9383        for(i=0; i<nrCastlingRights; i++) {
9384            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9385               board[CASTLING][i] == toX   && castlingRank[i] == toY
9386              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9387        }
9388
9389        if(gameInfo.variant == VariantSChess) { // update virginity
9390            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
9391            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
9392            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
9393            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
9394        }
9395
9396      if (fromX == toX && fromY == toY) return;
9397
9398      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9399      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9400      if(gameInfo.variant == VariantKnightmate)
9401          king += (int) WhiteUnicorn - (int) WhiteKing;
9402
9403     /* Code added by Tord: */
9404     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9405     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9406         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9407       board[fromY][fromX] = EmptySquare;
9408       board[toY][toX] = EmptySquare;
9409       if((toX > fromX) != (piece == WhiteRook)) {
9410         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9411       } else {
9412         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9413       }
9414     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9415                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9416       board[fromY][fromX] = EmptySquare;
9417       board[toY][toX] = EmptySquare;
9418       if((toX > fromX) != (piece == BlackRook)) {
9419         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9420       } else {
9421         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9422       }
9423     /* End of code added by Tord */
9424
9425     } else if (board[fromY][fromX] == king
9426         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9427         && toY == fromY && toX > fromX+1) {
9428         board[fromY][fromX] = EmptySquare;
9429         board[toY][toX] = king;
9430         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9431         board[fromY][BOARD_RGHT-1] = EmptySquare;
9432     } else if (board[fromY][fromX] == king
9433         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9434                && toY == fromY && toX < fromX-1) {
9435         board[fromY][fromX] = EmptySquare;
9436         board[toY][toX] = king;
9437         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9438         board[fromY][BOARD_LEFT] = EmptySquare;
9439     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9440                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9441                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9442                ) {
9443         /* white pawn promotion */
9444         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9445         if((gameInfo.variant==VariantBughouse || gameInfo.variant==VariantCrazyhouse) 
9446            && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9447             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9448         board[fromY][fromX] = EmptySquare;
9449     } else if ((fromY >= BOARD_HEIGHT>>1)
9450                && (toX != fromX)
9451                && gameInfo.variant != VariantXiangqi
9452                && gameInfo.variant != VariantBerolina
9453                && (board[fromY][fromX] == WhitePawn)
9454                && (board[toY][toX] == EmptySquare)) {
9455         board[fromY][fromX] = EmptySquare;
9456         board[toY][toX] = WhitePawn;
9457         captured = board[toY - 1][toX];
9458         board[toY - 1][toX] = EmptySquare;
9459     } else if ((fromY == BOARD_HEIGHT-4)
9460                && (toX == fromX)
9461                && gameInfo.variant == VariantBerolina
9462                && (board[fromY][fromX] == WhitePawn)
9463                && (board[toY][toX] == EmptySquare)) {
9464         board[fromY][fromX] = EmptySquare;
9465         board[toY][toX] = WhitePawn;
9466         if(oldEP & EP_BEROLIN_A) {
9467                 captured = board[fromY][fromX-1];
9468                 board[fromY][fromX-1] = EmptySquare;
9469         }else{  captured = board[fromY][fromX+1];
9470                 board[fromY][fromX+1] = EmptySquare;
9471         }
9472     } else if (board[fromY][fromX] == king
9473         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9474                && toY == fromY && toX > fromX+1) {
9475         board[fromY][fromX] = EmptySquare;
9476         board[toY][toX] = king;
9477         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9478         board[fromY][BOARD_RGHT-1] = EmptySquare;
9479     } else if (board[fromY][fromX] == king
9480         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9481                && toY == fromY && toX < fromX-1) {
9482         board[fromY][fromX] = EmptySquare;
9483         board[toY][toX] = king;
9484         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9485         board[fromY][BOARD_LEFT] = EmptySquare;
9486     } else if (fromY == 7 && fromX == 3
9487                && board[fromY][fromX] == BlackKing
9488                && toY == 7 && toX == 5) {
9489         board[fromY][fromX] = EmptySquare;
9490         board[toY][toX] = BlackKing;
9491         board[fromY][7] = EmptySquare;
9492         board[toY][4] = BlackRook;
9493     } else if (fromY == 7 && fromX == 3
9494                && board[fromY][fromX] == BlackKing
9495                && toY == 7 && toX == 1) {
9496         board[fromY][fromX] = EmptySquare;
9497         board[toY][toX] = BlackKing;
9498         board[fromY][0] = EmptySquare;
9499         board[toY][2] = BlackRook;
9500     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9501                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9502                && toY < promoRank && promoChar
9503                ) {
9504         /* black pawn promotion */
9505         board[toY][toX] = CharToPiece(ToLower(promoChar));
9506         if((gameInfo.variant==VariantBughouse || gameInfo.variant==VariantCrazyhouse) 
9507            && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9508             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9509         board[fromY][fromX] = EmptySquare;
9510     } else if ((fromY < BOARD_HEIGHT>>1)
9511                && (toX != fromX)
9512                && gameInfo.variant != VariantXiangqi
9513                && gameInfo.variant != VariantBerolina
9514                && (board[fromY][fromX] == BlackPawn)
9515                && (board[toY][toX] == EmptySquare)) {
9516         board[fromY][fromX] = EmptySquare;
9517         board[toY][toX] = BlackPawn;
9518         captured = board[toY + 1][toX];
9519         board[toY + 1][toX] = EmptySquare;
9520     } else if ((fromY == 3)
9521                && (toX == fromX)
9522                && gameInfo.variant == VariantBerolina
9523                && (board[fromY][fromX] == BlackPawn)
9524                && (board[toY][toX] == EmptySquare)) {
9525         board[fromY][fromX] = EmptySquare;
9526         board[toY][toX] = BlackPawn;
9527         if(oldEP & EP_BEROLIN_A) {
9528                 captured = board[fromY][fromX-1];
9529                 board[fromY][fromX-1] = EmptySquare;
9530         }else{  captured = board[fromY][fromX+1];
9531                 board[fromY][fromX+1] = EmptySquare;
9532         }
9533     } else {
9534         board[toY][toX] = board[fromY][fromX];
9535         board[fromY][fromX] = EmptySquare;
9536     }
9537   }
9538
9539     if (gameInfo.holdingsWidth != 0) {
9540
9541       /* !!A lot more code needs to be written to support holdings  */
9542       /* [HGM] OK, so I have written it. Holdings are stored in the */
9543       /* penultimate board files, so they are automaticlly stored   */
9544       /* in the game history.                                       */
9545       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9546                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9547         /* Delete from holdings, by decreasing count */
9548         /* and erasing image if necessary            */
9549         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9550         if(p < (int) BlackPawn) { /* white drop */
9551              p -= (int)WhitePawn;
9552                  p = PieceToNumber((ChessSquare)p);
9553              if(p >= gameInfo.holdingsSize) p = 0;
9554              if(--board[p][BOARD_WIDTH-2] <= 0)
9555                   board[p][BOARD_WIDTH-1] = EmptySquare;
9556              if((int)board[p][BOARD_WIDTH-2] < 0)
9557                         board[p][BOARD_WIDTH-2] = 0;
9558         } else {                  /* black drop */
9559              p -= (int)BlackPawn;
9560                  p = PieceToNumber((ChessSquare)p);
9561              if(p >= gameInfo.holdingsSize) p = 0;
9562              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9563                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9564              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9565                         board[BOARD_HEIGHT-1-p][1] = 0;
9566         }
9567       }
9568       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9569           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9570         /* [HGM] holdings: Add to holdings, if holdings exist */
9571         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9572                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9573                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9574         }
9575         p = (int) captured;
9576         if (p >= (int) BlackPawn) {
9577           p -= (int)BlackPawn;
9578           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9579                   /* in Shogi restore piece to its original  first */
9580                   captured = (ChessSquare) (DEMOTED captured);
9581                   p = DEMOTED p;
9582           }
9583           p = PieceToNumber((ChessSquare)p);
9584           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9585           board[p][BOARD_WIDTH-2]++;
9586           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9587         } else {
9588           p -= (int)WhitePawn;
9589           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9590                   captured = (ChessSquare) (DEMOTED captured);
9591                   p = DEMOTED p;
9592           }
9593           p = PieceToNumber((ChessSquare)p);
9594           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9595           board[BOARD_HEIGHT-1-p][1]++;
9596           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9597         }
9598       }
9599     } else if (gameInfo.variant == VariantAtomic) {
9600       if (captured != EmptySquare) {
9601         int y, x;
9602         for (y = toY-1; y <= toY+1; y++) {
9603           for (x = toX-1; x <= toX+1; x++) {
9604             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9605                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9606               board[y][x] = EmptySquare;
9607             }
9608           }
9609         }
9610         board[toY][toX] = EmptySquare;
9611       }
9612     }
9613     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9614         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9615     } else
9616     if(promoChar == '+') {
9617         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
9618         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9619     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9620         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9621         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
9622            && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
9623         board[toY][toX] = newPiece;
9624     }
9625     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9626                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9627         // [HGM] superchess: take promotion piece out of holdings
9628         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9629         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9630             if(!--board[k][BOARD_WIDTH-2])
9631                 board[k][BOARD_WIDTH-1] = EmptySquare;
9632         } else {
9633             if(!--board[BOARD_HEIGHT-1-k][1])
9634                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9635         }
9636     }
9637
9638 }
9639
9640 /* Updates forwardMostMove */
9641 void
9642 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
9643 {
9644 //    forwardMostMove++; // [HGM] bare: moved downstream
9645
9646     (void) CoordsToAlgebraic(boards[forwardMostMove],
9647                              PosFlags(forwardMostMove),
9648                              fromY, fromX, toY, toX, promoChar,
9649                              parseList[forwardMostMove]);
9650
9651     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9652         int timeLeft; static int lastLoadFlag=0; int king, piece;
9653         piece = boards[forwardMostMove][fromY][fromX];
9654         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9655         if(gameInfo.variant == VariantKnightmate)
9656             king += (int) WhiteUnicorn - (int) WhiteKing;
9657         if(forwardMostMove == 0) {
9658             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9659                 fprintf(serverMoves, "%s;", UserName());
9660             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9661                 fprintf(serverMoves, "%s;", second.tidy);
9662             fprintf(serverMoves, "%s;", first.tidy);
9663             if(gameMode == MachinePlaysWhite)
9664                 fprintf(serverMoves, "%s;", UserName());
9665             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9666                 fprintf(serverMoves, "%s;", second.tidy);
9667         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9668         lastLoadFlag = loadFlag;
9669         // print base move
9670         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9671         // print castling suffix
9672         if( toY == fromY && piece == king ) {
9673             if(toX-fromX > 1)
9674                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9675             if(fromX-toX >1)
9676                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9677         }
9678         // e.p. suffix
9679         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9680              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9681              boards[forwardMostMove][toY][toX] == EmptySquare
9682              && fromX != toX && fromY != toY)
9683                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9684         // promotion suffix
9685         if(promoChar != NULLCHAR) {
9686             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
9687                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
9688                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
9689             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
9690         }
9691         if(!loadFlag) {
9692                 char buf[MOVE_LEN*2], *p; int len;
9693             fprintf(serverMoves, "/%d/%d",
9694                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9695             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9696             else                      timeLeft = blackTimeRemaining/1000;
9697             fprintf(serverMoves, "/%d", timeLeft);
9698                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
9699                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
9700                 if(p = strchr(buf, '=')) *p = NULLCHAR;
9701                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
9702             fprintf(serverMoves, "/%s", buf);
9703         }
9704         fflush(serverMoves);
9705     }
9706
9707     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
9708         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
9709       return;
9710     }
9711     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9712     if (commentList[forwardMostMove+1] != NULL) {
9713         free(commentList[forwardMostMove+1]);
9714         commentList[forwardMostMove+1] = NULL;
9715     }
9716     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9717     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9718     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9719     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9720     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9721     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9722     adjustedClock = FALSE;
9723     gameInfo.result = GameUnfinished;
9724     if (gameInfo.resultDetails != NULL) {
9725         free(gameInfo.resultDetails);
9726         gameInfo.resultDetails = NULL;
9727     }
9728     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9729                               moveList[forwardMostMove - 1]);
9730     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9731       case MT_NONE:
9732       case MT_STALEMATE:
9733       default:
9734         break;
9735       case MT_CHECK:
9736         if(gameInfo.variant != VariantShogi)
9737             strcat(parseList[forwardMostMove - 1], "+");
9738         break;
9739       case MT_CHECKMATE:
9740       case MT_STAINMATE:
9741         strcat(parseList[forwardMostMove - 1], "#");
9742         break;
9743     }
9744
9745 }
9746
9747 /* Updates currentMove if not pausing */
9748 void
9749 ShowMove (int fromX, int fromY, int toX, int toY)
9750 {
9751     int instant = (gameMode == PlayFromGameFile) ?
9752         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9753     if(appData.noGUI) return;
9754     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9755         if (!instant) {
9756             if (forwardMostMove == currentMove + 1) {
9757                 AnimateMove(boards[forwardMostMove - 1],
9758                             fromX, fromY, toX, toY);
9759             }
9760         }
9761         currentMove = forwardMostMove;
9762     }
9763
9764     if (instant) return;
9765
9766     DisplayMove(currentMove - 1);
9767     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9768             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
9769                 SetHighlights(fromX, fromY, toX, toY);
9770             }
9771     }
9772     DrawPosition(FALSE, boards[currentMove]);
9773     DisplayBothClocks();
9774     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9775 }
9776
9777 void
9778 SendEgtPath (ChessProgramState *cps)
9779 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9780         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9781
9782         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9783
9784         while(*p) {
9785             char c, *q = name+1, *r, *s;
9786
9787             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9788             while(*p && *p != ',') *q++ = *p++;
9789             *q++ = ':'; *q = 0;
9790             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9791                 strcmp(name, ",nalimov:") == 0 ) {
9792                 // take nalimov path from the menu-changeable option first, if it is defined
9793               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9794                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9795             } else
9796             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9797                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9798                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9799                 s = r = StrStr(s, ":") + 1; // beginning of path info
9800                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9801                 c = *r; *r = 0;             // temporarily null-terminate path info
9802                     *--q = 0;               // strip of trailig ':' from name
9803                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9804                 *r = c;
9805                 SendToProgram(buf,cps);     // send egtbpath command for this format
9806             }
9807             if(*p == ',') p++; // read away comma to position for next format name
9808         }
9809 }
9810
9811 void
9812 InitChessProgram (ChessProgramState *cps, int setup)
9813 /* setup needed to setup FRC opening position */
9814 {
9815     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9816     if (appData.noChessProgram) return;
9817     hintRequested = FALSE;
9818     bookRequested = FALSE;
9819
9820     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
9821     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9822     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9823     if(cps->memSize) { /* [HGM] memory */
9824       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9825         SendToProgram(buf, cps);
9826     }
9827     SendEgtPath(cps); /* [HGM] EGT */
9828     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9829       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9830         SendToProgram(buf, cps);
9831     }
9832
9833     SendToProgram(cps->initString, cps);
9834     if (gameInfo.variant != VariantNormal &&
9835         gameInfo.variant != VariantLoadable
9836         /* [HGM] also send variant if board size non-standard */
9837         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9838                                             ) {
9839       char *v = VariantName(gameInfo.variant);
9840       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9841         /* [HGM] in protocol 1 we have to assume all variants valid */
9842         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9843         DisplayFatalError(buf, 0, 1);
9844         return;
9845       }
9846
9847       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9848       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9849       if( gameInfo.variant == VariantXiangqi )
9850            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9851       if( gameInfo.variant == VariantShogi )
9852            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9853       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9854            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9855       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9856           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9857            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9858       if( gameInfo.variant == VariantCourier )
9859            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9860       if( gameInfo.variant == VariantSuper )
9861            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9862       if( gameInfo.variant == VariantGreat )
9863            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9864       if( gameInfo.variant == VariantSChess )
9865            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9866       if( gameInfo.variant == VariantGrand )
9867            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
9868
9869       if(overruled) {
9870         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9871                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9872            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9873            if(StrStr(cps->variants, b) == NULL) {
9874                // specific sized variant not known, check if general sizing allowed
9875                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9876                    if(StrStr(cps->variants, "boardsize") == NULL) {
9877                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9878                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9879                        DisplayFatalError(buf, 0, 1);
9880                        return;
9881                    }
9882                    /* [HGM] here we really should compare with the maximum supported board size */
9883                }
9884            }
9885       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9886       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9887       SendToProgram(buf, cps);
9888     }
9889     currentlyInitializedVariant = gameInfo.variant;
9890
9891     /* [HGM] send opening position in FRC to first engine */
9892     if(setup) {
9893           SendToProgram("force\n", cps);
9894           SendBoard(cps, 0);
9895           /* engine is now in force mode! Set flag to wake it up after first move. */
9896           setboardSpoiledMachineBlack = 1;
9897     }
9898
9899     if (cps->sendICS) {
9900       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9901       SendToProgram(buf, cps);
9902     }
9903     cps->maybeThinking = FALSE;
9904     cps->offeredDraw = 0;
9905     if (!appData.icsActive) {
9906         SendTimeControl(cps, movesPerSession, timeControl,
9907                         timeIncrement, appData.searchDepth,
9908                         searchTime);
9909     }
9910     if (appData.showThinking
9911         // [HGM] thinking: four options require thinking output to be sent
9912         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9913                                 ) {
9914         SendToProgram("post\n", cps);
9915     }
9916     SendToProgram("hard\n", cps);
9917     if (!appData.ponderNextMove) {
9918         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9919            it without being sure what state we are in first.  "hard"
9920            is not a toggle, so that one is OK.
9921          */
9922         SendToProgram("easy\n", cps);
9923     }
9924     if (cps->usePing) {
9925       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9926       SendToProgram(buf, cps);
9927     }
9928     cps->initDone = TRUE;
9929     ClearEngineOutputPane(cps == &second);
9930 }
9931
9932
9933 void
9934 StartChessProgram (ChessProgramState *cps)
9935 {
9936     char buf[MSG_SIZ];
9937     int err;
9938
9939     if (appData.noChessProgram) return;
9940     cps->initDone = FALSE;
9941
9942     if (strcmp(cps->host, "localhost") == 0) {
9943         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9944     } else if (*appData.remoteShell == NULLCHAR) {
9945         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9946     } else {
9947         if (*appData.remoteUser == NULLCHAR) {
9948           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9949                     cps->program);
9950         } else {
9951           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9952                     cps->host, appData.remoteUser, cps->program);
9953         }
9954         err = StartChildProcess(buf, "", &cps->pr);
9955     }
9956
9957     if (err != 0) {
9958       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9959         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
9960         if(cps != &first) return;
9961         appData.noChessProgram = TRUE;
9962         ThawUI();
9963         SetNCPMode();
9964 //      DisplayFatalError(buf, err, 1);
9965 //      cps->pr = NoProc;
9966 //      cps->isr = NULL;
9967         return;
9968     }
9969
9970     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9971     if (cps->protocolVersion > 1) {
9972       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9973       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9974       cps->comboCnt = 0;  //                and values of combo boxes
9975       SendToProgram(buf, cps);
9976     } else {
9977       SendToProgram("xboard\n", cps);
9978     }
9979 }
9980
9981 void
9982 TwoMachinesEventIfReady P((void))
9983 {
9984   static int curMess = 0;
9985   if (first.lastPing != first.lastPong) {
9986     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9987     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9988     return;
9989   }
9990   if (second.lastPing != second.lastPong) {
9991     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9992     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9993     return;
9994   }
9995   DisplayMessage("", ""); curMess = 0;
9996   ThawUI();
9997   TwoMachinesEvent();
9998 }
9999
10000 char *
10001 MakeName (char *template)
10002 {
10003     time_t clock;
10004     struct tm *tm;
10005     static char buf[MSG_SIZ];
10006     char *p = buf;
10007     int i;
10008
10009     clock = time((time_t *)NULL);
10010     tm = localtime(&clock);
10011
10012     while(*p++ = *template++) if(p[-1] == '%') {
10013         switch(*template++) {
10014           case 0:   *p = 0; return buf;
10015           case 'Y': i = tm->tm_year+1900; break;
10016           case 'y': i = tm->tm_year-100; break;
10017           case 'M': i = tm->tm_mon+1; break;
10018           case 'd': i = tm->tm_mday; break;
10019           case 'h': i = tm->tm_hour; break;
10020           case 'm': i = tm->tm_min; break;
10021           case 's': i = tm->tm_sec; break;
10022           default:  i = 0;
10023         }
10024         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10025     }
10026     return buf;
10027 }
10028
10029 int
10030 CountPlayers (char *p)
10031 {
10032     int n = 0;
10033     while(p = strchr(p, '\n')) p++, n++; // count participants
10034     return n;
10035 }
10036
10037 FILE *
10038 WriteTourneyFile (char *results, FILE *f)
10039 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10040     if(f == NULL) f = fopen(appData.tourneyFile, "w");
10041     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10042         // create a file with tournament description
10043         fprintf(f, "-participants {%s}\n", appData.participants);
10044         fprintf(f, "-seedBase %d\n", appData.seedBase);
10045         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10046         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10047         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10048         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10049         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10050         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10051         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10052         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10053         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10054         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10055         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10056         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10057         if(searchTime > 0)
10058                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10059         else {
10060                 fprintf(f, "-mps %d\n", appData.movesPerSession);
10061                 fprintf(f, "-tc %s\n", appData.timeControl);
10062                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10063         }
10064         fprintf(f, "-results \"%s\"\n", results);
10065     }
10066     return f;
10067 }
10068
10069 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10070
10071 void
10072 Substitute (char *participants, int expunge)
10073 {
10074     int i, changed, changes=0, nPlayers=0;
10075     char *p, *q, *r, buf[MSG_SIZ];
10076     if(participants == NULL) return;
10077     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10078     r = p = participants; q = appData.participants;
10079     while(*p && *p == *q) {
10080         if(*p == '\n') r = p+1, nPlayers++;
10081         p++; q++;
10082     }
10083     if(*p) { // difference
10084         while(*p && *p++ != '\n');
10085         while(*q && *q++ != '\n');
10086       changed = nPlayers;
10087         changes = 1 + (strcmp(p, q) != 0);
10088     }
10089     if(changes == 1) { // a single engine mnemonic was changed
10090         q = r; while(*q) nPlayers += (*q++ == '\n');
10091         p = buf; while(*r && (*p = *r++) != '\n') p++;
10092         *p = NULLCHAR;
10093         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10094         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10095         if(mnemonic[i]) { // The substitute is valid
10096             FILE *f;
10097             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10098                 flock(fileno(f), LOCK_EX);
10099                 ParseArgsFromFile(f);
10100                 fseek(f, 0, SEEK_SET);
10101                 FREE(appData.participants); appData.participants = participants;
10102                 if(expunge) { // erase results of replaced engine
10103                     int len = strlen(appData.results), w, b, dummy;
10104                     for(i=0; i<len; i++) {
10105                         Pairing(i, nPlayers, &w, &b, &dummy);
10106                         if((w == changed || b == changed) && appData.results[i] == '*') {
10107                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10108                             fclose(f);
10109                             return;
10110                         }
10111                     }
10112                     for(i=0; i<len; i++) {
10113                         Pairing(i, nPlayers, &w, &b, &dummy);
10114                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10115                     }
10116                 }
10117                 WriteTourneyFile(appData.results, f);
10118                 fclose(f); // release lock
10119                 return;
10120             }
10121         } else DisplayError(_("No engine with the name you gave is installed"), 0);
10122     }
10123     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10124     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
10125     free(participants);
10126     return;
10127 }
10128
10129 int
10130 CheckPlayers (char *participants)
10131 {
10132         int i;
10133         char buf[MSG_SIZ], *p;
10134         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10135         while(p = strchr(participants, '\n')) {
10136             *p = NULLCHAR;
10137             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10138             if(!mnemonic[i]) {
10139                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10140                 *p = '\n';
10141                 DisplayError(buf, 0);
10142                 return 1;
10143             }
10144             *p = '\n';
10145             participants = p + 1;
10146         }
10147         return 0;
10148 }
10149
10150 int
10151 CreateTourney (char *name)
10152 {
10153         FILE *f;
10154         if(matchMode && strcmp(name, appData.tourneyFile)) {
10155              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10156         }
10157         if(name[0] == NULLCHAR) {
10158             if(appData.participants[0])
10159                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10160             return 0;
10161         }
10162         f = fopen(name, "r");
10163         if(f) { // file exists
10164             ASSIGN(appData.tourneyFile, name);
10165             ParseArgsFromFile(f); // parse it
10166         } else {
10167             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10168             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10169                 DisplayError(_("Not enough participants"), 0);
10170                 return 0;
10171             }
10172             if(CheckPlayers(appData.participants)) return 0;
10173             ASSIGN(appData.tourneyFile, name);
10174             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10175             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10176         }
10177         fclose(f);
10178         appData.noChessProgram = FALSE;
10179         appData.clockMode = TRUE;
10180         SetGNUMode();
10181         return 1;
10182 }
10183
10184 int
10185 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10186 {
10187     char buf[MSG_SIZ], *p, *q;
10188     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10189     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10190     skip = !all && group[0]; // if group requested, we start in skip mode
10191     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10192         p = names; q = buf; header = 0;
10193         while(*p && *p != '\n') *q++ = *p++;
10194         *q = 0;
10195         if(*p == '\n') p++;
10196         if(buf[0] == '#') {
10197             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10198             depth++; // we must be entering a new group
10199             if(all) continue; // suppress printing group headers when complete list requested
10200             header = 1;
10201             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10202         }
10203         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10204         if(engineList[i]) free(engineList[i]);
10205         engineList[i] = strdup(buf);
10206         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10207         if(engineMnemonic[i]) free(engineMnemonic[i]);
10208         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10209             strcat(buf, " (");
10210             sscanf(q + 8, "%s", buf + strlen(buf));
10211             strcat(buf, ")");
10212         }
10213         engineMnemonic[i] = strdup(buf);
10214         i++;
10215     }
10216     engineList[i] = engineMnemonic[i] = NULL;
10217     return i;
10218 }
10219
10220 // following implemented as macro to avoid type limitations
10221 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10222
10223 void
10224 SwapEngines (int n)
10225 {   // swap settings for first engine and other engine (so far only some selected options)
10226     int h;
10227     char *p;
10228     if(n == 0) return;
10229     SWAP(directory, p)
10230     SWAP(chessProgram, p)
10231     SWAP(isUCI, h)
10232     SWAP(hasOwnBookUCI, h)
10233     SWAP(protocolVersion, h)
10234     SWAP(reuse, h)
10235     SWAP(scoreIsAbsolute, h)
10236     SWAP(timeOdds, h)
10237     SWAP(logo, p)
10238     SWAP(pgnName, p)
10239     SWAP(pvSAN, h)
10240     SWAP(engOptions, p)
10241     SWAP(engInitString, p)
10242     SWAP(computerString, p)
10243     SWAP(features, p)
10244     SWAP(fenOverride, p)
10245     SWAP(NPS, h)
10246     SWAP(accumulateTC, h)
10247     SWAP(host, p)
10248 }
10249
10250 int
10251 GetEngineLine (char *s, int n)
10252 {
10253     int i;
10254     char buf[MSG_SIZ];
10255     extern char *icsNames;
10256     if(!s || !*s) return 0;
10257     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
10258     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
10259     if(!mnemonic[i]) return 0;
10260     if(n == 11) return 1; // just testing if there was a match
10261     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
10262     if(n == 1) SwapEngines(n);
10263     ParseArgsFromString(buf);
10264     if(n == 1) SwapEngines(n);
10265     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
10266         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
10267         ParseArgsFromString(buf);
10268     }
10269     return 1;
10270 }
10271
10272 int
10273 SetPlayer (int player, char *p)
10274 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
10275     int i;
10276     char buf[MSG_SIZ], *engineName;
10277     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10278     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10279     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10280     if(mnemonic[i]) {
10281         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10282         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10283         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10284         ParseArgsFromString(buf);
10285     }
10286     free(engineName);
10287     return i;
10288 }
10289
10290 char *recentEngines;
10291
10292 void
10293 RecentEngineEvent (int nr)
10294 {
10295     int n;
10296 //    SwapEngines(1); // bump first to second
10297 //    ReplaceEngine(&second, 1); // and load it there
10298     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10299     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10300     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10301         ReplaceEngine(&first, 0);
10302         FloatToFront(&appData.recentEngineList, command[n]);
10303     }
10304 }
10305
10306 int
10307 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10308 {   // determine players from game number
10309     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10310
10311     if(appData.tourneyType == 0) {
10312         roundsPerCycle = (nPlayers - 1) | 1;
10313         pairingsPerRound = nPlayers / 2;
10314     } else if(appData.tourneyType > 0) {
10315         roundsPerCycle = nPlayers - appData.tourneyType;
10316         pairingsPerRound = appData.tourneyType;
10317     }
10318     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10319     gamesPerCycle = gamesPerRound * roundsPerCycle;
10320     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10321     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10322     curRound = nr / gamesPerRound; nr %= gamesPerRound;
10323     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10324     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10325     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10326
10327     if(appData.cycleSync) *syncInterval = gamesPerCycle;
10328     if(appData.roundSync) *syncInterval = gamesPerRound;
10329
10330     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10331
10332     if(appData.tourneyType == 0) {
10333         if(curPairing == (nPlayers-1)/2 ) {
10334             *whitePlayer = curRound;
10335             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10336         } else {
10337             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10338             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10339             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10340             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10341         }
10342     } else if(appData.tourneyType > 1) {
10343         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10344         *whitePlayer = curRound + appData.tourneyType;
10345     } else if(appData.tourneyType > 0) {
10346         *whitePlayer = curPairing;
10347         *blackPlayer = curRound + appData.tourneyType;
10348     }
10349
10350     // take care of white/black alternation per round. 
10351     // For cycles and games this is already taken care of by default, derived from matchGame!
10352     return curRound & 1;
10353 }
10354
10355 int
10356 NextTourneyGame (int nr, int *swapColors)
10357 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10358     char *p, *q;
10359     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers;
10360     FILE *tf;
10361     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10362     tf = fopen(appData.tourneyFile, "r");
10363     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10364     ParseArgsFromFile(tf); fclose(tf);
10365     InitTimeControls(); // TC might be altered from tourney file
10366
10367     nPlayers = CountPlayers(appData.participants); // count participants
10368     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10369     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10370
10371     if(syncInterval) {
10372         p = q = appData.results;
10373         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10374         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10375             DisplayMessage(_("Waiting for other game(s)"),"");
10376             waitingForGame = TRUE;
10377             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10378             return 0;
10379         }
10380         waitingForGame = FALSE;
10381     }
10382
10383     if(appData.tourneyType < 0) {
10384         if(nr>=0 && !pairingReceived) {
10385             char buf[1<<16];
10386             if(pairing.pr == NoProc) {
10387                 if(!appData.pairingEngine[0]) {
10388                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10389                     return 0;
10390                 }
10391                 StartChessProgram(&pairing); // starts the pairing engine
10392             }
10393             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10394             SendToProgram(buf, &pairing);
10395             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10396             SendToProgram(buf, &pairing);
10397             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10398         }
10399         pairingReceived = 0;                              // ... so we continue here 
10400         *swapColors = 0;
10401         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10402         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10403         matchGame = 1; roundNr = nr / syncInterval + 1;
10404     }
10405
10406     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10407
10408     // redefine engines, engine dir, etc.
10409     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10410     if(first.pr == NoProc) {
10411       SetPlayer(whitePlayer, appData.participants); // find white player amongst it, and parse its engine line
10412       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10413     }
10414     if(second.pr == NoProc) {
10415       SwapEngines(1);
10416       SetPlayer(blackPlayer, appData.participants); // find black player amongst it, and parse its engine line
10417       SwapEngines(1);         // and make that valid for second engine by swapping
10418       InitEngine(&second, 1);
10419     }
10420     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10421     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10422     return 1;
10423 }
10424
10425 void
10426 NextMatchGame ()
10427 {   // performs game initialization that does not invoke engines, and then tries to start the game
10428     int res, firstWhite, swapColors = 0;
10429     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10430     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
10431         char buf[MSG_SIZ];
10432         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
10433         if(strcmp(buf, currentDebugFile)) { // name has changed
10434             FILE *f = fopen(buf, "w");
10435             if(f) { // if opening the new file failed, just keep using the old one
10436                 ASSIGN(currentDebugFile, buf);
10437                 fclose(debugFP);
10438                 debugFP = f;
10439             }
10440             if(appData.serverFileName) {
10441                 if(serverFP) fclose(serverFP);
10442                 serverFP = fopen(appData.serverFileName, "w");
10443                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
10444                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
10445             }
10446         }
10447     }
10448     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10449     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10450     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
10451     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10452     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10453     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10454     Reset(FALSE, first.pr != NoProc);
10455     res = LoadGameOrPosition(matchGame); // setup game
10456     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10457     if(!res) return; // abort when bad game/pos file
10458     TwoMachinesEvent();
10459 }
10460
10461 void
10462 UserAdjudicationEvent (int result)
10463 {
10464     ChessMove gameResult = GameIsDrawn;
10465
10466     if( result > 0 ) {
10467         gameResult = WhiteWins;
10468     }
10469     else if( result < 0 ) {
10470         gameResult = BlackWins;
10471     }
10472
10473     if( gameMode == TwoMachinesPlay ) {
10474         GameEnds( gameResult, "User adjudication", GE_XBOARD );
10475     }
10476 }
10477
10478
10479 // [HGM] save: calculate checksum of game to make games easily identifiable
10480 int
10481 StringCheckSum (char *s)
10482 {
10483         int i = 0;
10484         if(s==NULL) return 0;
10485         while(*s) i = i*259 + *s++;
10486         return i;
10487 }
10488
10489 int
10490 GameCheckSum ()
10491 {
10492         int i, sum=0;
10493         for(i=backwardMostMove; i<forwardMostMove; i++) {
10494                 sum += pvInfoList[i].depth;
10495                 sum += StringCheckSum(parseList[i]);
10496                 sum += StringCheckSum(commentList[i]);
10497                 sum *= 261;
10498         }
10499         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10500         return sum + StringCheckSum(commentList[i]);
10501 } // end of save patch
10502
10503 void
10504 GameEnds (ChessMove result, char *resultDetails, int whosays)
10505 {
10506     GameMode nextGameMode;
10507     int isIcsGame;
10508     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10509
10510     if(endingGame) return; /* [HGM] crash: forbid recursion */
10511     endingGame = 1;
10512     if(twoBoards) { // [HGM] dual: switch back to one board
10513         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10514         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10515     }
10516     if (appData.debugMode) {
10517       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10518               result, resultDetails ? resultDetails : "(null)", whosays);
10519     }
10520
10521     fromX = fromY = -1; // [HGM] abort any move the user is entering.
10522
10523     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
10524
10525     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10526         /* If we are playing on ICS, the server decides when the
10527            game is over, but the engine can offer to draw, claim
10528            a draw, or resign.
10529          */
10530 #if ZIPPY
10531         if (appData.zippyPlay && first.initDone) {
10532             if (result == GameIsDrawn) {
10533                 /* In case draw still needs to be claimed */
10534                 SendToICS(ics_prefix);
10535                 SendToICS("draw\n");
10536             } else if (StrCaseStr(resultDetails, "resign")) {
10537                 SendToICS(ics_prefix);
10538                 SendToICS("resign\n");
10539             }
10540         }
10541 #endif
10542         endingGame = 0; /* [HGM] crash */
10543         return;
10544     }
10545
10546     /* If we're loading the game from a file, stop */
10547     if (whosays == GE_FILE) {
10548       (void) StopLoadGameTimer();
10549       gameFileFP = NULL;
10550     }
10551
10552     /* Cancel draw offers */
10553     first.offeredDraw = second.offeredDraw = 0;
10554
10555     /* If this is an ICS game, only ICS can really say it's done;
10556        if not, anyone can. */
10557     isIcsGame = (gameMode == IcsPlayingWhite ||
10558                  gameMode == IcsPlayingBlack ||
10559                  gameMode == IcsObserving    ||
10560                  gameMode == IcsExamining);
10561
10562     if (!isIcsGame || whosays == GE_ICS) {
10563         /* OK -- not an ICS game, or ICS said it was done */
10564         StopClocks();
10565         if (!isIcsGame && !appData.noChessProgram)
10566           SetUserThinkingEnables();
10567
10568         /* [HGM] if a machine claims the game end we verify this claim */
10569         if(gameMode == TwoMachinesPlay && appData.testClaims) {
10570             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10571                 char claimer;
10572                 ChessMove trueResult = (ChessMove) -1;
10573
10574                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
10575                                             first.twoMachinesColor[0] :
10576                                             second.twoMachinesColor[0] ;
10577
10578                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10579                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10580                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10581                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10582                 } else
10583                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10584                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10585                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10586                 } else
10587                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10588                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10589                 }
10590
10591                 // now verify win claims, but not in drop games, as we don't understand those yet
10592                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10593                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10594                     (result == WhiteWins && claimer == 'w' ||
10595                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
10596                       if (appData.debugMode) {
10597                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
10598                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10599                       }
10600                       if(result != trueResult) {
10601                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10602                               result = claimer == 'w' ? BlackWins : WhiteWins;
10603                               resultDetails = buf;
10604                       }
10605                 } else
10606                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10607                     && (forwardMostMove <= backwardMostMove ||
10608                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10609                         (claimer=='b')==(forwardMostMove&1))
10610                                                                                   ) {
10611                       /* [HGM] verify: draws that were not flagged are false claims */
10612                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10613                       result = claimer == 'w' ? BlackWins : WhiteWins;
10614                       resultDetails = buf;
10615                 }
10616                 /* (Claiming a loss is accepted no questions asked!) */
10617             }
10618             /* [HGM] bare: don't allow bare King to win */
10619             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10620                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10621                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10622                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10623                && result != GameIsDrawn)
10624             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10625                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10626                         int p = (signed char)boards[forwardMostMove][i][j] - color;
10627                         if(p >= 0 && p <= (int)WhiteKing) k++;
10628                 }
10629                 if (appData.debugMode) {
10630                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10631                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10632                 }
10633                 if(k <= 1) {
10634                         result = GameIsDrawn;
10635                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10636                         resultDetails = buf;
10637                 }
10638             }
10639         }
10640
10641
10642         if(serverMoves != NULL && !loadFlag) { char c = '=';
10643             if(result==WhiteWins) c = '+';
10644             if(result==BlackWins) c = '-';
10645             if(resultDetails != NULL)
10646                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
10647         }
10648         if (resultDetails != NULL) {
10649             gameInfo.result = result;
10650             gameInfo.resultDetails = StrSave(resultDetails);
10651
10652             /* display last move only if game was not loaded from file */
10653             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10654                 DisplayMove(currentMove - 1);
10655
10656             if (forwardMostMove != 0) {
10657                 if (gameMode != PlayFromGameFile && gameMode != EditGame
10658                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10659                                                                 ) {
10660                     if (*appData.saveGameFile != NULLCHAR) {
10661                         SaveGameToFile(appData.saveGameFile, TRUE);
10662                     } else if (appData.autoSaveGames) {
10663                         AutoSaveGame();
10664                     }
10665                     if (*appData.savePositionFile != NULLCHAR) {
10666                         SavePositionToFile(appData.savePositionFile);
10667                     }
10668                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
10669                 }
10670             }
10671
10672             /* Tell program how game ended in case it is learning */
10673             /* [HGM] Moved this to after saving the PGN, just in case */
10674             /* engine died and we got here through time loss. In that */
10675             /* case we will get a fatal error writing the pipe, which */
10676             /* would otherwise lose us the PGN.                       */
10677             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
10678             /* output during GameEnds should never be fatal anymore   */
10679             if (gameMode == MachinePlaysWhite ||
10680                 gameMode == MachinePlaysBlack ||
10681                 gameMode == TwoMachinesPlay ||
10682                 gameMode == IcsPlayingWhite ||
10683                 gameMode == IcsPlayingBlack ||
10684                 gameMode == BeginningOfGame) {
10685                 char buf[MSG_SIZ];
10686                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10687                         resultDetails);
10688                 if (first.pr != NoProc) {
10689                     SendToProgram(buf, &first);
10690                 }
10691                 if (second.pr != NoProc &&
10692                     gameMode == TwoMachinesPlay) {
10693                     SendToProgram(buf, &second);
10694                 }
10695             }
10696         }
10697
10698         if (appData.icsActive) {
10699             if (appData.quietPlay &&
10700                 (gameMode == IcsPlayingWhite ||
10701                  gameMode == IcsPlayingBlack)) {
10702                 SendToICS(ics_prefix);
10703                 SendToICS("set shout 1\n");
10704             }
10705             nextGameMode = IcsIdle;
10706             ics_user_moved = FALSE;
10707             /* clean up premove.  It's ugly when the game has ended and the
10708              * premove highlights are still on the board.
10709              */
10710             if (gotPremove) {
10711               gotPremove = FALSE;
10712               ClearPremoveHighlights();
10713               DrawPosition(FALSE, boards[currentMove]);
10714             }
10715             if (whosays == GE_ICS) {
10716                 switch (result) {
10717                 case WhiteWins:
10718                     if (gameMode == IcsPlayingWhite)
10719                         PlayIcsWinSound();
10720                     else if(gameMode == IcsPlayingBlack)
10721                         PlayIcsLossSound();
10722                     break;
10723                 case BlackWins:
10724                     if (gameMode == IcsPlayingBlack)
10725                         PlayIcsWinSound();
10726                     else if(gameMode == IcsPlayingWhite)
10727                         PlayIcsLossSound();
10728                     break;
10729                 case GameIsDrawn:
10730                     PlayIcsDrawSound();
10731                     break;
10732                 default:
10733                     PlayIcsUnfinishedSound();
10734                 }
10735             }
10736         } else if (gameMode == EditGame ||
10737                    gameMode == PlayFromGameFile ||
10738                    gameMode == AnalyzeMode ||
10739                    gameMode == AnalyzeFile) {
10740             nextGameMode = gameMode;
10741         } else {
10742             nextGameMode = EndOfGame;
10743         }
10744         pausing = FALSE;
10745         ModeHighlight();
10746     } else {
10747         nextGameMode = gameMode;
10748     }
10749
10750     if (appData.noChessProgram) {
10751         gameMode = nextGameMode;
10752         ModeHighlight();
10753         endingGame = 0; /* [HGM] crash */
10754         return;
10755     }
10756
10757     if (first.reuse) {
10758         /* Put first chess program into idle state */
10759         if (first.pr != NoProc &&
10760             (gameMode == MachinePlaysWhite ||
10761              gameMode == MachinePlaysBlack ||
10762              gameMode == TwoMachinesPlay ||
10763              gameMode == IcsPlayingWhite ||
10764              gameMode == IcsPlayingBlack ||
10765              gameMode == BeginningOfGame)) {
10766             SendToProgram("force\n", &first);
10767             if (first.usePing) {
10768               char buf[MSG_SIZ];
10769               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10770               SendToProgram(buf, &first);
10771             }
10772         }
10773     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10774         /* Kill off first chess program */
10775         if (first.isr != NULL)
10776           RemoveInputSource(first.isr);
10777         first.isr = NULL;
10778
10779         if (first.pr != NoProc) {
10780             ExitAnalyzeMode();
10781             DoSleep( appData.delayBeforeQuit );
10782             SendToProgram("quit\n", &first);
10783             DoSleep( appData.delayAfterQuit );
10784             DestroyChildProcess(first.pr, first.useSigterm);
10785         }
10786         first.pr = NoProc;
10787     }
10788     if (second.reuse) {
10789         /* Put second chess program into idle state */
10790         if (second.pr != NoProc &&
10791             gameMode == TwoMachinesPlay) {
10792             SendToProgram("force\n", &second);
10793             if (second.usePing) {
10794               char buf[MSG_SIZ];
10795               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10796               SendToProgram(buf, &second);
10797             }
10798         }
10799     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10800         /* Kill off second chess program */
10801         if (second.isr != NULL)
10802           RemoveInputSource(second.isr);
10803         second.isr = NULL;
10804
10805         if (second.pr != NoProc) {
10806             DoSleep( appData.delayBeforeQuit );
10807             SendToProgram("quit\n", &second);
10808             DoSleep( appData.delayAfterQuit );
10809             DestroyChildProcess(second.pr, second.useSigterm);
10810         }
10811         second.pr = NoProc;
10812     }
10813
10814     if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10815         char resChar = '=';
10816         switch (result) {
10817         case WhiteWins:
10818           resChar = '+';
10819           if (first.twoMachinesColor[0] == 'w') {
10820             first.matchWins++;
10821           } else {
10822             second.matchWins++;
10823           }
10824           break;
10825         case BlackWins:
10826           resChar = '-';
10827           if (first.twoMachinesColor[0] == 'b') {
10828             first.matchWins++;
10829           } else {
10830             second.matchWins++;
10831           }
10832           break;
10833         case GameUnfinished:
10834           resChar = ' ';
10835         default:
10836           break;
10837         }
10838
10839         if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10840         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10841             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
10842             ReserveGame(nextGame, resChar); // sets nextGame
10843             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10844             else ranking = strdup("busy"); //suppress popup when aborted but not finished
10845         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10846
10847         if (nextGame <= appData.matchGames && !abortMatch) {
10848             gameMode = nextGameMode;
10849             matchGame = nextGame; // this will be overruled in tourney mode!
10850             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10851             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10852             endingGame = 0; /* [HGM] crash */
10853             return;
10854         } else {
10855             gameMode = nextGameMode;
10856             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10857                      first.tidy, second.tidy,
10858                      first.matchWins, second.matchWins,
10859                      appData.matchGames - (first.matchWins + second.matchWins));
10860             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
10861             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
10862             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10863             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10864                 first.twoMachinesColor = "black\n";
10865                 second.twoMachinesColor = "white\n";
10866             } else {
10867                 first.twoMachinesColor = "white\n";
10868                 second.twoMachinesColor = "black\n";
10869             }
10870         }
10871     }
10872     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10873         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10874       ExitAnalyzeMode();
10875     gameMode = nextGameMode;
10876     ModeHighlight();
10877     endingGame = 0;  /* [HGM] crash */
10878     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10879         if(matchMode == TRUE) { // match through command line: exit with or without popup
10880             if(ranking) {
10881                 ToNrEvent(forwardMostMove);
10882                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10883                 else ExitEvent(0);
10884             } else DisplayFatalError(buf, 0, 0);
10885         } else { // match through menu; just stop, with or without popup
10886             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10887             ModeHighlight();
10888             if(ranking){
10889                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10890             } else DisplayNote(buf);
10891       }
10892       if(ranking) free(ranking);
10893     }
10894 }
10895
10896 /* Assumes program was just initialized (initString sent).
10897    Leaves program in force mode. */
10898 void
10899 FeedMovesToProgram (ChessProgramState *cps, int upto)
10900 {
10901     int i;
10902
10903     if (appData.debugMode)
10904       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10905               startedFromSetupPosition ? "position and " : "",
10906               backwardMostMove, upto, cps->which);
10907     if(currentlyInitializedVariant != gameInfo.variant) {
10908       char buf[MSG_SIZ];
10909         // [HGM] variantswitch: make engine aware of new variant
10910         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10911                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10912         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10913         SendToProgram(buf, cps);
10914         currentlyInitializedVariant = gameInfo.variant;
10915     }
10916     SendToProgram("force\n", cps);
10917     if (startedFromSetupPosition) {
10918         SendBoard(cps, backwardMostMove);
10919     if (appData.debugMode) {
10920         fprintf(debugFP, "feedMoves\n");
10921     }
10922     }
10923     for (i = backwardMostMove; i < upto; i++) {
10924         SendMoveToProgram(i, cps);
10925     }
10926 }
10927
10928
10929 int
10930 ResurrectChessProgram ()
10931 {
10932      /* The chess program may have exited.
10933         If so, restart it and feed it all the moves made so far. */
10934     static int doInit = 0;
10935
10936     if (appData.noChessProgram) return 1;
10937
10938     if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10939         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10940         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10941         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10942     } else {
10943         if (first.pr != NoProc) return 1;
10944         StartChessProgram(&first);
10945     }
10946     InitChessProgram(&first, FALSE);
10947     FeedMovesToProgram(&first, currentMove);
10948
10949     if (!first.sendTime) {
10950         /* can't tell gnuchess what its clock should read,
10951            so we bow to its notion. */
10952         ResetClocks();
10953         timeRemaining[0][currentMove] = whiteTimeRemaining;
10954         timeRemaining[1][currentMove] = blackTimeRemaining;
10955     }
10956
10957     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10958                 appData.icsEngineAnalyze) && first.analysisSupport) {
10959       SendToProgram("analyze\n", &first);
10960       first.analyzing = TRUE;
10961     }
10962     return 1;
10963 }
10964
10965 /*
10966  * Button procedures
10967  */
10968 void
10969 Reset (int redraw, int init)
10970 {
10971     int i;
10972
10973     if (appData.debugMode) {
10974         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10975                 redraw, init, gameMode);
10976     }
10977     CleanupTail(); // [HGM] vari: delete any stored variations
10978     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
10979     pausing = pauseExamInvalid = FALSE;
10980     startedFromSetupPosition = blackPlaysFirst = FALSE;
10981     firstMove = TRUE;
10982     whiteFlag = blackFlag = FALSE;
10983     userOfferedDraw = FALSE;
10984     hintRequested = bookRequested = FALSE;
10985     first.maybeThinking = FALSE;
10986     second.maybeThinking = FALSE;
10987     first.bookSuspend = FALSE; // [HGM] book
10988     second.bookSuspend = FALSE;
10989     thinkOutput[0] = NULLCHAR;
10990     lastHint[0] = NULLCHAR;
10991     ClearGameInfo(&gameInfo);
10992     gameInfo.variant = StringToVariant(appData.variant);
10993     ics_user_moved = ics_clock_paused = FALSE;
10994     ics_getting_history = H_FALSE;
10995     ics_gamenum = -1;
10996     white_holding[0] = black_holding[0] = NULLCHAR;
10997     ClearProgramStats();
10998     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10999
11000     ResetFrontEnd();
11001     ClearHighlights();
11002     flipView = appData.flipView;
11003     ClearPremoveHighlights();
11004     gotPremove = FALSE;
11005     alarmSounded = FALSE;
11006
11007     GameEnds(EndOfFile, NULL, GE_PLAYER);
11008     if(appData.serverMovesName != NULL) {
11009         /* [HGM] prepare to make moves file for broadcasting */
11010         clock_t t = clock();
11011         if(serverMoves != NULL) fclose(serverMoves);
11012         serverMoves = fopen(appData.serverMovesName, "r");
11013         if(serverMoves != NULL) {
11014             fclose(serverMoves);
11015             /* delay 15 sec before overwriting, so all clients can see end */
11016             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11017         }
11018         serverMoves = fopen(appData.serverMovesName, "w");
11019     }
11020
11021     ExitAnalyzeMode();
11022     gameMode = BeginningOfGame;
11023     ModeHighlight();
11024     if(appData.icsActive) gameInfo.variant = VariantNormal;
11025     currentMove = forwardMostMove = backwardMostMove = 0;
11026     MarkTargetSquares(1);
11027     InitPosition(redraw);
11028     for (i = 0; i < MAX_MOVES; i++) {
11029         if (commentList[i] != NULL) {
11030             free(commentList[i]);
11031             commentList[i] = NULL;
11032         }
11033     }
11034     ResetClocks();
11035     timeRemaining[0][0] = whiteTimeRemaining;
11036     timeRemaining[1][0] = blackTimeRemaining;
11037
11038     if (first.pr == NoProc) {
11039         StartChessProgram(&first);
11040     }
11041     if (init) {
11042             InitChessProgram(&first, startedFromSetupPosition);
11043     }
11044     DisplayTitle("");
11045     DisplayMessage("", "");
11046     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11047     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11048     ClearMap();        // [HGM] exclude: invalidate map
11049 }
11050
11051 void
11052 AutoPlayGameLoop ()
11053 {
11054     for (;;) {
11055         if (!AutoPlayOneMove())
11056           return;
11057         if (matchMode || appData.timeDelay == 0)
11058           continue;
11059         if (appData.timeDelay < 0)
11060           return;
11061         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11062         break;
11063     }
11064 }
11065
11066 void
11067 AnalyzeNextGame()
11068 {
11069     ReloadGame(1); // next game
11070 }
11071
11072 int
11073 AutoPlayOneMove ()
11074 {
11075     int fromX, fromY, toX, toY;
11076
11077     if (appData.debugMode) {
11078       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11079     }
11080
11081     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11082       return FALSE;
11083
11084     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
11085       pvInfoList[currentMove].depth = programStats.depth;
11086       pvInfoList[currentMove].score = programStats.score;
11087       pvInfoList[currentMove].time  = 0;
11088       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11089     }
11090
11091     if (currentMove >= forwardMostMove) {
11092       if(gameMode == AnalyzeFile) {
11093           if(appData.loadGameIndex == -1) {
11094             GameEnds(EndOfFile, NULL, GE_FILE);
11095           ScheduleDelayedEvent(AnalyzeNextGame, 10);
11096           } else {
11097           ExitAnalyzeMode(); SendToProgram("force\n", &first);
11098         }
11099       }
11100 //      gameMode = EndOfGame;
11101 //      ModeHighlight();
11102
11103       /* [AS] Clear current move marker at the end of a game */
11104       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11105
11106       return FALSE;
11107     }
11108
11109     toX = moveList[currentMove][2] - AAA;
11110     toY = moveList[currentMove][3] - ONE;
11111
11112     if (moveList[currentMove][1] == '@') {
11113         if (appData.highlightLastMove) {
11114             SetHighlights(-1, -1, toX, toY);
11115         }
11116     } else {
11117         fromX = moveList[currentMove][0] - AAA;
11118         fromY = moveList[currentMove][1] - ONE;
11119
11120         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11121
11122         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11123
11124         if (appData.highlightLastMove) {
11125             SetHighlights(fromX, fromY, toX, toY);
11126         }
11127     }
11128     DisplayMove(currentMove);
11129     SendMoveToProgram(currentMove++, &first);
11130     DisplayBothClocks();
11131     DrawPosition(FALSE, boards[currentMove]);
11132     // [HGM] PV info: always display, routine tests if empty
11133     DisplayComment(currentMove - 1, commentList[currentMove]);
11134     return TRUE;
11135 }
11136
11137
11138 int
11139 LoadGameOneMove (ChessMove readAhead)
11140 {
11141     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11142     char promoChar = NULLCHAR;
11143     ChessMove moveType;
11144     char move[MSG_SIZ];
11145     char *p, *q;
11146
11147     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11148         gameMode != AnalyzeMode && gameMode != Training) {
11149         gameFileFP = NULL;
11150         return FALSE;
11151     }
11152
11153     yyboardindex = forwardMostMove;
11154     if (readAhead != EndOfFile) {
11155       moveType = readAhead;
11156     } else {
11157       if (gameFileFP == NULL)
11158           return FALSE;
11159       moveType = (ChessMove) Myylex();
11160     }
11161
11162     done = FALSE;
11163     switch (moveType) {
11164       case Comment:
11165         if (appData.debugMode)
11166           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11167         p = yy_text;
11168
11169         /* append the comment but don't display it */
11170         AppendComment(currentMove, p, FALSE);
11171         return TRUE;
11172
11173       case WhiteCapturesEnPassant:
11174       case BlackCapturesEnPassant:
11175       case WhitePromotion:
11176       case BlackPromotion:
11177       case WhiteNonPromotion:
11178       case BlackNonPromotion:
11179       case NormalMove:
11180       case WhiteKingSideCastle:
11181       case WhiteQueenSideCastle:
11182       case BlackKingSideCastle:
11183       case BlackQueenSideCastle:
11184       case WhiteKingSideCastleWild:
11185       case WhiteQueenSideCastleWild:
11186       case BlackKingSideCastleWild:
11187       case BlackQueenSideCastleWild:
11188       /* PUSH Fabien */
11189       case WhiteHSideCastleFR:
11190       case WhiteASideCastleFR:
11191       case BlackHSideCastleFR:
11192       case BlackASideCastleFR:
11193       /* POP Fabien */
11194         if (appData.debugMode)
11195           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11196         fromX = currentMoveString[0] - AAA;
11197         fromY = currentMoveString[1] - ONE;
11198         toX = currentMoveString[2] - AAA;
11199         toY = currentMoveString[3] - ONE;
11200         promoChar = currentMoveString[4];
11201         break;
11202
11203       case WhiteDrop:
11204       case BlackDrop:
11205         if (appData.debugMode)
11206           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11207         fromX = moveType == WhiteDrop ?
11208           (int) CharToPiece(ToUpper(currentMoveString[0])) :
11209         (int) CharToPiece(ToLower(currentMoveString[0]));
11210         fromY = DROP_RANK;
11211         toX = currentMoveString[2] - AAA;
11212         toY = currentMoveString[3] - ONE;
11213         break;
11214
11215       case WhiteWins:
11216       case BlackWins:
11217       case GameIsDrawn:
11218       case GameUnfinished:
11219         if (appData.debugMode)
11220           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11221         p = strchr(yy_text, '{');
11222         if (p == NULL) p = strchr(yy_text, '(');
11223         if (p == NULL) {
11224             p = yy_text;
11225             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11226         } else {
11227             q = strchr(p, *p == '{' ? '}' : ')');
11228             if (q != NULL) *q = NULLCHAR;
11229             p++;
11230         }
11231         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11232         GameEnds(moveType, p, GE_FILE);
11233         done = TRUE;
11234         if (cmailMsgLoaded) {
11235             ClearHighlights();
11236             flipView = WhiteOnMove(currentMove);
11237             if (moveType == GameUnfinished) flipView = !flipView;
11238             if (appData.debugMode)
11239               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11240         }
11241         break;
11242
11243       case EndOfFile:
11244         if (appData.debugMode)
11245           fprintf(debugFP, "Parser hit end of file\n");
11246         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11247           case MT_NONE:
11248           case MT_CHECK:
11249             break;
11250           case MT_CHECKMATE:
11251           case MT_STAINMATE:
11252             if (WhiteOnMove(currentMove)) {
11253                 GameEnds(BlackWins, "Black mates", GE_FILE);
11254             } else {
11255                 GameEnds(WhiteWins, "White mates", GE_FILE);
11256             }
11257             break;
11258           case MT_STALEMATE:
11259             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11260             break;
11261         }
11262         done = TRUE;
11263         break;
11264
11265       case MoveNumberOne:
11266         if (lastLoadGameStart == GNUChessGame) {
11267             /* GNUChessGames have numbers, but they aren't move numbers */
11268             if (appData.debugMode)
11269               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11270                       yy_text, (int) moveType);
11271             return LoadGameOneMove(EndOfFile); /* tail recursion */
11272         }
11273         /* else fall thru */
11274
11275       case XBoardGame:
11276       case GNUChessGame:
11277       case PGNTag:
11278         /* Reached start of next game in file */
11279         if (appData.debugMode)
11280           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11281         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11282           case MT_NONE:
11283           case MT_CHECK:
11284             break;
11285           case MT_CHECKMATE:
11286           case MT_STAINMATE:
11287             if (WhiteOnMove(currentMove)) {
11288                 GameEnds(BlackWins, "Black mates", GE_FILE);
11289             } else {
11290                 GameEnds(WhiteWins, "White mates", GE_FILE);
11291             }
11292             break;
11293           case MT_STALEMATE:
11294             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11295             break;
11296         }
11297         done = TRUE;
11298         break;
11299
11300       case PositionDiagram:     /* should not happen; ignore */
11301       case ElapsedTime:         /* ignore */
11302       case NAG:                 /* ignore */
11303         if (appData.debugMode)
11304           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11305                   yy_text, (int) moveType);
11306         return LoadGameOneMove(EndOfFile); /* tail recursion */
11307
11308       case IllegalMove:
11309         if (appData.testLegality) {
11310             if (appData.debugMode)
11311               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11312             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11313                     (forwardMostMove / 2) + 1,
11314                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11315             DisplayError(move, 0);
11316             done = TRUE;
11317         } else {
11318             if (appData.debugMode)
11319               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11320                       yy_text, currentMoveString);
11321             fromX = currentMoveString[0] - AAA;
11322             fromY = currentMoveString[1] - ONE;
11323             toX = currentMoveString[2] - AAA;
11324             toY = currentMoveString[3] - ONE;
11325             promoChar = currentMoveString[4];
11326         }
11327         break;
11328
11329       case AmbiguousMove:
11330         if (appData.debugMode)
11331           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11332         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11333                 (forwardMostMove / 2) + 1,
11334                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11335         DisplayError(move, 0);
11336         done = TRUE;
11337         break;
11338
11339       default:
11340       case ImpossibleMove:
11341         if (appData.debugMode)
11342           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11343         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11344                 (forwardMostMove / 2) + 1,
11345                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11346         DisplayError(move, 0);
11347         done = TRUE;
11348         break;
11349     }
11350
11351     if (done) {
11352         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11353             DrawPosition(FALSE, boards[currentMove]);
11354             DisplayBothClocks();
11355             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11356               DisplayComment(currentMove - 1, commentList[currentMove]);
11357         }
11358         (void) StopLoadGameTimer();
11359         gameFileFP = NULL;
11360         cmailOldMove = forwardMostMove;
11361         return FALSE;
11362     } else {
11363         /* currentMoveString is set as a side-effect of yylex */
11364
11365         thinkOutput[0] = NULLCHAR;
11366         MakeMove(fromX, fromY, toX, toY, promoChar);
11367         currentMove = forwardMostMove;
11368         return TRUE;
11369     }
11370 }
11371
11372 /* Load the nth game from the given file */
11373 int
11374 LoadGameFromFile (char *filename, int n, char *title, int useList)
11375 {
11376     FILE *f;
11377     char buf[MSG_SIZ];
11378
11379     if (strcmp(filename, "-") == 0) {
11380         f = stdin;
11381         title = "stdin";
11382     } else {
11383         f = fopen(filename, "rb");
11384         if (f == NULL) {
11385           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
11386             DisplayError(buf, errno);
11387             return FALSE;
11388         }
11389     }
11390     if (fseek(f, 0, 0) == -1) {
11391         /* f is not seekable; probably a pipe */
11392         useList = FALSE;
11393     }
11394     if (useList && n == 0) {
11395         int error = GameListBuild(f);
11396         if (error) {
11397             DisplayError(_("Cannot build game list"), error);
11398         } else if (!ListEmpty(&gameList) &&
11399                    ((ListGame *) gameList.tailPred)->number > 1) {
11400             GameListPopUp(f, title);
11401             return TRUE;
11402         }
11403         GameListDestroy();
11404         n = 1;
11405     }
11406     if (n == 0) n = 1;
11407     return LoadGame(f, n, title, FALSE);
11408 }
11409
11410
11411 void
11412 MakeRegisteredMove ()
11413 {
11414     int fromX, fromY, toX, toY;
11415     char promoChar;
11416     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11417         switch (cmailMoveType[lastLoadGameNumber - 1]) {
11418           case CMAIL_MOVE:
11419           case CMAIL_DRAW:
11420             if (appData.debugMode)
11421               fprintf(debugFP, "Restoring %s for game %d\n",
11422                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11423
11424             thinkOutput[0] = NULLCHAR;
11425             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11426             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11427             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11428             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11429             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11430             promoChar = cmailMove[lastLoadGameNumber - 1][4];
11431             MakeMove(fromX, fromY, toX, toY, promoChar);
11432             ShowMove(fromX, fromY, toX, toY);
11433
11434             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11435               case MT_NONE:
11436               case MT_CHECK:
11437                 break;
11438
11439               case MT_CHECKMATE:
11440               case MT_STAINMATE:
11441                 if (WhiteOnMove(currentMove)) {
11442                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
11443                 } else {
11444                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
11445                 }
11446                 break;
11447
11448               case MT_STALEMATE:
11449                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11450                 break;
11451             }
11452
11453             break;
11454
11455           case CMAIL_RESIGN:
11456             if (WhiteOnMove(currentMove)) {
11457                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11458             } else {
11459                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11460             }
11461             break;
11462
11463           case CMAIL_ACCEPT:
11464             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11465             break;
11466
11467           default:
11468             break;
11469         }
11470     }
11471
11472     return;
11473 }
11474
11475 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11476 int
11477 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11478 {
11479     int retVal;
11480
11481     if (gameNumber > nCmailGames) {
11482         DisplayError(_("No more games in this message"), 0);
11483         return FALSE;
11484     }
11485     if (f == lastLoadGameFP) {
11486         int offset = gameNumber - lastLoadGameNumber;
11487         if (offset == 0) {
11488             cmailMsg[0] = NULLCHAR;
11489             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11490                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11491                 nCmailMovesRegistered--;
11492             }
11493             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11494             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11495                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11496             }
11497         } else {
11498             if (! RegisterMove()) return FALSE;
11499         }
11500     }
11501
11502     retVal = LoadGame(f, gameNumber, title, useList);
11503
11504     /* Make move registered during previous look at this game, if any */
11505     MakeRegisteredMove();
11506
11507     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11508         commentList[currentMove]
11509           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11510         DisplayComment(currentMove - 1, commentList[currentMove]);
11511     }
11512
11513     return retVal;
11514 }
11515
11516 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11517 int
11518 ReloadGame (int offset)
11519 {
11520     int gameNumber = lastLoadGameNumber + offset;
11521     if (lastLoadGameFP == NULL) {
11522         DisplayError(_("No game has been loaded yet"), 0);
11523         return FALSE;
11524     }
11525     if (gameNumber <= 0) {
11526         DisplayError(_("Can't back up any further"), 0);
11527         return FALSE;
11528     }
11529     if (cmailMsgLoaded) {
11530         return CmailLoadGame(lastLoadGameFP, gameNumber,
11531                              lastLoadGameTitle, lastLoadGameUseList);
11532     } else {
11533         return LoadGame(lastLoadGameFP, gameNumber,
11534                         lastLoadGameTitle, lastLoadGameUseList);
11535     }
11536 }
11537
11538 int keys[EmptySquare+1];
11539
11540 int
11541 PositionMatches (Board b1, Board b2)
11542 {
11543     int r, f, sum=0;
11544     switch(appData.searchMode) {
11545         case 1: return CompareWithRights(b1, b2);
11546         case 2:
11547             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11548                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11549             }
11550             return TRUE;
11551         case 3:
11552             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11553               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11554                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11555             }
11556             return sum==0;
11557         case 4:
11558             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11559                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11560             }
11561             return sum==0;
11562     }
11563     return TRUE;
11564 }
11565
11566 #define Q_PROMO  4
11567 #define Q_EP     3
11568 #define Q_BCASTL 2
11569 #define Q_WCASTL 1
11570
11571 int pieceList[256], quickBoard[256];
11572 ChessSquare pieceType[256] = { EmptySquare };
11573 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11574 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11575 int soughtTotal, turn;
11576 Boolean epOK, flipSearch;
11577
11578 typedef struct {
11579     unsigned char piece, to;
11580 } Move;
11581
11582 #define DSIZE (250000)
11583
11584 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11585 Move *moveDatabase = initialSpace;
11586 unsigned int movePtr, dataSize = DSIZE;
11587
11588 int
11589 MakePieceList (Board board, int *counts)
11590 {
11591     int r, f, n=Q_PROMO, total=0;
11592     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11593     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11594         int sq = f + (r<<4);
11595         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
11596             quickBoard[sq] = ++n;
11597             pieceList[n] = sq;
11598             pieceType[n] = board[r][f];
11599             counts[board[r][f]]++;
11600             if(board[r][f] == WhiteKing) pieceList[1] = n; else
11601             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
11602             total++;
11603         }
11604     }
11605     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
11606     return total;
11607 }
11608
11609 void
11610 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
11611 {
11612     int sq = fromX + (fromY<<4);
11613     int piece = quickBoard[sq];
11614     quickBoard[sq] = 0;
11615     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
11616     if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11617         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
11618         moveDatabase[movePtr++].piece = Q_WCASTL;
11619         quickBoard[sq] = piece;
11620         piece = quickBoard[from]; quickBoard[from] = 0;
11621         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11622     } else
11623     if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11624         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
11625         moveDatabase[movePtr++].piece = Q_BCASTL;
11626         quickBoard[sq] = piece;
11627         piece = quickBoard[from]; quickBoard[from] = 0;
11628         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11629     } else
11630     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
11631         quickBoard[(fromY<<4)+toX] = 0;
11632         moveDatabase[movePtr].piece = Q_EP;
11633         moveDatabase[movePtr++].to = (fromY<<4)+toX;
11634         moveDatabase[movePtr].to = sq;
11635     } else
11636     if(promoPiece != pieceType[piece]) {
11637         moveDatabase[movePtr++].piece = Q_PROMO;
11638         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
11639     }
11640     moveDatabase[movePtr].piece = piece;
11641     quickBoard[sq] = piece;
11642     movePtr++;
11643 }
11644
11645 int
11646 PackGame (Board board)
11647 {
11648     Move *newSpace = NULL;
11649     moveDatabase[movePtr].piece = 0; // terminate previous game
11650     if(movePtr > dataSize) {
11651         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
11652         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
11653         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
11654         if(newSpace) {
11655             int i;
11656             Move *p = moveDatabase, *q = newSpace;
11657             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
11658             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
11659             moveDatabase = newSpace;
11660         } else { // calloc failed, we must be out of memory. Too bad...
11661             dataSize = 0; // prevent calloc events for all subsequent games
11662             return 0;     // and signal this one isn't cached
11663         }
11664     }
11665     movePtr++;
11666     MakePieceList(board, counts);
11667     return movePtr;
11668 }
11669
11670 int
11671 QuickCompare (Board board, int *minCounts, int *maxCounts)
11672 {   // compare according to search mode
11673     int r, f;
11674     switch(appData.searchMode)
11675     {
11676       case 1: // exact position match
11677         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
11678         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11679             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11680         }
11681         break;
11682       case 2: // can have extra material on empty squares
11683         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11684             if(board[r][f] == EmptySquare) continue;
11685             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11686         }
11687         break;
11688       case 3: // material with exact Pawn structure
11689         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11690             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
11691             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11692         } // fall through to material comparison
11693       case 4: // exact material
11694         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
11695         break;
11696       case 6: // material range with given imbalance
11697         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
11698         // fall through to range comparison
11699       case 5: // material range
11700         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
11701     }
11702     return TRUE;
11703 }
11704
11705 int
11706 QuickScan (Board board, Move *move)
11707 {   // reconstruct game,and compare all positions in it
11708     int cnt=0, stretch=0, total = MakePieceList(board, counts);
11709     do {
11710         int piece = move->piece;
11711         int to = move->to, from = pieceList[piece];
11712         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
11713           if(!piece) return -1;
11714           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
11715             piece = (++move)->piece;
11716             from = pieceList[piece];
11717             counts[pieceType[piece]]--;
11718             pieceType[piece] = (ChessSquare) move->to;
11719             counts[move->to]++;
11720           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
11721             counts[pieceType[quickBoard[to]]]--;
11722             quickBoard[to] = 0; total--;
11723             move++;
11724             continue;
11725           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
11726             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
11727             from  = pieceList[piece]; // so this must be King
11728             quickBoard[from] = 0;
11729             pieceList[piece] = to;
11730             from = pieceList[(++move)->piece]; // for FRC this has to be done here
11731             quickBoard[from] = 0; // rook
11732             quickBoard[to] = piece;
11733             to = move->to; piece = move->piece;
11734             goto aftercastle;
11735           }
11736         }
11737         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
11738         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
11739         quickBoard[from] = 0;
11740       aftercastle:
11741         quickBoard[to] = piece;
11742         pieceList[piece] = to;
11743         cnt++; turn ^= 3;
11744         if(QuickCompare(soughtBoard, minSought, maxSought) ||
11745            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
11746            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
11747                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
11748           ) {
11749             static int lastCounts[EmptySquare+1];
11750             int i;
11751             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
11752             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
11753         } else stretch = 0;
11754         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
11755         move++;
11756     } while(1);
11757 }
11758
11759 void
11760 InitSearch ()
11761 {
11762     int r, f;
11763     flipSearch = FALSE;
11764     CopyBoard(soughtBoard, boards[currentMove]);
11765     soughtTotal = MakePieceList(soughtBoard, maxSought);
11766     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
11767     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
11768     CopyBoard(reverseBoard, boards[currentMove]);
11769     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11770         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
11771         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
11772         reverseBoard[r][f] = piece;
11773     }
11774     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3; 
11775     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
11776     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
11777                  || (boards[currentMove][CASTLING][2] == NoRights || 
11778                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
11779                  && (boards[currentMove][CASTLING][5] == NoRights || 
11780                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
11781       ) {
11782         flipSearch = TRUE;
11783         CopyBoard(flipBoard, soughtBoard);
11784         CopyBoard(rotateBoard, reverseBoard);
11785         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11786             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
11787             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
11788         }
11789     }
11790     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
11791     if(appData.searchMode >= 5) {
11792         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
11793         MakePieceList(soughtBoard, minSought);
11794         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
11795     }
11796     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
11797         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
11798 }
11799
11800 GameInfo dummyInfo;
11801 static int creatingBook;
11802
11803 int
11804 GameContainsPosition (FILE *f, ListGame *lg)
11805 {
11806     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
11807     int fromX, fromY, toX, toY;
11808     char promoChar;
11809     static int initDone=FALSE;
11810
11811     // weed out games based on numerical tag comparison
11812     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
11813     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
11814     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
11815     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
11816     if(!initDone) {
11817         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
11818         initDone = TRUE;
11819     }
11820     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen);
11821     else CopyBoard(boards[scratch], initialPosition); // default start position
11822     if(lg->moves) {
11823         turn = btm + 1;
11824         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
11825         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
11826     }
11827     if(btm) plyNr++;
11828     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11829     fseek(f, lg->offset, 0);
11830     yynewfile(f);
11831     while(1) {
11832         yyboardindex = scratch;
11833         quickFlag = plyNr+1;
11834         next = Myylex();
11835         quickFlag = 0;
11836         switch(next) {
11837             case PGNTag:
11838                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
11839             default:
11840                 continue;
11841
11842             case XBoardGame:
11843             case GNUChessGame:
11844                 if(plyNr) return -1; // after we have seen moves, this is for new game
11845               continue;
11846
11847             case AmbiguousMove: // we cannot reconstruct the game beyond these two
11848             case ImpossibleMove:
11849             case WhiteWins: // game ends here with these four
11850             case BlackWins:
11851             case GameIsDrawn:
11852             case GameUnfinished:
11853                 return -1;
11854
11855             case IllegalMove:
11856                 if(appData.testLegality) return -1;
11857             case WhiteCapturesEnPassant:
11858             case BlackCapturesEnPassant:
11859             case WhitePromotion:
11860             case BlackPromotion:
11861             case WhiteNonPromotion:
11862             case BlackNonPromotion:
11863             case NormalMove:
11864             case WhiteKingSideCastle:
11865             case WhiteQueenSideCastle:
11866             case BlackKingSideCastle:
11867             case BlackQueenSideCastle:
11868             case WhiteKingSideCastleWild:
11869             case WhiteQueenSideCastleWild:
11870             case BlackKingSideCastleWild:
11871             case BlackQueenSideCastleWild:
11872             case WhiteHSideCastleFR:
11873             case WhiteASideCastleFR:
11874             case BlackHSideCastleFR:
11875             case BlackASideCastleFR:
11876                 fromX = currentMoveString[0] - AAA;
11877                 fromY = currentMoveString[1] - ONE;
11878                 toX = currentMoveString[2] - AAA;
11879                 toY = currentMoveString[3] - ONE;
11880                 promoChar = currentMoveString[4];
11881                 break;
11882             case WhiteDrop:
11883             case BlackDrop:
11884                 fromX = next == WhiteDrop ?
11885                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
11886                   (int) CharToPiece(ToLower(currentMoveString[0]));
11887                 fromY = DROP_RANK;
11888                 toX = currentMoveString[2] - AAA;
11889                 toY = currentMoveString[3] - ONE;
11890                 promoChar = 0;
11891                 break;
11892         }
11893         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
11894         plyNr++;
11895         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
11896         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11897         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
11898         if(appData.findMirror) {
11899             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
11900             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
11901         }
11902     }
11903 }
11904
11905 /* Load the nth game from open file f */
11906 int
11907 LoadGame (FILE *f, int gameNumber, char *title, int useList)
11908 {
11909     ChessMove cm;
11910     char buf[MSG_SIZ];
11911     int gn = gameNumber;
11912     ListGame *lg = NULL;
11913     int numPGNTags = 0;
11914     int err, pos = -1;
11915     GameMode oldGameMode;
11916     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
11917
11918     if (appData.debugMode)
11919         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
11920
11921     if (gameMode == Training )
11922         SetTrainingModeOff();
11923
11924     oldGameMode = gameMode;
11925     if (gameMode != BeginningOfGame) {
11926       Reset(FALSE, TRUE);
11927     }
11928
11929     gameFileFP = f;
11930     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
11931         fclose(lastLoadGameFP);
11932     }
11933
11934     if (useList) {
11935         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
11936
11937         if (lg) {
11938             fseek(f, lg->offset, 0);
11939             GameListHighlight(gameNumber);
11940             pos = lg->position;
11941             gn = 1;
11942         }
11943         else {
11944             if(gameMode == AnalyzeFile && appData.loadGameIndex == -1)
11945               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
11946             else
11947             DisplayError(_("Game number out of range"), 0);
11948             return FALSE;
11949         }
11950     } else {
11951         GameListDestroy();
11952         if (fseek(f, 0, 0) == -1) {
11953             if (f == lastLoadGameFP ?
11954                 gameNumber == lastLoadGameNumber + 1 :
11955                 gameNumber == 1) {
11956                 gn = 1;
11957             } else {
11958                 DisplayError(_("Can't seek on game file"), 0);
11959                 return FALSE;
11960             }
11961         }
11962     }
11963     lastLoadGameFP = f;
11964     lastLoadGameNumber = gameNumber;
11965     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
11966     lastLoadGameUseList = useList;
11967
11968     yynewfile(f);
11969
11970     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
11971       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
11972                 lg->gameInfo.black);
11973             DisplayTitle(buf);
11974     } else if (*title != NULLCHAR) {
11975         if (gameNumber > 1) {
11976           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
11977             DisplayTitle(buf);
11978         } else {
11979             DisplayTitle(title);
11980         }
11981     }
11982
11983     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
11984         gameMode = PlayFromGameFile;
11985         ModeHighlight();
11986     }
11987
11988     currentMove = forwardMostMove = backwardMostMove = 0;
11989     CopyBoard(boards[0], initialPosition);
11990     StopClocks();
11991
11992     /*
11993      * Skip the first gn-1 games in the file.
11994      * Also skip over anything that precedes an identifiable
11995      * start of game marker, to avoid being confused by
11996      * garbage at the start of the file.  Currently
11997      * recognized start of game markers are the move number "1",
11998      * the pattern "gnuchess .* game", the pattern
11999      * "^[#;%] [^ ]* game file", and a PGN tag block.
12000      * A game that starts with one of the latter two patterns
12001      * will also have a move number 1, possibly
12002      * following a position diagram.
12003      * 5-4-02: Let's try being more lenient and allowing a game to
12004      * start with an unnumbered move.  Does that break anything?
12005      */
12006     cm = lastLoadGameStart = EndOfFile;
12007     while (gn > 0) {
12008         yyboardindex = forwardMostMove;
12009         cm = (ChessMove) Myylex();
12010         switch (cm) {
12011           case EndOfFile:
12012             if (cmailMsgLoaded) {
12013                 nCmailGames = CMAIL_MAX_GAMES - gn;
12014             } else {
12015                 Reset(TRUE, TRUE);
12016                 DisplayError(_("Game not found in file"), 0);
12017             }
12018             return FALSE;
12019
12020           case GNUChessGame:
12021           case XBoardGame:
12022             gn--;
12023             lastLoadGameStart = cm;
12024             break;
12025
12026           case MoveNumberOne:
12027             switch (lastLoadGameStart) {
12028               case GNUChessGame:
12029               case XBoardGame:
12030               case PGNTag:
12031                 break;
12032               case MoveNumberOne:
12033               case EndOfFile:
12034                 gn--;           /* count this game */
12035                 lastLoadGameStart = cm;
12036                 break;
12037               default:
12038                 /* impossible */
12039                 break;
12040             }
12041             break;
12042
12043           case PGNTag:
12044             switch (lastLoadGameStart) {
12045               case GNUChessGame:
12046               case PGNTag:
12047               case MoveNumberOne:
12048               case EndOfFile:
12049                 gn--;           /* count this game */
12050                 lastLoadGameStart = cm;
12051                 break;
12052               case XBoardGame:
12053                 lastLoadGameStart = cm; /* game counted already */
12054                 break;
12055               default:
12056                 /* impossible */
12057                 break;
12058             }
12059             if (gn > 0) {
12060                 do {
12061                     yyboardindex = forwardMostMove;
12062                     cm = (ChessMove) Myylex();
12063                 } while (cm == PGNTag || cm == Comment);
12064             }
12065             break;
12066
12067           case WhiteWins:
12068           case BlackWins:
12069           case GameIsDrawn:
12070             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
12071                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
12072                     != CMAIL_OLD_RESULT) {
12073                     nCmailResults ++ ;
12074                     cmailResult[  CMAIL_MAX_GAMES
12075                                 - gn - 1] = CMAIL_OLD_RESULT;
12076                 }
12077             }
12078             break;
12079
12080           case NormalMove:
12081             /* Only a NormalMove can be at the start of a game
12082              * without a position diagram. */
12083             if (lastLoadGameStart == EndOfFile ) {
12084               gn--;
12085               lastLoadGameStart = MoveNumberOne;
12086             }
12087             break;
12088
12089           default:
12090             break;
12091         }
12092     }
12093
12094     if (appData.debugMode)
12095       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
12096
12097     if (cm == XBoardGame) {
12098         /* Skip any header junk before position diagram and/or move 1 */
12099         for (;;) {
12100             yyboardindex = forwardMostMove;
12101             cm = (ChessMove) Myylex();
12102
12103             if (cm == EndOfFile ||
12104                 cm == GNUChessGame || cm == XBoardGame) {
12105                 /* Empty game; pretend end-of-file and handle later */
12106                 cm = EndOfFile;
12107                 break;
12108             }
12109
12110             if (cm == MoveNumberOne || cm == PositionDiagram ||
12111                 cm == PGNTag || cm == Comment)
12112               break;
12113         }
12114     } else if (cm == GNUChessGame) {
12115         if (gameInfo.event != NULL) {
12116             free(gameInfo.event);
12117         }
12118         gameInfo.event = StrSave(yy_text);
12119     }
12120
12121     startedFromSetupPosition = FALSE;
12122     while (cm == PGNTag) {
12123         if (appData.debugMode)
12124           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
12125         err = ParsePGNTag(yy_text, &gameInfo);
12126         if (!err) numPGNTags++;
12127
12128         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
12129         if(gameInfo.variant != oldVariant) {
12130             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
12131             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
12132             InitPosition(TRUE);
12133             oldVariant = gameInfo.variant;
12134             if (appData.debugMode)
12135               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
12136         }
12137
12138
12139         if (gameInfo.fen != NULL) {
12140           Board initial_position;
12141           startedFromSetupPosition = TRUE;
12142           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
12143             Reset(TRUE, TRUE);
12144             DisplayError(_("Bad FEN position in file"), 0);
12145             return FALSE;
12146           }
12147           CopyBoard(boards[0], initial_position);
12148           if (blackPlaysFirst) {
12149             currentMove = forwardMostMove = backwardMostMove = 1;
12150             CopyBoard(boards[1], initial_position);
12151             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12152             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12153             timeRemaining[0][1] = whiteTimeRemaining;
12154             timeRemaining[1][1] = blackTimeRemaining;
12155             if (commentList[0] != NULL) {
12156               commentList[1] = commentList[0];
12157               commentList[0] = NULL;
12158             }
12159           } else {
12160             currentMove = forwardMostMove = backwardMostMove = 0;
12161           }
12162           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12163           {   int i;
12164               initialRulePlies = FENrulePlies;
12165               for( i=0; i< nrCastlingRights; i++ )
12166                   initialRights[i] = initial_position[CASTLING][i];
12167           }
12168           yyboardindex = forwardMostMove;
12169           free(gameInfo.fen);
12170           gameInfo.fen = NULL;
12171         }
12172
12173         yyboardindex = forwardMostMove;
12174         cm = (ChessMove) Myylex();
12175
12176         /* Handle comments interspersed among the tags */
12177         while (cm == Comment) {
12178             char *p;
12179             if (appData.debugMode)
12180               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12181             p = yy_text;
12182             AppendComment(currentMove, p, FALSE);
12183             yyboardindex = forwardMostMove;
12184             cm = (ChessMove) Myylex();
12185         }
12186     }
12187
12188     /* don't rely on existence of Event tag since if game was
12189      * pasted from clipboard the Event tag may not exist
12190      */
12191     if (numPGNTags > 0){
12192         char *tags;
12193         if (gameInfo.variant == VariantNormal) {
12194           VariantClass v = StringToVariant(gameInfo.event);
12195           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12196           if(v < VariantShogi) gameInfo.variant = v;
12197         }
12198         if (!matchMode) {
12199           if( appData.autoDisplayTags ) {
12200             tags = PGNTags(&gameInfo);
12201             TagsPopUp(tags, CmailMsg());
12202             free(tags);
12203           }
12204         }
12205     } else {
12206         /* Make something up, but don't display it now */
12207         SetGameInfo();
12208         TagsPopDown();
12209     }
12210
12211     if (cm == PositionDiagram) {
12212         int i, j;
12213         char *p;
12214         Board initial_position;
12215
12216         if (appData.debugMode)
12217           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12218
12219         if (!startedFromSetupPosition) {
12220             p = yy_text;
12221             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12222               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12223                 switch (*p) {
12224                   case '{':
12225                   case '[':
12226                   case '-':
12227                   case ' ':
12228                   case '\t':
12229                   case '\n':
12230                   case '\r':
12231                     break;
12232                   default:
12233                     initial_position[i][j++] = CharToPiece(*p);
12234                     break;
12235                 }
12236             while (*p == ' ' || *p == '\t' ||
12237                    *p == '\n' || *p == '\r') p++;
12238
12239             if (strncmp(p, "black", strlen("black"))==0)
12240               blackPlaysFirst = TRUE;
12241             else
12242               blackPlaysFirst = FALSE;
12243             startedFromSetupPosition = TRUE;
12244
12245             CopyBoard(boards[0], initial_position);
12246             if (blackPlaysFirst) {
12247                 currentMove = forwardMostMove = backwardMostMove = 1;
12248                 CopyBoard(boards[1], initial_position);
12249                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12250                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12251                 timeRemaining[0][1] = whiteTimeRemaining;
12252                 timeRemaining[1][1] = blackTimeRemaining;
12253                 if (commentList[0] != NULL) {
12254                     commentList[1] = commentList[0];
12255                     commentList[0] = NULL;
12256                 }
12257             } else {
12258                 currentMove = forwardMostMove = backwardMostMove = 0;
12259             }
12260         }
12261         yyboardindex = forwardMostMove;
12262         cm = (ChessMove) Myylex();
12263     }
12264
12265   if(!creatingBook) {
12266     if (first.pr == NoProc) {
12267         StartChessProgram(&first);
12268     }
12269     InitChessProgram(&first, FALSE);
12270     SendToProgram("force\n", &first);
12271     if (startedFromSetupPosition) {
12272         SendBoard(&first, forwardMostMove);
12273     if (appData.debugMode) {
12274         fprintf(debugFP, "Load Game\n");
12275     }
12276         DisplayBothClocks();
12277     }
12278   }
12279
12280     /* [HGM] server: flag to write setup moves in broadcast file as one */
12281     loadFlag = appData.suppressLoadMoves;
12282
12283     while (cm == Comment) {
12284         char *p;
12285         if (appData.debugMode)
12286           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12287         p = yy_text;
12288         AppendComment(currentMove, p, FALSE);
12289         yyboardindex = forwardMostMove;
12290         cm = (ChessMove) Myylex();
12291     }
12292
12293     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12294         cm == WhiteWins || cm == BlackWins ||
12295         cm == GameIsDrawn || cm == GameUnfinished) {
12296         DisplayMessage("", _("No moves in game"));
12297         if (cmailMsgLoaded) {
12298             if (appData.debugMode)
12299               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12300             ClearHighlights();
12301             flipView = FALSE;
12302         }
12303         DrawPosition(FALSE, boards[currentMove]);
12304         DisplayBothClocks();
12305         gameMode = EditGame;
12306         ModeHighlight();
12307         gameFileFP = NULL;
12308         cmailOldMove = 0;
12309         return TRUE;
12310     }
12311
12312     // [HGM] PV info: routine tests if comment empty
12313     if (!matchMode && (pausing || appData.timeDelay != 0)) {
12314         DisplayComment(currentMove - 1, commentList[currentMove]);
12315     }
12316     if (!matchMode && appData.timeDelay != 0)
12317       DrawPosition(FALSE, boards[currentMove]);
12318
12319     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12320       programStats.ok_to_send = 1;
12321     }
12322
12323     /* if the first token after the PGN tags is a move
12324      * and not move number 1, retrieve it from the parser
12325      */
12326     if (cm != MoveNumberOne)
12327         LoadGameOneMove(cm);
12328
12329     /* load the remaining moves from the file */
12330     while (LoadGameOneMove(EndOfFile)) {
12331       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12332       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12333     }
12334
12335     /* rewind to the start of the game */
12336     currentMove = backwardMostMove;
12337
12338     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12339
12340     if (oldGameMode == AnalyzeFile ||
12341         oldGameMode == AnalyzeMode) {
12342       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
12343       keepInfo = 1;
12344       AnalyzeFileEvent();
12345       keepInfo = 0;
12346     }
12347
12348     if(creatingBook) return TRUE;
12349     if (!matchMode && pos > 0) {
12350         ToNrEvent(pos); // [HGM] no autoplay if selected on position
12351     } else
12352     if (matchMode || appData.timeDelay == 0) {
12353       ToEndEvent();
12354     } else if (appData.timeDelay > 0) {
12355       AutoPlayGameLoop();
12356     }
12357
12358     if (appData.debugMode)
12359         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
12360
12361     loadFlag = 0; /* [HGM] true game starts */
12362     return TRUE;
12363 }
12364
12365 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
12366 int
12367 ReloadPosition (int offset)
12368 {
12369     int positionNumber = lastLoadPositionNumber + offset;
12370     if (lastLoadPositionFP == NULL) {
12371         DisplayError(_("No position has been loaded yet"), 0);
12372         return FALSE;
12373     }
12374     if (positionNumber <= 0) {
12375         DisplayError(_("Can't back up any further"), 0);
12376         return FALSE;
12377     }
12378     return LoadPosition(lastLoadPositionFP, positionNumber,
12379                         lastLoadPositionTitle);
12380 }
12381
12382 /* Load the nth position from the given file */
12383 int
12384 LoadPositionFromFile (char *filename, int n, char *title)
12385 {
12386     FILE *f;
12387     char buf[MSG_SIZ];
12388
12389     if (strcmp(filename, "-") == 0) {
12390         return LoadPosition(stdin, n, "stdin");
12391     } else {
12392         f = fopen(filename, "rb");
12393         if (f == NULL) {
12394             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12395             DisplayError(buf, errno);
12396             return FALSE;
12397         } else {
12398             return LoadPosition(f, n, title);
12399         }
12400     }
12401 }
12402
12403 /* Load the nth position from the given open file, and close it */
12404 int
12405 LoadPosition (FILE *f, int positionNumber, char *title)
12406 {
12407     char *p, line[MSG_SIZ];
12408     Board initial_position;
12409     int i, j, fenMode, pn;
12410
12411     if (gameMode == Training )
12412         SetTrainingModeOff();
12413
12414     if (gameMode != BeginningOfGame) {
12415         Reset(FALSE, TRUE);
12416     }
12417     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12418         fclose(lastLoadPositionFP);
12419     }
12420     if (positionNumber == 0) positionNumber = 1;
12421     lastLoadPositionFP = f;
12422     lastLoadPositionNumber = positionNumber;
12423     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12424     if (first.pr == NoProc && !appData.noChessProgram) {
12425       StartChessProgram(&first);
12426       InitChessProgram(&first, FALSE);
12427     }
12428     pn = positionNumber;
12429     if (positionNumber < 0) {
12430         /* Negative position number means to seek to that byte offset */
12431         if (fseek(f, -positionNumber, 0) == -1) {
12432             DisplayError(_("Can't seek on position file"), 0);
12433             return FALSE;
12434         };
12435         pn = 1;
12436     } else {
12437         if (fseek(f, 0, 0) == -1) {
12438             if (f == lastLoadPositionFP ?
12439                 positionNumber == lastLoadPositionNumber + 1 :
12440                 positionNumber == 1) {
12441                 pn = 1;
12442             } else {
12443                 DisplayError(_("Can't seek on position file"), 0);
12444                 return FALSE;
12445             }
12446         }
12447     }
12448     /* See if this file is FEN or old-style xboard */
12449     if (fgets(line, MSG_SIZ, f) == NULL) {
12450         DisplayError(_("Position not found in file"), 0);
12451         return FALSE;
12452     }
12453     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12454     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12455
12456     if (pn >= 2) {
12457         if (fenMode || line[0] == '#') pn--;
12458         while (pn > 0) {
12459             /* skip positions before number pn */
12460             if (fgets(line, MSG_SIZ, f) == NULL) {
12461                 Reset(TRUE, TRUE);
12462                 DisplayError(_("Position not found in file"), 0);
12463                 return FALSE;
12464             }
12465             if (fenMode || line[0] == '#') pn--;
12466         }
12467     }
12468
12469     if (fenMode) {
12470         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
12471             DisplayError(_("Bad FEN position in file"), 0);
12472             return FALSE;
12473         }
12474     } else {
12475         (void) fgets(line, MSG_SIZ, f);
12476         (void) fgets(line, MSG_SIZ, f);
12477
12478         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12479             (void) fgets(line, MSG_SIZ, f);
12480             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12481                 if (*p == ' ')
12482                   continue;
12483                 initial_position[i][j++] = CharToPiece(*p);
12484             }
12485         }
12486
12487         blackPlaysFirst = FALSE;
12488         if (!feof(f)) {
12489             (void) fgets(line, MSG_SIZ, f);
12490             if (strncmp(line, "black", strlen("black"))==0)
12491               blackPlaysFirst = TRUE;
12492         }
12493     }
12494     startedFromSetupPosition = TRUE;
12495
12496     CopyBoard(boards[0], initial_position);
12497     if (blackPlaysFirst) {
12498         currentMove = forwardMostMove = backwardMostMove = 1;
12499         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12500         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12501         CopyBoard(boards[1], initial_position);
12502         DisplayMessage("", _("Black to play"));
12503     } else {
12504         currentMove = forwardMostMove = backwardMostMove = 0;
12505         DisplayMessage("", _("White to play"));
12506     }
12507     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12508     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12509         SendToProgram("force\n", &first);
12510         SendBoard(&first, forwardMostMove);
12511     }
12512     if (appData.debugMode) {
12513 int i, j;
12514   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12515   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12516         fprintf(debugFP, "Load Position\n");
12517     }
12518
12519     if (positionNumber > 1) {
12520       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12521         DisplayTitle(line);
12522     } else {
12523         DisplayTitle(title);
12524     }
12525     gameMode = EditGame;
12526     ModeHighlight();
12527     ResetClocks();
12528     timeRemaining[0][1] = whiteTimeRemaining;
12529     timeRemaining[1][1] = blackTimeRemaining;
12530     DrawPosition(FALSE, boards[currentMove]);
12531
12532     return TRUE;
12533 }
12534
12535
12536 void
12537 CopyPlayerNameIntoFileName (char **dest, char *src)
12538 {
12539     while (*src != NULLCHAR && *src != ',') {
12540         if (*src == ' ') {
12541             *(*dest)++ = '_';
12542             src++;
12543         } else {
12544             *(*dest)++ = *src++;
12545         }
12546     }
12547 }
12548
12549 char *
12550 DefaultFileName (char *ext)
12551 {
12552     static char def[MSG_SIZ];
12553     char *p;
12554
12555     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12556         p = def;
12557         CopyPlayerNameIntoFileName(&p, gameInfo.white);
12558         *p++ = '-';
12559         CopyPlayerNameIntoFileName(&p, gameInfo.black);
12560         *p++ = '.';
12561         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12562     } else {
12563         def[0] = NULLCHAR;
12564     }
12565     return def;
12566 }
12567
12568 /* Save the current game to the given file */
12569 int
12570 SaveGameToFile (char *filename, int append)
12571 {
12572     FILE *f;
12573     char buf[MSG_SIZ];
12574     int result, i, t,tot=0;
12575
12576     if (strcmp(filename, "-") == 0) {
12577         return SaveGame(stdout, 0, NULL);
12578     } else {
12579         for(i=0; i<10; i++) { // upto 10 tries
12580              f = fopen(filename, append ? "a" : "w");
12581              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12582              if(f || errno != 13) break;
12583              DoSleep(t = 5 + random()%11); // wait 5-15 msec
12584              tot += t;
12585         }
12586         if (f == NULL) {
12587             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12588             DisplayError(buf, errno);
12589             return FALSE;
12590         } else {
12591             safeStrCpy(buf, lastMsg, MSG_SIZ);
12592             DisplayMessage(_("Waiting for access to save file"), "");
12593             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
12594             DisplayMessage(_("Saving game"), "");
12595             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
12596             result = SaveGame(f, 0, NULL);
12597             DisplayMessage(buf, "");
12598             return result;
12599         }
12600     }
12601 }
12602
12603 char *
12604 SavePart (char *str)
12605 {
12606     static char buf[MSG_SIZ];
12607     char *p;
12608
12609     p = strchr(str, ' ');
12610     if (p == NULL) return str;
12611     strncpy(buf, str, p - str);
12612     buf[p - str] = NULLCHAR;
12613     return buf;
12614 }
12615
12616 #define PGN_MAX_LINE 75
12617
12618 #define PGN_SIDE_WHITE  0
12619 #define PGN_SIDE_BLACK  1
12620
12621 static int
12622 FindFirstMoveOutOfBook (int side)
12623 {
12624     int result = -1;
12625
12626     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12627         int index = backwardMostMove;
12628         int has_book_hit = 0;
12629
12630         if( (index % 2) != side ) {
12631             index++;
12632         }
12633
12634         while( index < forwardMostMove ) {
12635             /* Check to see if engine is in book */
12636             int depth = pvInfoList[index].depth;
12637             int score = pvInfoList[index].score;
12638             int in_book = 0;
12639
12640             if( depth <= 2 ) {
12641                 in_book = 1;
12642             }
12643             else if( score == 0 && depth == 63 ) {
12644                 in_book = 1; /* Zappa */
12645             }
12646             else if( score == 2 && depth == 99 ) {
12647                 in_book = 1; /* Abrok */
12648             }
12649
12650             has_book_hit += in_book;
12651
12652             if( ! in_book ) {
12653                 result = index;
12654
12655                 break;
12656             }
12657
12658             index += 2;
12659         }
12660     }
12661
12662     return result;
12663 }
12664
12665 void
12666 GetOutOfBookInfo (char * buf)
12667 {
12668     int oob[2];
12669     int i;
12670     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12671
12672     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12673     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12674
12675     *buf = '\0';
12676
12677     if( oob[0] >= 0 || oob[1] >= 0 ) {
12678         for( i=0; i<2; i++ ) {
12679             int idx = oob[i];
12680
12681             if( idx >= 0 ) {
12682                 if( i > 0 && oob[0] >= 0 ) {
12683                     strcat( buf, "   " );
12684                 }
12685
12686                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12687                 sprintf( buf+strlen(buf), "%s%.2f",
12688                     pvInfoList[idx].score >= 0 ? "+" : "",
12689                     pvInfoList[idx].score / 100.0 );
12690             }
12691         }
12692     }
12693 }
12694
12695 /* Save game in PGN style and close the file */
12696 int
12697 SaveGamePGN (FILE *f)
12698 {
12699     int i, offset, linelen, newblock;
12700 //    char *movetext;
12701     char numtext[32];
12702     int movelen, numlen, blank;
12703     char move_buffer[100]; /* [AS] Buffer for move+PV info */
12704
12705     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12706
12707     PrintPGNTags(f, &gameInfo);
12708
12709     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
12710
12711     if (backwardMostMove > 0 || startedFromSetupPosition) {
12712         char *fen = PositionToFEN(backwardMostMove, NULL);
12713         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12714         fprintf(f, "\n{--------------\n");
12715         PrintPosition(f, backwardMostMove);
12716         fprintf(f, "--------------}\n");
12717         free(fen);
12718     }
12719     else {
12720         /* [AS] Out of book annotation */
12721         if( appData.saveOutOfBookInfo ) {
12722             char buf[64];
12723
12724             GetOutOfBookInfo( buf );
12725
12726             if( buf[0] != '\0' ) {
12727                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12728             }
12729         }
12730
12731         fprintf(f, "\n");
12732     }
12733
12734     i = backwardMostMove;
12735     linelen = 0;
12736     newblock = TRUE;
12737
12738     while (i < forwardMostMove) {
12739         /* Print comments preceding this move */
12740         if (commentList[i] != NULL) {
12741             if (linelen > 0) fprintf(f, "\n");
12742             fprintf(f, "%s", commentList[i]);
12743             linelen = 0;
12744             newblock = TRUE;
12745         }
12746
12747         /* Format move number */
12748         if ((i % 2) == 0)
12749           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
12750         else
12751           if (newblock)
12752             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
12753           else
12754             numtext[0] = NULLCHAR;
12755
12756         numlen = strlen(numtext);
12757         newblock = FALSE;
12758
12759         /* Print move number */
12760         blank = linelen > 0 && numlen > 0;
12761         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
12762             fprintf(f, "\n");
12763             linelen = 0;
12764             blank = 0;
12765         }
12766         if (blank) {
12767             fprintf(f, " ");
12768             linelen++;
12769         }
12770         fprintf(f, "%s", numtext);
12771         linelen += numlen;
12772
12773         /* Get move */
12774         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
12775         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
12776
12777         /* Print move */
12778         blank = linelen > 0 && movelen > 0;
12779         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12780             fprintf(f, "\n");
12781             linelen = 0;
12782             blank = 0;
12783         }
12784         if (blank) {
12785             fprintf(f, " ");
12786             linelen++;
12787         }
12788         fprintf(f, "%s", move_buffer);
12789         linelen += movelen;
12790
12791         /* [AS] Add PV info if present */
12792         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
12793             /* [HGM] add time */
12794             char buf[MSG_SIZ]; int seconds;
12795
12796             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
12797
12798             if( seconds <= 0)
12799               buf[0] = 0;
12800             else
12801               if( seconds < 30 )
12802                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
12803               else
12804                 {
12805                   seconds = (seconds + 4)/10; // round to full seconds
12806                   if( seconds < 60 )
12807                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
12808                   else
12809                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
12810                 }
12811
12812             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
12813                       pvInfoList[i].score >= 0 ? "+" : "",
12814                       pvInfoList[i].score / 100.0,
12815                       pvInfoList[i].depth,
12816                       buf );
12817
12818             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
12819
12820             /* Print score/depth */
12821             blank = linelen > 0 && movelen > 0;
12822             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12823                 fprintf(f, "\n");
12824                 linelen = 0;
12825                 blank = 0;
12826             }
12827             if (blank) {
12828                 fprintf(f, " ");
12829                 linelen++;
12830             }
12831             fprintf(f, "%s", move_buffer);
12832             linelen += movelen;
12833         }
12834
12835         i++;
12836     }
12837
12838     /* Start a new line */
12839     if (linelen > 0) fprintf(f, "\n");
12840
12841     /* Print comments after last move */
12842     if (commentList[i] != NULL) {
12843         fprintf(f, "%s\n", commentList[i]);
12844     }
12845
12846     /* Print result */
12847     if (gameInfo.resultDetails != NULL &&
12848         gameInfo.resultDetails[0] != NULLCHAR) {
12849         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
12850                 PGNResult(gameInfo.result));
12851     } else {
12852         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12853     }
12854
12855     fclose(f);
12856     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12857     return TRUE;
12858 }
12859
12860 /* Save game in old style and close the file */
12861 int
12862 SaveGameOldStyle (FILE *f)
12863 {
12864     int i, offset;
12865     time_t tm;
12866
12867     tm = time((time_t *) NULL);
12868
12869     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
12870     PrintOpponents(f);
12871
12872     if (backwardMostMove > 0 || startedFromSetupPosition) {
12873         fprintf(f, "\n[--------------\n");
12874         PrintPosition(f, backwardMostMove);
12875         fprintf(f, "--------------]\n");
12876     } else {
12877         fprintf(f, "\n");
12878     }
12879
12880     i = backwardMostMove;
12881     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12882
12883     while (i < forwardMostMove) {
12884         if (commentList[i] != NULL) {
12885             fprintf(f, "[%s]\n", commentList[i]);
12886         }
12887
12888         if ((i % 2) == 1) {
12889             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
12890             i++;
12891         } else {
12892             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
12893             i++;
12894             if (commentList[i] != NULL) {
12895                 fprintf(f, "\n");
12896                 continue;
12897             }
12898             if (i >= forwardMostMove) {
12899                 fprintf(f, "\n");
12900                 break;
12901             }
12902             fprintf(f, "%s\n", parseList[i]);
12903             i++;
12904         }
12905     }
12906
12907     if (commentList[i] != NULL) {
12908         fprintf(f, "[%s]\n", commentList[i]);
12909     }
12910
12911     /* This isn't really the old style, but it's close enough */
12912     if (gameInfo.resultDetails != NULL &&
12913         gameInfo.resultDetails[0] != NULLCHAR) {
12914         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
12915                 gameInfo.resultDetails);
12916     } else {
12917         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12918     }
12919
12920     fclose(f);
12921     return TRUE;
12922 }
12923
12924 /* Save the current game to open file f and close the file */
12925 int
12926 SaveGame (FILE *f, int dummy, char *dummy2)
12927 {
12928     if (gameMode == EditPosition) EditPositionDone(TRUE);
12929     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12930     if (appData.oldSaveStyle)
12931       return SaveGameOldStyle(f);
12932     else
12933       return SaveGamePGN(f);
12934 }
12935
12936 /* Save the current position to the given file */
12937 int
12938 SavePositionToFile (char *filename)
12939 {
12940     FILE *f;
12941     char buf[MSG_SIZ];
12942
12943     if (strcmp(filename, "-") == 0) {
12944         return SavePosition(stdout, 0, NULL);
12945     } else {
12946         f = fopen(filename, "a");
12947         if (f == NULL) {
12948             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12949             DisplayError(buf, errno);
12950             return FALSE;
12951         } else {
12952             safeStrCpy(buf, lastMsg, MSG_SIZ);
12953             DisplayMessage(_("Waiting for access to save file"), "");
12954             flock(fileno(f), LOCK_EX); // [HGM] lock
12955             DisplayMessage(_("Saving position"), "");
12956             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
12957             SavePosition(f, 0, NULL);
12958             DisplayMessage(buf, "");
12959             return TRUE;
12960         }
12961     }
12962 }
12963
12964 /* Save the current position to the given open file and close the file */
12965 int
12966 SavePosition (FILE *f, int dummy, char *dummy2)
12967 {
12968     time_t tm;
12969     char *fen;
12970
12971     if (gameMode == EditPosition) EditPositionDone(TRUE);
12972     if (appData.oldSaveStyle) {
12973         tm = time((time_t *) NULL);
12974
12975         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
12976         PrintOpponents(f);
12977         fprintf(f, "[--------------\n");
12978         PrintPosition(f, currentMove);
12979         fprintf(f, "--------------]\n");
12980     } else {
12981         fen = PositionToFEN(currentMove, NULL);
12982         fprintf(f, "%s\n", fen);
12983         free(fen);
12984     }
12985     fclose(f);
12986     return TRUE;
12987 }
12988
12989 void
12990 ReloadCmailMsgEvent (int unregister)
12991 {
12992 #if !WIN32
12993     static char *inFilename = NULL;
12994     static char *outFilename;
12995     int i;
12996     struct stat inbuf, outbuf;
12997     int status;
12998
12999     /* Any registered moves are unregistered if unregister is set, */
13000     /* i.e. invoked by the signal handler */
13001     if (unregister) {
13002         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13003             cmailMoveRegistered[i] = FALSE;
13004             if (cmailCommentList[i] != NULL) {
13005                 free(cmailCommentList[i]);
13006                 cmailCommentList[i] = NULL;
13007             }
13008         }
13009         nCmailMovesRegistered = 0;
13010     }
13011
13012     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13013         cmailResult[i] = CMAIL_NOT_RESULT;
13014     }
13015     nCmailResults = 0;
13016
13017     if (inFilename == NULL) {
13018         /* Because the filenames are static they only get malloced once  */
13019         /* and they never get freed                                      */
13020         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
13021         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
13022
13023         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
13024         sprintf(outFilename, "%s.out", appData.cmailGameName);
13025     }
13026
13027     status = stat(outFilename, &outbuf);
13028     if (status < 0) {
13029         cmailMailedMove = FALSE;
13030     } else {
13031         status = stat(inFilename, &inbuf);
13032         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
13033     }
13034
13035     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
13036        counts the games, notes how each one terminated, etc.
13037
13038        It would be nice to remove this kludge and instead gather all
13039        the information while building the game list.  (And to keep it
13040        in the game list nodes instead of having a bunch of fixed-size
13041        parallel arrays.)  Note this will require getting each game's
13042        termination from the PGN tags, as the game list builder does
13043        not process the game moves.  --mann
13044        */
13045     cmailMsgLoaded = TRUE;
13046     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
13047
13048     /* Load first game in the file or popup game menu */
13049     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
13050
13051 #endif /* !WIN32 */
13052     return;
13053 }
13054
13055 int
13056 RegisterMove ()
13057 {
13058     FILE *f;
13059     char string[MSG_SIZ];
13060
13061     if (   cmailMailedMove
13062         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
13063         return TRUE;            /* Allow free viewing  */
13064     }
13065
13066     /* Unregister move to ensure that we don't leave RegisterMove        */
13067     /* with the move registered when the conditions for registering no   */
13068     /* longer hold                                                       */
13069     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
13070         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
13071         nCmailMovesRegistered --;
13072
13073         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
13074           {
13075               free(cmailCommentList[lastLoadGameNumber - 1]);
13076               cmailCommentList[lastLoadGameNumber - 1] = NULL;
13077           }
13078     }
13079
13080     if (cmailOldMove == -1) {
13081         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
13082         return FALSE;
13083     }
13084
13085     if (currentMove > cmailOldMove + 1) {
13086         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
13087         return FALSE;
13088     }
13089
13090     if (currentMove < cmailOldMove) {
13091         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
13092         return FALSE;
13093     }
13094
13095     if (forwardMostMove > currentMove) {
13096         /* Silently truncate extra moves */
13097         TruncateGame();
13098     }
13099
13100     if (   (currentMove == cmailOldMove + 1)
13101         || (   (currentMove == cmailOldMove)
13102             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
13103                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
13104         if (gameInfo.result != GameUnfinished) {
13105             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
13106         }
13107
13108         if (commentList[currentMove] != NULL) {
13109             cmailCommentList[lastLoadGameNumber - 1]
13110               = StrSave(commentList[currentMove]);
13111         }
13112         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
13113
13114         if (appData.debugMode)
13115           fprintf(debugFP, "Saving %s for game %d\n",
13116                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
13117
13118         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
13119
13120         f = fopen(string, "w");
13121         if (appData.oldSaveStyle) {
13122             SaveGameOldStyle(f); /* also closes the file */
13123
13124             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
13125             f = fopen(string, "w");
13126             SavePosition(f, 0, NULL); /* also closes the file */
13127         } else {
13128             fprintf(f, "{--------------\n");
13129             PrintPosition(f, currentMove);
13130             fprintf(f, "--------------}\n\n");
13131
13132             SaveGame(f, 0, NULL); /* also closes the file*/
13133         }
13134
13135         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
13136         nCmailMovesRegistered ++;
13137     } else if (nCmailGames == 1) {
13138         DisplayError(_("You have not made a move yet"), 0);
13139         return FALSE;
13140     }
13141
13142     return TRUE;
13143 }
13144
13145 void
13146 MailMoveEvent ()
13147 {
13148 #if !WIN32
13149     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
13150     FILE *commandOutput;
13151     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
13152     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
13153     int nBuffers;
13154     int i;
13155     int archived;
13156     char *arcDir;
13157
13158     if (! cmailMsgLoaded) {
13159         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
13160         return;
13161     }
13162
13163     if (nCmailGames == nCmailResults) {
13164         DisplayError(_("No unfinished games"), 0);
13165         return;
13166     }
13167
13168 #if CMAIL_PROHIBIT_REMAIL
13169     if (cmailMailedMove) {
13170       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);
13171         DisplayError(msg, 0);
13172         return;
13173     }
13174 #endif
13175
13176     if (! (cmailMailedMove || RegisterMove())) return;
13177
13178     if (   cmailMailedMove
13179         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13180       snprintf(string, MSG_SIZ, partCommandString,
13181                appData.debugMode ? " -v" : "", appData.cmailGameName);
13182         commandOutput = popen(string, "r");
13183
13184         if (commandOutput == NULL) {
13185             DisplayError(_("Failed to invoke cmail"), 0);
13186         } else {
13187             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13188                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13189             }
13190             if (nBuffers > 1) {
13191                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13192                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13193                 nBytes = MSG_SIZ - 1;
13194             } else {
13195                 (void) memcpy(msg, buffer, nBytes);
13196             }
13197             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13198
13199             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13200                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
13201
13202                 archived = TRUE;
13203                 for (i = 0; i < nCmailGames; i ++) {
13204                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
13205                         archived = FALSE;
13206                     }
13207                 }
13208                 if (   archived
13209                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13210                         != NULL)) {
13211                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13212                            arcDir,
13213                            appData.cmailGameName,
13214                            gameInfo.date);
13215                     LoadGameFromFile(buffer, 1, buffer, FALSE);
13216                     cmailMsgLoaded = FALSE;
13217                 }
13218             }
13219
13220             DisplayInformation(msg);
13221             pclose(commandOutput);
13222         }
13223     } else {
13224         if ((*cmailMsg) != '\0') {
13225             DisplayInformation(cmailMsg);
13226         }
13227     }
13228
13229     return;
13230 #endif /* !WIN32 */
13231 }
13232
13233 char *
13234 CmailMsg ()
13235 {
13236 #if WIN32
13237     return NULL;
13238 #else
13239     int  prependComma = 0;
13240     char number[5];
13241     char string[MSG_SIZ];       /* Space for game-list */
13242     int  i;
13243
13244     if (!cmailMsgLoaded) return "";
13245
13246     if (cmailMailedMove) {
13247       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13248     } else {
13249         /* Create a list of games left */
13250       snprintf(string, MSG_SIZ, "[");
13251         for (i = 0; i < nCmailGames; i ++) {
13252             if (! (   cmailMoveRegistered[i]
13253                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13254                 if (prependComma) {
13255                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13256                 } else {
13257                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13258                     prependComma = 1;
13259                 }
13260
13261                 strcat(string, number);
13262             }
13263         }
13264         strcat(string, "]");
13265
13266         if (nCmailMovesRegistered + nCmailResults == 0) {
13267             switch (nCmailGames) {
13268               case 1:
13269                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13270                 break;
13271
13272               case 2:
13273                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13274                 break;
13275
13276               default:
13277                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13278                          nCmailGames);
13279                 break;
13280             }
13281         } else {
13282             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13283               case 1:
13284                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13285                          string);
13286                 break;
13287
13288               case 0:
13289                 if (nCmailResults == nCmailGames) {
13290                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13291                 } else {
13292                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13293                 }
13294                 break;
13295
13296               default:
13297                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13298                          string);
13299             }
13300         }
13301     }
13302     return cmailMsg;
13303 #endif /* WIN32 */
13304 }
13305
13306 void
13307 ResetGameEvent ()
13308 {
13309     if (gameMode == Training)
13310       SetTrainingModeOff();
13311
13312     Reset(TRUE, TRUE);
13313     cmailMsgLoaded = FALSE;
13314     if (appData.icsActive) {
13315       SendToICS(ics_prefix);
13316       SendToICS("refresh\n");
13317     }
13318 }
13319
13320 void
13321 ExitEvent (int status)
13322 {
13323     exiting++;
13324     if (exiting > 2) {
13325       /* Give up on clean exit */
13326       exit(status);
13327     }
13328     if (exiting > 1) {
13329       /* Keep trying for clean exit */
13330       return;
13331     }
13332
13333     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
13334
13335     if (telnetISR != NULL) {
13336       RemoveInputSource(telnetISR);
13337     }
13338     if (icsPR != NoProc) {
13339       DestroyChildProcess(icsPR, TRUE);
13340     }
13341
13342     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
13343     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
13344
13345     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
13346     /* make sure this other one finishes before killing it!                  */
13347     if(endingGame) { int count = 0;
13348         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
13349         while(endingGame && count++ < 10) DoSleep(1);
13350         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
13351     }
13352
13353     /* Kill off chess programs */
13354     if (first.pr != NoProc) {
13355         ExitAnalyzeMode();
13356
13357         DoSleep( appData.delayBeforeQuit );
13358         SendToProgram("quit\n", &first);
13359         DoSleep( appData.delayAfterQuit );
13360         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
13361     }
13362     if (second.pr != NoProc) {
13363         DoSleep( appData.delayBeforeQuit );
13364         SendToProgram("quit\n", &second);
13365         DoSleep( appData.delayAfterQuit );
13366         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
13367     }
13368     if (first.isr != NULL) {
13369         RemoveInputSource(first.isr);
13370     }
13371     if (second.isr != NULL) {
13372         RemoveInputSource(second.isr);
13373     }
13374
13375     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13376     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13377
13378     ShutDownFrontEnd();
13379     exit(status);
13380 }
13381
13382 void
13383 PauseEngine (ChessProgramState *cps)
13384 {
13385     SendToProgram("pause\n", cps);
13386     cps->pause = 2;
13387 }
13388
13389 void
13390 UnPauseEngine (ChessProgramState *cps)
13391 {
13392     SendToProgram("resume\n", cps);
13393     cps->pause = 1;
13394 }
13395
13396 void
13397 PauseEvent ()
13398 {
13399     if (appData.debugMode)
13400         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13401     if (pausing) {
13402         pausing = FALSE;
13403         ModeHighlight();
13404         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
13405             StartClocks();
13406             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
13407                 if(stalledEngine->other->pause) UnPauseEngine(stalledEngine->other);
13408                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
13409             }
13410             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
13411             HandleMachineMove(stashedInputMove, stalledEngine);
13412             stalledEngine = NULL;
13413             return;
13414         }
13415         if (gameMode == MachinePlaysWhite ||
13416             gameMode == TwoMachinesPlay   ||
13417             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
13418             if(first.pause)  UnPauseEngine(&first);
13419             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
13420             if(second.pause) UnPauseEngine(&second);
13421             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
13422             StartClocks();
13423         } else {
13424             DisplayBothClocks();
13425         }
13426         if (gameMode == PlayFromGameFile) {
13427             if (appData.timeDelay >= 0)
13428                 AutoPlayGameLoop();
13429         } else if (gameMode == IcsExamining && pauseExamInvalid) {
13430             Reset(FALSE, TRUE);
13431             SendToICS(ics_prefix);
13432             SendToICS("refresh\n");
13433         } else if (currentMove < forwardMostMove) {
13434             ForwardInner(forwardMostMove);
13435         }
13436         pauseExamInvalid = FALSE;
13437     } else {
13438         switch (gameMode) {
13439           default:
13440             return;
13441           case IcsExamining:
13442             pauseExamForwardMostMove = forwardMostMove;
13443             pauseExamInvalid = FALSE;
13444             /* fall through */
13445           case IcsObserving:
13446           case IcsPlayingWhite:
13447           case IcsPlayingBlack:
13448             pausing = TRUE;
13449             ModeHighlight();
13450             return;
13451           case PlayFromGameFile:
13452             (void) StopLoadGameTimer();
13453             pausing = TRUE;
13454             ModeHighlight();
13455             break;
13456           case BeginningOfGame:
13457             if (appData.icsActive) return;
13458             /* else fall through */
13459           case MachinePlaysWhite:
13460           case MachinePlaysBlack:
13461           case TwoMachinesPlay:
13462             if (forwardMostMove == 0)
13463               return;           /* don't pause if no one has moved */
13464             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
13465                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
13466                 if(onMove->pause) {           // thinking engine can be paused
13467                     PauseEngine(onMove);      // do it
13468                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
13469                         PauseEngine(onMove->other);
13470                     else
13471                         SendToProgram("easy\n", onMove->other);
13472                     StopClocks();
13473                 }
13474             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
13475                 if(first.pause) {
13476                     PauseEngine(&first);
13477                     StopClocks();
13478                 }
13479             } else { // human on move, pause pondering by either method
13480                 if(first.pause) 
13481                     PauseEngine(&first);
13482                 else
13483                     SendToProgram("easy\n", &first);
13484                 StopClocks();
13485             }
13486             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
13487           case AnalyzeMode:
13488             pausing = TRUE;
13489             ModeHighlight();
13490             break;
13491         }
13492     }
13493 }
13494
13495 void
13496 EditCommentEvent ()
13497 {
13498     char title[MSG_SIZ];
13499
13500     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13501       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13502     } else {
13503       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13504                WhiteOnMove(currentMove - 1) ? " " : ".. ",
13505                parseList[currentMove - 1]);
13506     }
13507
13508     EditCommentPopUp(currentMove, title, commentList[currentMove]);
13509 }
13510
13511
13512 void
13513 EditTagsEvent ()
13514 {
13515     char *tags = PGNTags(&gameInfo);
13516     bookUp = FALSE;
13517     EditTagsPopUp(tags, NULL);
13518     free(tags);
13519 }
13520
13521 void
13522 ToggleSecond ()
13523 {
13524   if(second.analyzing) {
13525     SendToProgram("exit\n", &second);
13526     second.analyzing = FALSE;
13527   } else {
13528     if (second.pr == NoProc) StartChessProgram(&second);
13529     InitChessProgram(&second, FALSE);
13530     FeedMovesToProgram(&second, currentMove);
13531
13532     SendToProgram("analyze\n", &second);
13533     second.analyzing = TRUE;
13534   }
13535 }
13536
13537 /* Toggle ShowThinking */
13538 void
13539 ToggleShowThinking()
13540 {
13541   appData.showThinking = !appData.showThinking;
13542   ShowThinkingEvent();
13543 }
13544
13545 int
13546 AnalyzeModeEvent ()
13547 {
13548     char buf[MSG_SIZ];
13549
13550     if (!first.analysisSupport) {
13551       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
13552       DisplayError(buf, 0);
13553       return 0;
13554     }
13555     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
13556     if (appData.icsActive) {
13557         if (gameMode != IcsObserving) {
13558           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
13559             DisplayError(buf, 0);
13560             /* secure check */
13561             if (appData.icsEngineAnalyze) {
13562                 if (appData.debugMode)
13563                     fprintf(debugFP, _("Found unexpected active ICS engine analyze \n"));
13564                 ExitAnalyzeMode();
13565                 ModeHighlight();
13566             }
13567             return 0;
13568         }
13569         /* if enable, user wants to disable icsEngineAnalyze */
13570         if (appData.icsEngineAnalyze) {
13571                 ExitAnalyzeMode();
13572                 ModeHighlight();
13573                 return 0;
13574         }
13575         appData.icsEngineAnalyze = TRUE;
13576         if (appData.debugMode)
13577             fprintf(debugFP, _("ICS engine analyze starting... \n"));
13578     }
13579
13580     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
13581     if (appData.noChessProgram || gameMode == AnalyzeMode)
13582       return 0;
13583
13584     if (gameMode != AnalyzeFile) {
13585         if (!appData.icsEngineAnalyze) {
13586                EditGameEvent();
13587                if (gameMode != EditGame) return 0;
13588         }
13589         if (!appData.showThinking) ToggleShowThinking();
13590         ResurrectChessProgram();
13591         SendToProgram("analyze\n", &first);
13592         first.analyzing = TRUE;
13593         /*first.maybeThinking = TRUE;*/
13594         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13595         EngineOutputPopUp();
13596     }
13597     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
13598     pausing = FALSE;
13599     ModeHighlight();
13600     SetGameInfo();
13601
13602     StartAnalysisClock();
13603     GetTimeMark(&lastNodeCountTime);
13604     lastNodeCount = 0;
13605     return 1;
13606 }
13607
13608 void
13609 AnalyzeFileEvent ()
13610 {
13611     if (appData.noChessProgram || gameMode == AnalyzeFile)
13612       return;
13613
13614     if (!first.analysisSupport) {
13615       char buf[MSG_SIZ];
13616       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
13617       DisplayError(buf, 0);
13618       return;
13619     }
13620
13621     if (gameMode != AnalyzeMode) {
13622         EditGameEvent();
13623         if (gameMode != EditGame) return;
13624         if (!appData.showThinking) ToggleShowThinking();
13625         ResurrectChessProgram();
13626         SendToProgram("analyze\n", &first);
13627         first.analyzing = TRUE;
13628         /*first.maybeThinking = TRUE;*/
13629         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13630         EngineOutputPopUp();
13631     }
13632     gameMode = AnalyzeFile;
13633     pausing = FALSE;
13634     ModeHighlight();
13635     SetGameInfo();
13636
13637     StartAnalysisClock();
13638     GetTimeMark(&lastNodeCountTime);
13639     lastNodeCount = 0;
13640     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
13641     AnalysisPeriodicEvent(1);
13642 }
13643
13644 void
13645 MachineWhiteEvent ()
13646 {
13647     char buf[MSG_SIZ];
13648     char *bookHit = NULL;
13649
13650     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
13651       return;
13652
13653
13654     if (gameMode == PlayFromGameFile ||
13655         gameMode == TwoMachinesPlay  ||
13656         gameMode == Training         ||
13657         gameMode == AnalyzeMode      ||
13658         gameMode == EndOfGame)
13659         EditGameEvent();
13660
13661     if (gameMode == EditPosition)
13662         EditPositionDone(TRUE);
13663
13664     if (!WhiteOnMove(currentMove)) {
13665         DisplayError(_("It is not White's turn"), 0);
13666         return;
13667     }
13668
13669     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13670       ExitAnalyzeMode();
13671
13672     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13673         gameMode == AnalyzeFile)
13674         TruncateGame();
13675
13676     ResurrectChessProgram();    /* in case it isn't running */
13677     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
13678         gameMode = MachinePlaysWhite;
13679         ResetClocks();
13680     } else
13681     gameMode = MachinePlaysWhite;
13682     pausing = FALSE;
13683     ModeHighlight();
13684     SetGameInfo();
13685     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13686     DisplayTitle(buf);
13687     if (first.sendName) {
13688       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
13689       SendToProgram(buf, &first);
13690     }
13691     if (first.sendTime) {
13692       if (first.useColors) {
13693         SendToProgram("black\n", &first); /*gnu kludge*/
13694       }
13695       SendTimeRemaining(&first, TRUE);
13696     }
13697     if (first.useColors) {
13698       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
13699     }
13700     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13701     SetMachineThinkingEnables();
13702     first.maybeThinking = TRUE;
13703     StartClocks();
13704     firstMove = FALSE;
13705
13706     if (appData.autoFlipView && !flipView) {
13707       flipView = !flipView;
13708       DrawPosition(FALSE, NULL);
13709       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13710     }
13711
13712     if(bookHit) { // [HGM] book: simulate book reply
13713         static char bookMove[MSG_SIZ]; // a bit generous?
13714
13715         programStats.nodes = programStats.depth = programStats.time =
13716         programStats.score = programStats.got_only_move = 0;
13717         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13718
13719         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13720         strcat(bookMove, bookHit);
13721         HandleMachineMove(bookMove, &first);
13722     }
13723 }
13724
13725 void
13726 MachineBlackEvent ()
13727 {
13728   char buf[MSG_SIZ];
13729   char *bookHit = NULL;
13730
13731     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
13732         return;
13733
13734
13735     if (gameMode == PlayFromGameFile ||
13736         gameMode == TwoMachinesPlay  ||
13737         gameMode == Training         ||
13738         gameMode == AnalyzeMode      ||
13739         gameMode == EndOfGame)
13740         EditGameEvent();
13741
13742     if (gameMode == EditPosition)
13743         EditPositionDone(TRUE);
13744
13745     if (WhiteOnMove(currentMove)) {
13746         DisplayError(_("It is not Black's turn"), 0);
13747         return;
13748     }
13749
13750     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13751       ExitAnalyzeMode();
13752
13753     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13754         gameMode == AnalyzeFile)
13755         TruncateGame();
13756
13757     ResurrectChessProgram();    /* in case it isn't running */
13758     gameMode = MachinePlaysBlack;
13759     pausing = FALSE;
13760     ModeHighlight();
13761     SetGameInfo();
13762     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13763     DisplayTitle(buf);
13764     if (first.sendName) {
13765       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
13766       SendToProgram(buf, &first);
13767     }
13768     if (first.sendTime) {
13769       if (first.useColors) {
13770         SendToProgram("white\n", &first); /*gnu kludge*/
13771       }
13772       SendTimeRemaining(&first, FALSE);
13773     }
13774     if (first.useColors) {
13775       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
13776     }
13777     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13778     SetMachineThinkingEnables();
13779     first.maybeThinking = TRUE;
13780     StartClocks();
13781
13782     if (appData.autoFlipView && flipView) {
13783       flipView = !flipView;
13784       DrawPosition(FALSE, NULL);
13785       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13786     }
13787     if(bookHit) { // [HGM] book: simulate book reply
13788         static char bookMove[MSG_SIZ]; // a bit generous?
13789
13790         programStats.nodes = programStats.depth = programStats.time =
13791         programStats.score = programStats.got_only_move = 0;
13792         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13793
13794         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13795         strcat(bookMove, bookHit);
13796         HandleMachineMove(bookMove, &first);
13797     }
13798 }
13799
13800
13801 void
13802 DisplayTwoMachinesTitle ()
13803 {
13804     char buf[MSG_SIZ];
13805     if (appData.matchGames > 0) {
13806         if(appData.tourneyFile[0]) {
13807           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
13808                    gameInfo.white, _("vs."), gameInfo.black,
13809                    nextGame+1, appData.matchGames+1,
13810                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
13811         } else 
13812         if (first.twoMachinesColor[0] == 'w') {
13813           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13814                    gameInfo.white, _("vs."),  gameInfo.black,
13815                    first.matchWins, second.matchWins,
13816                    matchGame - 1 - (first.matchWins + second.matchWins));
13817         } else {
13818           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13819                    gameInfo.white, _("vs."), gameInfo.black,
13820                    second.matchWins, first.matchWins,
13821                    matchGame - 1 - (first.matchWins + second.matchWins));
13822         }
13823     } else {
13824       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13825     }
13826     DisplayTitle(buf);
13827 }
13828
13829 void
13830 SettingsMenuIfReady ()
13831 {
13832   if (second.lastPing != second.lastPong) {
13833     DisplayMessage("", _("Waiting for second chess program"));
13834     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
13835     return;
13836   }
13837   ThawUI();
13838   DisplayMessage("", "");
13839   SettingsPopUp(&second);
13840 }
13841
13842 int
13843 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
13844 {
13845     char buf[MSG_SIZ];
13846     if (cps->pr == NoProc) {
13847         StartChessProgram(cps);
13848         if (cps->protocolVersion == 1) {
13849           retry();
13850         } else {
13851           /* kludge: allow timeout for initial "feature" command */
13852           FreezeUI();
13853           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
13854           DisplayMessage("", buf);
13855           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
13856         }
13857         return 1;
13858     }
13859     return 0;
13860 }
13861
13862 void
13863 TwoMachinesEvent P((void))
13864 {
13865     int i;
13866     char buf[MSG_SIZ];
13867     ChessProgramState *onmove;
13868     char *bookHit = NULL;
13869     static int stalling = 0;
13870     TimeMark now;
13871     long wait;
13872
13873     if (appData.noChessProgram) return;
13874
13875     switch (gameMode) {
13876       case TwoMachinesPlay:
13877         return;
13878       case MachinePlaysWhite:
13879       case MachinePlaysBlack:
13880         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13881             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13882             return;
13883         }
13884         /* fall through */
13885       case BeginningOfGame:
13886       case PlayFromGameFile:
13887       case EndOfGame:
13888         EditGameEvent();
13889         if (gameMode != EditGame) return;
13890         break;
13891       case EditPosition:
13892         EditPositionDone(TRUE);
13893         break;
13894       case AnalyzeMode:
13895       case AnalyzeFile:
13896         ExitAnalyzeMode();
13897         break;
13898       case EditGame:
13899       default:
13900         break;
13901     }
13902
13903 //    forwardMostMove = currentMove;
13904     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
13905
13906     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
13907
13908     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
13909     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
13910       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13911       return;
13912     }
13913
13914     if(second.protocolVersion >= 2 && !strstr(second.variants, VariantName(gameInfo.variant))) {
13915         DisplayError("second engine does not play this", 0);
13916         return;
13917     }
13918
13919     if(!stalling) {
13920       InitChessProgram(&second, FALSE); // unbalances ping of second engine
13921       SendToProgram("force\n", &second);
13922       stalling = 1;
13923       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13924       return;
13925     }
13926     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
13927     if(appData.matchPause>10000 || appData.matchPause<10)
13928                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
13929     wait = SubtractTimeMarks(&now, &pauseStart);
13930     if(wait < appData.matchPause) {
13931         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
13932         return;
13933     }
13934     // we are now committed to starting the game
13935     stalling = 0;
13936     DisplayMessage("", "");
13937     if (startedFromSetupPosition) {
13938         SendBoard(&second, backwardMostMove);
13939     if (appData.debugMode) {
13940         fprintf(debugFP, "Two Machines\n");
13941     }
13942     }
13943     for (i = backwardMostMove; i < forwardMostMove; i++) {
13944         SendMoveToProgram(i, &second);
13945     }
13946
13947     gameMode = TwoMachinesPlay;
13948     pausing = FALSE;
13949     ModeHighlight(); // [HGM] logo: this triggers display update of logos
13950     SetGameInfo();
13951     DisplayTwoMachinesTitle();
13952     firstMove = TRUE;
13953     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
13954         onmove = &first;
13955     } else {
13956         onmove = &second;
13957     }
13958     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
13959     SendToProgram(first.computerString, &first);
13960     if (first.sendName) {
13961       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
13962       SendToProgram(buf, &first);
13963     }
13964     SendToProgram(second.computerString, &second);
13965     if (second.sendName) {
13966       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
13967       SendToProgram(buf, &second);
13968     }
13969
13970     ResetClocks();
13971     if (!first.sendTime || !second.sendTime) {
13972         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13973         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13974     }
13975     if (onmove->sendTime) {
13976       if (onmove->useColors) {
13977         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
13978       }
13979       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
13980     }
13981     if (onmove->useColors) {
13982       SendToProgram(onmove->twoMachinesColor, onmove);
13983     }
13984     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
13985 //    SendToProgram("go\n", onmove);
13986     onmove->maybeThinking = TRUE;
13987     SetMachineThinkingEnables();
13988
13989     StartClocks();
13990
13991     if(bookHit) { // [HGM] book: simulate book reply
13992         static char bookMove[MSG_SIZ]; // a bit generous?
13993
13994         programStats.nodes = programStats.depth = programStats.time =
13995         programStats.score = programStats.got_only_move = 0;
13996         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13997
13998         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13999         strcat(bookMove, bookHit);
14000         savedMessage = bookMove; // args for deferred call
14001         savedState = onmove;
14002         ScheduleDelayedEvent(DeferredBookMove, 1);
14003     }
14004 }
14005
14006 void
14007 TrainingEvent ()
14008 {
14009     if (gameMode == Training) {
14010       SetTrainingModeOff();
14011       gameMode = PlayFromGameFile;
14012       DisplayMessage("", _("Training mode off"));
14013     } else {
14014       gameMode = Training;
14015       animateTraining = appData.animate;
14016
14017       /* make sure we are not already at the end of the game */
14018       if (currentMove < forwardMostMove) {
14019         SetTrainingModeOn();
14020         DisplayMessage("", _("Training mode on"));
14021       } else {
14022         gameMode = PlayFromGameFile;
14023         DisplayError(_("Already at end of game"), 0);
14024       }
14025     }
14026     ModeHighlight();
14027 }
14028
14029 void
14030 IcsClientEvent ()
14031 {
14032     if (!appData.icsActive) return;
14033     switch (gameMode) {
14034       case IcsPlayingWhite:
14035       case IcsPlayingBlack:
14036       case IcsObserving:
14037       case IcsIdle:
14038       case BeginningOfGame:
14039       case IcsExamining:
14040         return;
14041
14042       case EditGame:
14043         break;
14044
14045       case EditPosition:
14046         EditPositionDone(TRUE);
14047         break;
14048
14049       case AnalyzeMode:
14050       case AnalyzeFile:
14051         ExitAnalyzeMode();
14052         break;
14053
14054       default:
14055         EditGameEvent();
14056         break;
14057     }
14058
14059     gameMode = IcsIdle;
14060     ModeHighlight();
14061     return;
14062 }
14063
14064 void
14065 EditGameEvent ()
14066 {
14067     int i;
14068
14069     switch (gameMode) {
14070       case Training:
14071         SetTrainingModeOff();
14072         break;
14073       case MachinePlaysWhite:
14074       case MachinePlaysBlack:
14075       case BeginningOfGame:
14076         SendToProgram("force\n", &first);
14077         SetUserThinkingEnables();
14078         break;
14079       case PlayFromGameFile:
14080         (void) StopLoadGameTimer();
14081         if (gameFileFP != NULL) {
14082             gameFileFP = NULL;
14083         }
14084         break;
14085       case EditPosition:
14086         EditPositionDone(TRUE);
14087         break;
14088       case AnalyzeMode:
14089       case AnalyzeFile:
14090         ExitAnalyzeMode();
14091         SendToProgram("force\n", &first);
14092         break;
14093       case TwoMachinesPlay:
14094         GameEnds(EndOfFile, NULL, GE_PLAYER);
14095         ResurrectChessProgram();
14096         SetUserThinkingEnables();
14097         break;
14098       case EndOfGame:
14099         ResurrectChessProgram();
14100         break;
14101       case IcsPlayingBlack:
14102       case IcsPlayingWhite:
14103         DisplayError(_("Warning: You are still playing a game"), 0);
14104         break;
14105       case IcsObserving:
14106         DisplayError(_("Warning: You are still observing a game"), 0);
14107         break;
14108       case IcsExamining:
14109         DisplayError(_("Warning: You are still examining a game"), 0);
14110         break;
14111       case IcsIdle:
14112         break;
14113       case EditGame:
14114       default:
14115         return;
14116     }
14117
14118     pausing = FALSE;
14119     StopClocks();
14120     first.offeredDraw = second.offeredDraw = 0;
14121
14122     if (gameMode == PlayFromGameFile) {
14123         whiteTimeRemaining = timeRemaining[0][currentMove];
14124         blackTimeRemaining = timeRemaining[1][currentMove];
14125         DisplayTitle("");
14126     }
14127
14128     if (gameMode == MachinePlaysWhite ||
14129         gameMode == MachinePlaysBlack ||
14130         gameMode == TwoMachinesPlay ||
14131         gameMode == EndOfGame) {
14132         i = forwardMostMove;
14133         while (i > currentMove) {
14134             SendToProgram("undo\n", &first);
14135             i--;
14136         }
14137         if(!adjustedClock) {
14138         whiteTimeRemaining = timeRemaining[0][currentMove];
14139         blackTimeRemaining = timeRemaining[1][currentMove];
14140         DisplayBothClocks();
14141         }
14142         if (whiteFlag || blackFlag) {
14143             whiteFlag = blackFlag = 0;
14144         }
14145         DisplayTitle("");
14146     }
14147
14148     gameMode = EditGame;
14149     ModeHighlight();
14150     SetGameInfo();
14151 }
14152
14153
14154 void
14155 EditPositionEvent ()
14156 {
14157     if (gameMode == EditPosition) {
14158         EditGameEvent();
14159         return;
14160     }
14161
14162     EditGameEvent();
14163     if (gameMode != EditGame) return;
14164
14165     gameMode = EditPosition;
14166     ModeHighlight();
14167     SetGameInfo();
14168     if (currentMove > 0)
14169       CopyBoard(boards[0], boards[currentMove]);
14170
14171     blackPlaysFirst = !WhiteOnMove(currentMove);
14172     ResetClocks();
14173     currentMove = forwardMostMove = backwardMostMove = 0;
14174     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14175     DisplayMove(-1);
14176     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
14177 }
14178
14179 void
14180 ExitAnalyzeMode ()
14181 {
14182     /* [DM] icsEngineAnalyze - possible call from other functions */
14183     if (appData.icsEngineAnalyze) {
14184         appData.icsEngineAnalyze = FALSE;
14185
14186         DisplayMessage("",_("Close ICS engine analyze..."));
14187     }
14188     if (first.analysisSupport && first.analyzing) {
14189       SendToBoth("exit\n");
14190       first.analyzing = second.analyzing = FALSE;
14191     }
14192     thinkOutput[0] = NULLCHAR;
14193 }
14194
14195 void
14196 EditPositionDone (Boolean fakeRights)
14197 {
14198     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
14199
14200     startedFromSetupPosition = TRUE;
14201     InitChessProgram(&first, FALSE);
14202     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
14203       boards[0][EP_STATUS] = EP_NONE;
14204       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
14205       if(boards[0][0][BOARD_WIDTH>>1] == king) {
14206         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
14207         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
14208       } else boards[0][CASTLING][2] = NoRights;
14209       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
14210         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
14211         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
14212       } else boards[0][CASTLING][5] = NoRights;
14213       if(gameInfo.variant == VariantSChess) {
14214         int i;
14215         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
14216           boards[0][VIRGIN][i] = 0;
14217           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
14218           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
14219         }
14220       }
14221     }
14222     SendToProgram("force\n", &first);
14223     if (blackPlaysFirst) {
14224         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
14225         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
14226         currentMove = forwardMostMove = backwardMostMove = 1;
14227         CopyBoard(boards[1], boards[0]);
14228     } else {
14229         currentMove = forwardMostMove = backwardMostMove = 0;
14230     }
14231     SendBoard(&first, forwardMostMove);
14232     if (appData.debugMode) {
14233         fprintf(debugFP, "EditPosDone\n");
14234     }
14235     DisplayTitle("");
14236     DisplayMessage("", "");
14237     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14238     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14239     gameMode = EditGame;
14240     ModeHighlight();
14241     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14242     ClearHighlights(); /* [AS] */
14243 }
14244
14245 /* Pause for `ms' milliseconds */
14246 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14247 void
14248 TimeDelay (long ms)
14249 {
14250     TimeMark m1, m2;
14251
14252     GetTimeMark(&m1);
14253     do {
14254         GetTimeMark(&m2);
14255     } while (SubtractTimeMarks(&m2, &m1) < ms);
14256 }
14257
14258 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14259 void
14260 SendMultiLineToICS (char *buf)
14261 {
14262     char temp[MSG_SIZ+1], *p;
14263     int len;
14264
14265     len = strlen(buf);
14266     if (len > MSG_SIZ)
14267       len = MSG_SIZ;
14268
14269     strncpy(temp, buf, len);
14270     temp[len] = 0;
14271
14272     p = temp;
14273     while (*p) {
14274         if (*p == '\n' || *p == '\r')
14275           *p = ' ';
14276         ++p;
14277     }
14278
14279     strcat(temp, "\n");
14280     SendToICS(temp);
14281     SendToPlayer(temp, strlen(temp));
14282 }
14283
14284 void
14285 SetWhiteToPlayEvent ()
14286 {
14287     if (gameMode == EditPosition) {
14288         blackPlaysFirst = FALSE;
14289         DisplayBothClocks();    /* works because currentMove is 0 */
14290     } else if (gameMode == IcsExamining) {
14291         SendToICS(ics_prefix);
14292         SendToICS("tomove white\n");
14293     }
14294 }
14295
14296 void
14297 SetBlackToPlayEvent ()
14298 {
14299     if (gameMode == EditPosition) {
14300         blackPlaysFirst = TRUE;
14301         currentMove = 1;        /* kludge */
14302         DisplayBothClocks();
14303         currentMove = 0;
14304     } else if (gameMode == IcsExamining) {
14305         SendToICS(ics_prefix);
14306         SendToICS("tomove black\n");
14307     }
14308 }
14309
14310 void
14311 EditPositionMenuEvent (ChessSquare selection, int x, int y)
14312 {
14313     char buf[MSG_SIZ];
14314     ChessSquare piece = boards[0][y][x];
14315
14316     if (gameMode != EditPosition && gameMode != IcsExamining) return;
14317
14318     switch (selection) {
14319       case ClearBoard:
14320         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
14321             SendToICS(ics_prefix);
14322             SendToICS("bsetup clear\n");
14323         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
14324             SendToICS(ics_prefix);
14325             SendToICS("clearboard\n");
14326         } else {
14327             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
14328                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
14329                 for (y = 0; y < BOARD_HEIGHT; y++) {
14330                     if (gameMode == IcsExamining) {
14331                         if (boards[currentMove][y][x] != EmptySquare) {
14332                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
14333                                     AAA + x, ONE + y);
14334                             SendToICS(buf);
14335                         }
14336                     } else {
14337                         boards[0][y][x] = p;
14338                     }
14339                 }
14340             }
14341         }
14342         if (gameMode == EditPosition) {
14343             DrawPosition(FALSE, boards[0]);
14344         }
14345         break;
14346
14347       case WhitePlay:
14348         SetWhiteToPlayEvent();
14349         break;
14350
14351       case BlackPlay:
14352         SetBlackToPlayEvent();
14353         break;
14354
14355       case EmptySquare:
14356         if (gameMode == IcsExamining) {
14357             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14358             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
14359             SendToICS(buf);
14360         } else {
14361             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14362                 if(x == BOARD_LEFT-2) {
14363                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
14364                     boards[0][y][1] = 0;
14365                 } else
14366                 if(x == BOARD_RGHT+1) {
14367                     if(y >= gameInfo.holdingsSize) break;
14368                     boards[0][y][BOARD_WIDTH-2] = 0;
14369                 } else break;
14370             }
14371             boards[0][y][x] = EmptySquare;
14372             DrawPosition(FALSE, boards[0]);
14373         }
14374         break;
14375
14376       case PromotePiece:
14377         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
14378            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
14379             selection = (ChessSquare) (PROMOTED piece);
14380         } else if(piece == EmptySquare) selection = WhiteSilver;
14381         else selection = (ChessSquare)((int)piece - 1);
14382         goto defaultlabel;
14383
14384       case DemotePiece:
14385         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
14386            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
14387             selection = (ChessSquare) (DEMOTED piece);
14388         } else if(piece == EmptySquare) selection = BlackSilver;
14389         else selection = (ChessSquare)((int)piece + 1);
14390         goto defaultlabel;
14391
14392       case WhiteQueen:
14393       case BlackQueen:
14394         if(gameInfo.variant == VariantShatranj ||
14395            gameInfo.variant == VariantXiangqi  ||
14396            gameInfo.variant == VariantCourier  ||
14397            gameInfo.variant == VariantMakruk     )
14398             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
14399         goto defaultlabel;
14400
14401       case WhiteKing:
14402       case BlackKing:
14403         if(gameInfo.variant == VariantXiangqi)
14404             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
14405         if(gameInfo.variant == VariantKnightmate)
14406             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
14407       default:
14408         defaultlabel:
14409         if (gameMode == IcsExamining) {
14410             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14411             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
14412                      PieceToChar(selection), AAA + x, ONE + y);
14413             SendToICS(buf);
14414         } else {
14415             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14416                 int n;
14417                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
14418                     n = PieceToNumber(selection - BlackPawn);
14419                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
14420                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
14421                     boards[0][BOARD_HEIGHT-1-n][1]++;
14422                 } else
14423                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
14424                     n = PieceToNumber(selection);
14425                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
14426                     boards[0][n][BOARD_WIDTH-1] = selection;
14427                     boards[0][n][BOARD_WIDTH-2]++;
14428                 }
14429             } else
14430             boards[0][y][x] = selection;
14431             DrawPosition(TRUE, boards[0]);
14432             ClearHighlights();
14433             fromX = fromY = -1;
14434         }
14435         break;
14436     }
14437 }
14438
14439
14440 void
14441 DropMenuEvent (ChessSquare selection, int x, int y)
14442 {
14443     ChessMove moveType;
14444
14445     switch (gameMode) {
14446       case IcsPlayingWhite:
14447       case MachinePlaysBlack:
14448         if (!WhiteOnMove(currentMove)) {
14449             DisplayMoveError(_("It is Black's turn"));
14450             return;
14451         }
14452         moveType = WhiteDrop;
14453         break;
14454       case IcsPlayingBlack:
14455       case MachinePlaysWhite:
14456         if (WhiteOnMove(currentMove)) {
14457             DisplayMoveError(_("It is White's turn"));
14458             return;
14459         }
14460         moveType = BlackDrop;
14461         break;
14462       case EditGame:
14463         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
14464         break;
14465       default:
14466         return;
14467     }
14468
14469     if (moveType == BlackDrop && selection < BlackPawn) {
14470       selection = (ChessSquare) ((int) selection
14471                                  + (int) BlackPawn - (int) WhitePawn);
14472     }
14473     if (boards[currentMove][y][x] != EmptySquare) {
14474         DisplayMoveError(_("That square is occupied"));
14475         return;
14476     }
14477
14478     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
14479 }
14480
14481 void
14482 AcceptEvent ()
14483 {
14484     /* Accept a pending offer of any kind from opponent */
14485
14486     if (appData.icsActive) {
14487         SendToICS(ics_prefix);
14488         SendToICS("accept\n");
14489     } else if (cmailMsgLoaded) {
14490         if (currentMove == cmailOldMove &&
14491             commentList[cmailOldMove] != NULL &&
14492             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14493                    "Black offers a draw" : "White offers a draw")) {
14494             TruncateGame();
14495             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14496             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14497         } else {
14498             DisplayError(_("There is no pending offer on this move"), 0);
14499             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14500         }
14501     } else {
14502         /* Not used for offers from chess program */
14503     }
14504 }
14505
14506 void
14507 DeclineEvent ()
14508 {
14509     /* Decline a pending offer of any kind from opponent */
14510
14511     if (appData.icsActive) {
14512         SendToICS(ics_prefix);
14513         SendToICS("decline\n");
14514     } else if (cmailMsgLoaded) {
14515         if (currentMove == cmailOldMove &&
14516             commentList[cmailOldMove] != NULL &&
14517             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14518                    "Black offers a draw" : "White offers a draw")) {
14519 #ifdef NOTDEF
14520             AppendComment(cmailOldMove, "Draw declined", TRUE);
14521             DisplayComment(cmailOldMove - 1, "Draw declined");
14522 #endif /*NOTDEF*/
14523         } else {
14524             DisplayError(_("There is no pending offer on this move"), 0);
14525         }
14526     } else {
14527         /* Not used for offers from chess program */
14528     }
14529 }
14530
14531 void
14532 RematchEvent ()
14533 {
14534     /* Issue ICS rematch command */
14535     if (appData.icsActive) {
14536         SendToICS(ics_prefix);
14537         SendToICS("rematch\n");
14538     }
14539 }
14540
14541 void
14542 CallFlagEvent ()
14543 {
14544     /* Call your opponent's flag (claim a win on time) */
14545     if (appData.icsActive) {
14546         SendToICS(ics_prefix);
14547         SendToICS("flag\n");
14548     } else {
14549         switch (gameMode) {
14550           default:
14551             return;
14552           case MachinePlaysWhite:
14553             if (whiteFlag) {
14554                 if (blackFlag)
14555                   GameEnds(GameIsDrawn, "Both players ran out of time",
14556                            GE_PLAYER);
14557                 else
14558                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
14559             } else {
14560                 DisplayError(_("Your opponent is not out of time"), 0);
14561             }
14562             break;
14563           case MachinePlaysBlack:
14564             if (blackFlag) {
14565                 if (whiteFlag)
14566                   GameEnds(GameIsDrawn, "Both players ran out of time",
14567                            GE_PLAYER);
14568                 else
14569                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
14570             } else {
14571                 DisplayError(_("Your opponent is not out of time"), 0);
14572             }
14573             break;
14574         }
14575     }
14576 }
14577
14578 void
14579 ClockClick (int which)
14580 {       // [HGM] code moved to back-end from winboard.c
14581         if(which) { // black clock
14582           if (gameMode == EditPosition || gameMode == IcsExamining) {
14583             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14584             SetBlackToPlayEvent();
14585           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
14586           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
14587           } else if (shiftKey) {
14588             AdjustClock(which, -1);
14589           } else if (gameMode == IcsPlayingWhite ||
14590                      gameMode == MachinePlaysBlack) {
14591             CallFlagEvent();
14592           }
14593         } else { // white clock
14594           if (gameMode == EditPosition || gameMode == IcsExamining) {
14595             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14596             SetWhiteToPlayEvent();
14597           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
14598           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
14599           } else if (shiftKey) {
14600             AdjustClock(which, -1);
14601           } else if (gameMode == IcsPlayingBlack ||
14602                    gameMode == MachinePlaysWhite) {
14603             CallFlagEvent();
14604           }
14605         }
14606 }
14607
14608 void
14609 DrawEvent ()
14610 {
14611     /* Offer draw or accept pending draw offer from opponent */
14612
14613     if (appData.icsActive) {
14614         /* Note: tournament rules require draw offers to be
14615            made after you make your move but before you punch
14616            your clock.  Currently ICS doesn't let you do that;
14617            instead, you immediately punch your clock after making
14618            a move, but you can offer a draw at any time. */
14619
14620         SendToICS(ics_prefix);
14621         SendToICS("draw\n");
14622         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
14623     } else if (cmailMsgLoaded) {
14624         if (currentMove == cmailOldMove &&
14625             commentList[cmailOldMove] != NULL &&
14626             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14627                    "Black offers a draw" : "White offers a draw")) {
14628             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14629             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14630         } else if (currentMove == cmailOldMove + 1) {
14631             char *offer = WhiteOnMove(cmailOldMove) ?
14632               "White offers a draw" : "Black offers a draw";
14633             AppendComment(currentMove, offer, TRUE);
14634             DisplayComment(currentMove - 1, offer);
14635             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
14636         } else {
14637             DisplayError(_("You must make your move before offering a draw"), 0);
14638             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14639         }
14640     } else if (first.offeredDraw) {
14641         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
14642     } else {
14643         if (first.sendDrawOffers) {
14644             SendToProgram("draw\n", &first);
14645             userOfferedDraw = TRUE;
14646         }
14647     }
14648 }
14649
14650 void
14651 AdjournEvent ()
14652 {
14653     /* Offer Adjourn or accept pending Adjourn offer from opponent */
14654
14655     if (appData.icsActive) {
14656         SendToICS(ics_prefix);
14657         SendToICS("adjourn\n");
14658     } else {
14659         /* Currently GNU Chess doesn't offer or accept Adjourns */
14660     }
14661 }
14662
14663
14664 void
14665 AbortEvent ()
14666 {
14667     /* Offer Abort or accept pending Abort offer from opponent */
14668
14669     if (appData.icsActive) {
14670         SendToICS(ics_prefix);
14671         SendToICS("abort\n");
14672     } else {
14673         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
14674     }
14675 }
14676
14677 void
14678 ResignEvent ()
14679 {
14680     /* Resign.  You can do this even if it's not your turn. */
14681
14682     if (appData.icsActive) {
14683         SendToICS(ics_prefix);
14684         SendToICS("resign\n");
14685     } else {
14686         switch (gameMode) {
14687           case MachinePlaysWhite:
14688             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14689             break;
14690           case MachinePlaysBlack:
14691             GameEnds(BlackWins, "White resigns", GE_PLAYER);
14692             break;
14693           case EditGame:
14694             if (cmailMsgLoaded) {
14695                 TruncateGame();
14696                 if (WhiteOnMove(cmailOldMove)) {
14697                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
14698                 } else {
14699                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14700                 }
14701                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
14702             }
14703             break;
14704           default:
14705             break;
14706         }
14707     }
14708 }
14709
14710
14711 void
14712 StopObservingEvent ()
14713 {
14714     /* Stop observing current games */
14715     SendToICS(ics_prefix);
14716     SendToICS("unobserve\n");
14717 }
14718
14719 void
14720 StopExaminingEvent ()
14721 {
14722     /* Stop observing current game */
14723     SendToICS(ics_prefix);
14724     SendToICS("unexamine\n");
14725 }
14726
14727 void
14728 ForwardInner (int target)
14729 {
14730     int limit; int oldSeekGraphUp = seekGraphUp;
14731
14732     if (appData.debugMode)
14733         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
14734                 target, currentMove, forwardMostMove);
14735
14736     if (gameMode == EditPosition)
14737       return;
14738
14739     seekGraphUp = FALSE;
14740     MarkTargetSquares(1);
14741
14742     if (gameMode == PlayFromGameFile && !pausing)
14743       PauseEvent();
14744
14745     if (gameMode == IcsExamining && pausing)
14746       limit = pauseExamForwardMostMove;
14747     else
14748       limit = forwardMostMove;
14749
14750     if (target > limit) target = limit;
14751
14752     if (target > 0 && moveList[target - 1][0]) {
14753         int fromX, fromY, toX, toY;
14754         toX = moveList[target - 1][2] - AAA;
14755         toY = moveList[target - 1][3] - ONE;
14756         if (moveList[target - 1][1] == '@') {
14757             if (appData.highlightLastMove) {
14758                 SetHighlights(-1, -1, toX, toY);
14759             }
14760         } else {
14761             fromX = moveList[target - 1][0] - AAA;
14762             fromY = moveList[target - 1][1] - ONE;
14763             if (target == currentMove + 1) {
14764                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
14765             }
14766             if (appData.highlightLastMove) {
14767                 SetHighlights(fromX, fromY, toX, toY);
14768             }
14769         }
14770     }
14771     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14772         gameMode == Training || gameMode == PlayFromGameFile ||
14773         gameMode == AnalyzeFile) {
14774         while (currentMove < target) {
14775             if(second.analyzing) SendMoveToProgram(currentMove, &second);
14776             SendMoveToProgram(currentMove++, &first);
14777         }
14778     } else {
14779         currentMove = target;
14780     }
14781
14782     if (gameMode == EditGame || gameMode == EndOfGame) {
14783         whiteTimeRemaining = timeRemaining[0][currentMove];
14784         blackTimeRemaining = timeRemaining[1][currentMove];
14785     }
14786     DisplayBothClocks();
14787     DisplayMove(currentMove - 1);
14788     DrawPosition(oldSeekGraphUp, boards[currentMove]);
14789     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14790     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
14791         DisplayComment(currentMove - 1, commentList[currentMove]);
14792     }
14793     ClearMap(); // [HGM] exclude: invalidate map
14794 }
14795
14796
14797 void
14798 ForwardEvent ()
14799 {
14800     if (gameMode == IcsExamining && !pausing) {
14801         SendToICS(ics_prefix);
14802         SendToICS("forward\n");
14803     } else {
14804         ForwardInner(currentMove + 1);
14805     }
14806 }
14807
14808 void
14809 ToEndEvent ()
14810 {
14811     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14812         /* to optimze, we temporarily turn off analysis mode while we feed
14813          * the remaining moves to the engine. Otherwise we get analysis output
14814          * after each move.
14815          */
14816         if (first.analysisSupport) {
14817           SendToProgram("exit\nforce\n", &first);
14818           first.analyzing = FALSE;
14819         }
14820     }
14821
14822     if (gameMode == IcsExamining && !pausing) {
14823         SendToICS(ics_prefix);
14824         SendToICS("forward 999999\n");
14825     } else {
14826         ForwardInner(forwardMostMove);
14827     }
14828
14829     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14830         /* we have fed all the moves, so reactivate analysis mode */
14831         SendToProgram("analyze\n", &first);
14832         first.analyzing = TRUE;
14833         /*first.maybeThinking = TRUE;*/
14834         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14835     }
14836 }
14837
14838 void
14839 BackwardInner (int target)
14840 {
14841     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
14842
14843     if (appData.debugMode)
14844         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
14845                 target, currentMove, forwardMostMove);
14846
14847     if (gameMode == EditPosition) return;
14848     seekGraphUp = FALSE;
14849     MarkTargetSquares(1);
14850     if (currentMove <= backwardMostMove) {
14851         ClearHighlights();
14852         DrawPosition(full_redraw, boards[currentMove]);
14853         return;
14854     }
14855     if (gameMode == PlayFromGameFile && !pausing)
14856       PauseEvent();
14857
14858     if (moveList[target][0]) {
14859         int fromX, fromY, toX, toY;
14860         toX = moveList[target][2] - AAA;
14861         toY = moveList[target][3] - ONE;
14862         if (moveList[target][1] == '@') {
14863             if (appData.highlightLastMove) {
14864                 SetHighlights(-1, -1, toX, toY);
14865             }
14866         } else {
14867             fromX = moveList[target][0] - AAA;
14868             fromY = moveList[target][1] - ONE;
14869             if (target == currentMove - 1) {
14870                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
14871             }
14872             if (appData.highlightLastMove) {
14873                 SetHighlights(fromX, fromY, toX, toY);
14874             }
14875         }
14876     }
14877     if (gameMode == EditGame || gameMode==AnalyzeMode ||
14878         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
14879         while (currentMove > target) {
14880             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
14881                 // null move cannot be undone. Reload program with move history before it.
14882                 int i;
14883                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
14884                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
14885                 }
14886                 SendBoard(&first, i); 
14887               if(second.analyzing) SendBoard(&second, i);
14888                 for(currentMove=i; currentMove<target; currentMove++) {
14889                     SendMoveToProgram(currentMove, &first);
14890                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
14891                 }
14892                 break;
14893             }
14894             SendToBoth("undo\n");
14895             currentMove--;
14896         }
14897     } else {
14898         currentMove = target;
14899     }
14900
14901     if (gameMode == EditGame || gameMode == EndOfGame) {
14902         whiteTimeRemaining = timeRemaining[0][currentMove];
14903         blackTimeRemaining = timeRemaining[1][currentMove];
14904     }
14905     DisplayBothClocks();
14906     DisplayMove(currentMove - 1);
14907     DrawPosition(full_redraw, boards[currentMove]);
14908     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14909     // [HGM] PV info: routine tests if comment empty
14910     DisplayComment(currentMove - 1, commentList[currentMove]);
14911     ClearMap(); // [HGM] exclude: invalidate map
14912 }
14913
14914 void
14915 BackwardEvent ()
14916 {
14917     if (gameMode == IcsExamining && !pausing) {
14918         SendToICS(ics_prefix);
14919         SendToICS("backward\n");
14920     } else {
14921         BackwardInner(currentMove - 1);
14922     }
14923 }
14924
14925 void
14926 ToStartEvent ()
14927 {
14928     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14929         /* to optimize, we temporarily turn off analysis mode while we undo
14930          * all the moves. Otherwise we get analysis output after each undo.
14931          */
14932         if (first.analysisSupport) {
14933           SendToProgram("exit\nforce\n", &first);
14934           first.analyzing = FALSE;
14935         }
14936     }
14937
14938     if (gameMode == IcsExamining && !pausing) {
14939         SendToICS(ics_prefix);
14940         SendToICS("backward 999999\n");
14941     } else {
14942         BackwardInner(backwardMostMove);
14943     }
14944
14945     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14946         /* we have fed all the moves, so reactivate analysis mode */
14947         SendToProgram("analyze\n", &first);
14948         first.analyzing = TRUE;
14949         /*first.maybeThinking = TRUE;*/
14950         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14951     }
14952 }
14953
14954 void
14955 ToNrEvent (int to)
14956 {
14957   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
14958   if (to >= forwardMostMove) to = forwardMostMove;
14959   if (to <= backwardMostMove) to = backwardMostMove;
14960   if (to < currentMove) {
14961     BackwardInner(to);
14962   } else {
14963     ForwardInner(to);
14964   }
14965 }
14966
14967 void
14968 RevertEvent (Boolean annotate)
14969 {
14970     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
14971         return;
14972     }
14973     if (gameMode != IcsExamining) {
14974         DisplayError(_("You are not examining a game"), 0);
14975         return;
14976     }
14977     if (pausing) {
14978         DisplayError(_("You can't revert while pausing"), 0);
14979         return;
14980     }
14981     SendToICS(ics_prefix);
14982     SendToICS("revert\n");
14983 }
14984
14985 void
14986 RetractMoveEvent ()
14987 {
14988     switch (gameMode) {
14989       case MachinePlaysWhite:
14990       case MachinePlaysBlack:
14991         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14992             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
14993             return;
14994         }
14995         if (forwardMostMove < 2) return;
14996         currentMove = forwardMostMove = forwardMostMove - 2;
14997         whiteTimeRemaining = timeRemaining[0][currentMove];
14998         blackTimeRemaining = timeRemaining[1][currentMove];
14999         DisplayBothClocks();
15000         DisplayMove(currentMove - 1);
15001         ClearHighlights();/*!! could figure this out*/
15002         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
15003         SendToProgram("remove\n", &first);
15004         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
15005         break;
15006
15007       case BeginningOfGame:
15008       default:
15009         break;
15010
15011       case IcsPlayingWhite:
15012       case IcsPlayingBlack:
15013         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
15014             SendToICS(ics_prefix);
15015             SendToICS("takeback 2\n");
15016         } else {
15017             SendToICS(ics_prefix);
15018             SendToICS("takeback 1\n");
15019         }
15020         break;
15021     }
15022 }
15023
15024 void
15025 MoveNowEvent ()
15026 {
15027     ChessProgramState *cps;
15028
15029     switch (gameMode) {
15030       case MachinePlaysWhite:
15031         if (!WhiteOnMove(forwardMostMove)) {
15032             DisplayError(_("It is your turn"), 0);
15033             return;
15034         }
15035         cps = &first;
15036         break;
15037       case MachinePlaysBlack:
15038         if (WhiteOnMove(forwardMostMove)) {
15039             DisplayError(_("It is your turn"), 0);
15040             return;
15041         }
15042         cps = &first;
15043         break;
15044       case TwoMachinesPlay:
15045         if (WhiteOnMove(forwardMostMove) ==
15046             (first.twoMachinesColor[0] == 'w')) {
15047             cps = &first;
15048         } else {
15049             cps = &second;
15050         }
15051         break;
15052       case BeginningOfGame:
15053       default:
15054         return;
15055     }
15056     SendToProgram("?\n", cps);
15057 }
15058
15059 void
15060 TruncateGameEvent ()
15061 {
15062     EditGameEvent();
15063     if (gameMode != EditGame) return;
15064     TruncateGame();
15065 }
15066
15067 void
15068 TruncateGame ()
15069 {
15070     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
15071     if (forwardMostMove > currentMove) {
15072         if (gameInfo.resultDetails != NULL) {
15073             free(gameInfo.resultDetails);
15074             gameInfo.resultDetails = NULL;
15075             gameInfo.result = GameUnfinished;
15076         }
15077         forwardMostMove = currentMove;
15078         HistorySet(parseList, backwardMostMove, forwardMostMove,
15079                    currentMove-1);
15080     }
15081 }
15082
15083 void
15084 HintEvent ()
15085 {
15086     if (appData.noChessProgram) return;
15087     switch (gameMode) {
15088       case MachinePlaysWhite:
15089         if (WhiteOnMove(forwardMostMove)) {
15090             DisplayError(_("Wait until your turn"), 0);
15091             return;
15092         }
15093         break;
15094       case BeginningOfGame:
15095       case MachinePlaysBlack:
15096         if (!WhiteOnMove(forwardMostMove)) {
15097             DisplayError(_("Wait until your turn"), 0);
15098             return;
15099         }
15100         break;
15101       default:
15102         DisplayError(_("No hint available"), 0);
15103         return;
15104     }
15105     SendToProgram("hint\n", &first);
15106     hintRequested = TRUE;
15107 }
15108
15109 void
15110 CreateBookEvent ()
15111 {
15112     ListGame * lg = (ListGame *) gameList.head;
15113     FILE *f;
15114     int nItem;
15115     static int secondTime = FALSE;
15116
15117     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
15118         DisplayError(_("Game list not loaded or empty"), 0);
15119         return;
15120     }
15121
15122     if(!secondTime && (f = fopen(appData.polyglotBook, "r"))) {
15123         fclose(f);
15124         secondTime++;
15125         DisplayNote(_("Book file exists! Try again for overwrite."));
15126         return;
15127     }
15128
15129     creatingBook = TRUE;
15130     secondTime = FALSE;
15131
15132     /* Get list size */
15133     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
15134         LoadGame(f, nItem, "", TRUE);
15135         AddGameToBook(TRUE);
15136         lg = (ListGame *) lg->node.succ;
15137     }
15138
15139     creatingBook = FALSE;
15140     FlushBook();
15141 }
15142
15143 void
15144 BookEvent ()
15145 {
15146     if (appData.noChessProgram) return;
15147     switch (gameMode) {
15148       case MachinePlaysWhite:
15149         if (WhiteOnMove(forwardMostMove)) {
15150             DisplayError(_("Wait until your turn"), 0);
15151             return;
15152         }
15153         break;
15154       case BeginningOfGame:
15155       case MachinePlaysBlack:
15156         if (!WhiteOnMove(forwardMostMove)) {
15157             DisplayError(_("Wait until your turn"), 0);
15158             return;
15159         }
15160         break;
15161       case EditPosition:
15162         EditPositionDone(TRUE);
15163         break;
15164       case TwoMachinesPlay:
15165         return;
15166       default:
15167         break;
15168     }
15169     SendToProgram("bk\n", &first);
15170     bookOutput[0] = NULLCHAR;
15171     bookRequested = TRUE;
15172 }
15173
15174 void
15175 AboutGameEvent ()
15176 {
15177     char *tags = PGNTags(&gameInfo);
15178     TagsPopUp(tags, CmailMsg());
15179     free(tags);
15180 }
15181
15182 /* end button procedures */
15183
15184 void
15185 PrintPosition (FILE *fp, int move)
15186 {
15187     int i, j;
15188
15189     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15190         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15191             char c = PieceToChar(boards[move][i][j]);
15192             fputc(c == 'x' ? '.' : c, fp);
15193             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
15194         }
15195     }
15196     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
15197       fprintf(fp, "white to play\n");
15198     else
15199       fprintf(fp, "black to play\n");
15200 }
15201
15202 void
15203 PrintOpponents (FILE *fp)
15204 {
15205     if (gameInfo.white != NULL) {
15206         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
15207     } else {
15208         fprintf(fp, "\n");
15209     }
15210 }
15211
15212 /* Find last component of program's own name, using some heuristics */
15213 void
15214 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
15215 {
15216     char *p, *q, c;
15217     int local = (strcmp(host, "localhost") == 0);
15218     while (!local && (p = strchr(prog, ';')) != NULL) {
15219         p++;
15220         while (*p == ' ') p++;
15221         prog = p;
15222     }
15223     if (*prog == '"' || *prog == '\'') {
15224         q = strchr(prog + 1, *prog);
15225     } else {
15226         q = strchr(prog, ' ');
15227     }
15228     if (q == NULL) q = prog + strlen(prog);
15229     p = q;
15230     while (p >= prog && *p != '/' && *p != '\\') p--;
15231     p++;
15232     if(p == prog && *p == '"') p++;
15233     c = *q; *q = 0;
15234     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
15235     memcpy(buf, p, q - p);
15236     buf[q - p] = NULLCHAR;
15237     if (!local) {
15238         strcat(buf, "@");
15239         strcat(buf, host);
15240     }
15241 }
15242
15243 char *
15244 TimeControlTagValue ()
15245 {
15246     char buf[MSG_SIZ];
15247     if (!appData.clockMode) {
15248       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
15249     } else if (movesPerSession > 0) {
15250       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
15251     } else if (timeIncrement == 0) {
15252       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
15253     } else {
15254       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
15255     }
15256     return StrSave(buf);
15257 }
15258
15259 void
15260 SetGameInfo ()
15261 {
15262     /* This routine is used only for certain modes */
15263     VariantClass v = gameInfo.variant;
15264     ChessMove r = GameUnfinished;
15265     char *p = NULL;
15266
15267     if(keepInfo) return;
15268
15269     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
15270         r = gameInfo.result;
15271         p = gameInfo.resultDetails;
15272         gameInfo.resultDetails = NULL;
15273     }
15274     ClearGameInfo(&gameInfo);
15275     gameInfo.variant = v;
15276
15277     switch (gameMode) {
15278       case MachinePlaysWhite:
15279         gameInfo.event = StrSave( appData.pgnEventHeader );
15280         gameInfo.site = StrSave(HostName());
15281         gameInfo.date = PGNDate();
15282         gameInfo.round = StrSave("-");
15283         gameInfo.white = StrSave(first.tidy);
15284         gameInfo.black = StrSave(UserName());
15285         gameInfo.timeControl = TimeControlTagValue();
15286         break;
15287
15288       case MachinePlaysBlack:
15289         gameInfo.event = StrSave( appData.pgnEventHeader );
15290         gameInfo.site = StrSave(HostName());
15291         gameInfo.date = PGNDate();
15292         gameInfo.round = StrSave("-");
15293         gameInfo.white = StrSave(UserName());
15294         gameInfo.black = StrSave(first.tidy);
15295         gameInfo.timeControl = TimeControlTagValue();
15296         break;
15297
15298       case TwoMachinesPlay:
15299         gameInfo.event = StrSave( appData.pgnEventHeader );
15300         gameInfo.site = StrSave(HostName());
15301         gameInfo.date = PGNDate();
15302         if (roundNr > 0) {
15303             char buf[MSG_SIZ];
15304             snprintf(buf, MSG_SIZ, "%d", roundNr);
15305             gameInfo.round = StrSave(buf);
15306         } else {
15307             gameInfo.round = StrSave("-");
15308         }
15309         if (first.twoMachinesColor[0] == 'w') {
15310             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15311             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15312         } else {
15313             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15314             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15315         }
15316         gameInfo.timeControl = TimeControlTagValue();
15317         break;
15318
15319       case EditGame:
15320         gameInfo.event = StrSave("Edited game");
15321         gameInfo.site = StrSave(HostName());
15322         gameInfo.date = PGNDate();
15323         gameInfo.round = StrSave("-");
15324         gameInfo.white = StrSave("-");
15325         gameInfo.black = StrSave("-");
15326         gameInfo.result = r;
15327         gameInfo.resultDetails = p;
15328         break;
15329
15330       case EditPosition:
15331         gameInfo.event = StrSave("Edited position");
15332         gameInfo.site = StrSave(HostName());
15333         gameInfo.date = PGNDate();
15334         gameInfo.round = StrSave("-");
15335         gameInfo.white = StrSave("-");
15336         gameInfo.black = StrSave("-");
15337         break;
15338
15339       case IcsPlayingWhite:
15340       case IcsPlayingBlack:
15341       case IcsObserving:
15342       case IcsExamining:
15343         break;
15344
15345       case PlayFromGameFile:
15346         gameInfo.event = StrSave("Game from non-PGN file");
15347         gameInfo.site = StrSave(HostName());
15348         gameInfo.date = PGNDate();
15349         gameInfo.round = StrSave("-");
15350         gameInfo.white = StrSave("?");
15351         gameInfo.black = StrSave("?");
15352         break;
15353
15354       default:
15355         break;
15356     }
15357 }
15358
15359 void
15360 ReplaceComment (int index, char *text)
15361 {
15362     int len;
15363     char *p;
15364     float score;
15365
15366     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
15367        pvInfoList[index-1].depth == len &&
15368        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
15369        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
15370     while (*text == '\n') text++;
15371     len = strlen(text);
15372     while (len > 0 && text[len - 1] == '\n') len--;
15373
15374     if (commentList[index] != NULL)
15375       free(commentList[index]);
15376
15377     if (len == 0) {
15378         commentList[index] = NULL;
15379         return;
15380     }
15381   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
15382       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
15383       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
15384     commentList[index] = (char *) malloc(len + 2);
15385     strncpy(commentList[index], text, len);
15386     commentList[index][len] = '\n';
15387     commentList[index][len + 1] = NULLCHAR;
15388   } else {
15389     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
15390     char *p;
15391     commentList[index] = (char *) malloc(len + 7);
15392     safeStrCpy(commentList[index], "{\n", 3);
15393     safeStrCpy(commentList[index]+2, text, len+1);
15394     commentList[index][len+2] = NULLCHAR;
15395     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
15396     strcat(commentList[index], "\n}\n");
15397   }
15398 }
15399
15400 void
15401 CrushCRs (char *text)
15402 {
15403   char *p = text;
15404   char *q = text;
15405   char ch;
15406
15407   do {
15408     ch = *p++;
15409     if (ch == '\r') continue;
15410     *q++ = ch;
15411   } while (ch != '\0');
15412 }
15413
15414 void
15415 AppendComment (int index, char *text, Boolean addBraces)
15416 /* addBraces  tells if we should add {} */
15417 {
15418     int oldlen, len;
15419     char *old;
15420
15421 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
15422     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
15423
15424     CrushCRs(text);
15425     while (*text == '\n') text++;
15426     len = strlen(text);
15427     while (len > 0 && text[len - 1] == '\n') len--;
15428     text[len] = NULLCHAR;
15429
15430     if (len == 0) return;
15431
15432     if (commentList[index] != NULL) {
15433       Boolean addClosingBrace = addBraces;
15434         old = commentList[index];
15435         oldlen = strlen(old);
15436         while(commentList[index][oldlen-1] ==  '\n')
15437           commentList[index][--oldlen] = NULLCHAR;
15438         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
15439         safeStrCpy(commentList[index], old, oldlen + len + 6);
15440         free(old);
15441         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
15442         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
15443           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
15444           while (*text == '\n') { text++; len--; }
15445           commentList[index][--oldlen] = NULLCHAR;
15446       }
15447         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
15448         else          strcat(commentList[index], "\n");
15449         strcat(commentList[index], text);
15450         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
15451         else          strcat(commentList[index], "\n");
15452     } else {
15453         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
15454         if(addBraces)
15455           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
15456         else commentList[index][0] = NULLCHAR;
15457         strcat(commentList[index], text);
15458         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
15459         if(addBraces == TRUE) strcat(commentList[index], "}\n");
15460     }
15461 }
15462
15463 static char *
15464 FindStr (char * text, char * sub_text)
15465 {
15466     char * result = strstr( text, sub_text );
15467
15468     if( result != NULL ) {
15469         result += strlen( sub_text );
15470     }
15471
15472     return result;
15473 }
15474
15475 /* [AS] Try to extract PV info from PGN comment */
15476 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
15477 char *
15478 GetInfoFromComment (int index, char * text)
15479 {
15480     char * sep = text, *p;
15481
15482     if( text != NULL && index > 0 ) {
15483         int score = 0;
15484         int depth = 0;
15485         int time = -1, sec = 0, deci;
15486         char * s_eval = FindStr( text, "[%eval " );
15487         char * s_emt = FindStr( text, "[%emt " );
15488
15489         if( s_eval != NULL || s_emt != NULL ) {
15490             /* New style */
15491             char delim;
15492
15493             if( s_eval != NULL ) {
15494                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
15495                     return text;
15496                 }
15497
15498                 if( delim != ']' ) {
15499                     return text;
15500                 }
15501             }
15502
15503             if( s_emt != NULL ) {
15504             }
15505                 return text;
15506         }
15507         else {
15508             /* We expect something like: [+|-]nnn.nn/dd */
15509             int score_lo = 0;
15510
15511             if(*text != '{') return text; // [HGM] braces: must be normal comment
15512
15513             sep = strchr( text, '/' );
15514             if( sep == NULL || sep < (text+4) ) {
15515                 return text;
15516             }
15517
15518             p = text;
15519             if(p[1] == '(') { // comment starts with PV
15520                p = strchr(p, ')'); // locate end of PV
15521                if(p == NULL || sep < p+5) return text;
15522                // at this point we have something like "{(.*) +0.23/6 ..."
15523                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
15524                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
15525                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
15526             }
15527             time = -1; sec = -1; deci = -1;
15528             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
15529                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
15530                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
15531                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
15532                 return text;
15533             }
15534
15535             if( score_lo < 0 || score_lo >= 100 ) {
15536                 return text;
15537             }
15538
15539             if(sec >= 0) time = 600*time + 10*sec; else
15540             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
15541
15542             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
15543
15544             /* [HGM] PV time: now locate end of PV info */
15545             while( *++sep >= '0' && *sep <= '9'); // strip depth
15546             if(time >= 0)
15547             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
15548             if(sec >= 0)
15549             while( *++sep >= '0' && *sep <= '9'); // strip seconds
15550             if(deci >= 0)
15551             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
15552             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
15553         }
15554
15555         if( depth <= 0 ) {
15556             return text;
15557         }
15558
15559         if( time < 0 ) {
15560             time = -1;
15561         }
15562
15563         pvInfoList[index-1].depth = depth;
15564         pvInfoList[index-1].score = score;
15565         pvInfoList[index-1].time  = 10*time; // centi-sec
15566         if(*sep == '}') *sep = 0; else *--sep = '{';
15567         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
15568     }
15569     return sep;
15570 }
15571
15572 void
15573 SendToProgram (char *message, ChessProgramState *cps)
15574 {
15575     int count, outCount, error;
15576     char buf[MSG_SIZ];
15577
15578     if (cps->pr == NoProc) return;
15579     Attention(cps);
15580
15581     if (appData.debugMode) {
15582         TimeMark now;
15583         GetTimeMark(&now);
15584         fprintf(debugFP, "%ld >%-6s: %s",
15585                 SubtractTimeMarks(&now, &programStartTime),
15586                 cps->which, message);
15587         if(serverFP)
15588             fprintf(serverFP, "%ld >%-6s: %s",
15589                 SubtractTimeMarks(&now, &programStartTime),
15590                 cps->which, message), fflush(serverFP);
15591     }
15592
15593     count = strlen(message);
15594     outCount = OutputToProcess(cps->pr, message, count, &error);
15595     if (outCount < count && !exiting
15596                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
15597       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15598       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
15599         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15600             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15601                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15602                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15603                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15604             } else {
15605                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15606                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15607                 gameInfo.result = res;
15608             }
15609             gameInfo.resultDetails = StrSave(buf);
15610         }
15611         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15612         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15613     }
15614 }
15615
15616 void
15617 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
15618 {
15619     char *end_str;
15620     char buf[MSG_SIZ];
15621     ChessProgramState *cps = (ChessProgramState *)closure;
15622
15623     if (isr != cps->isr) return; /* Killed intentionally */
15624     if (count <= 0) {
15625         if (count == 0) {
15626             RemoveInputSource(cps->isr);
15627             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
15628                     _(cps->which), cps->program);
15629             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
15630             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15631                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15632                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15633                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15634                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15635                 } else {
15636                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15637                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15638                     gameInfo.result = res;
15639                 }
15640                 gameInfo.resultDetails = StrSave(buf);
15641             }
15642             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15643             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
15644         } else {
15645             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
15646                     _(cps->which), cps->program);
15647             RemoveInputSource(cps->isr);
15648
15649             /* [AS] Program is misbehaving badly... kill it */
15650             if( count == -2 ) {
15651                 DestroyChildProcess( cps->pr, 9 );
15652                 cps->pr = NoProc;
15653             }
15654
15655             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15656         }
15657         return;
15658     }
15659
15660     if ((end_str = strchr(message, '\r')) != NULL)
15661       *end_str = NULLCHAR;
15662     if ((end_str = strchr(message, '\n')) != NULL)
15663       *end_str = NULLCHAR;
15664
15665     if (appData.debugMode) {
15666         TimeMark now; int print = 1;
15667         char *quote = ""; char c; int i;
15668
15669         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
15670                 char start = message[0];
15671                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
15672                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
15673                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
15674                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
15675                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
15676                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
15677                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
15678                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
15679                    sscanf(message, "hint: %c", &c)!=1 && 
15680                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
15681                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
15682                     print = (appData.engineComments >= 2);
15683                 }
15684                 message[0] = start; // restore original message
15685         }
15686         if(print) {
15687                 GetTimeMark(&now);
15688                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
15689                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15690                         quote,
15691                         message);
15692                 if(serverFP)
15693                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
15694                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15695                         quote,
15696                         message), fflush(serverFP);
15697         }
15698     }
15699
15700     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
15701     if (appData.icsEngineAnalyze) {
15702         if (strstr(message, "whisper") != NULL ||
15703              strstr(message, "kibitz") != NULL ||
15704             strstr(message, "tellics") != NULL) return;
15705     }
15706
15707     HandleMachineMove(message, cps);
15708 }
15709
15710
15711 void
15712 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
15713 {
15714     char buf[MSG_SIZ];
15715     int seconds;
15716
15717     if( timeControl_2 > 0 ) {
15718         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
15719             tc = timeControl_2;
15720         }
15721     }
15722     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
15723     inc /= cps->timeOdds;
15724     st  /= cps->timeOdds;
15725
15726     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
15727
15728     if (st > 0) {
15729       /* Set exact time per move, normally using st command */
15730       if (cps->stKludge) {
15731         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
15732         seconds = st % 60;
15733         if (seconds == 0) {
15734           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
15735         } else {
15736           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
15737         }
15738       } else {
15739         snprintf(buf, MSG_SIZ, "st %d\n", st);
15740       }
15741     } else {
15742       /* Set conventional or incremental time control, using level command */
15743       if (seconds == 0) {
15744         /* Note old gnuchess bug -- minutes:seconds used to not work.
15745            Fixed in later versions, but still avoid :seconds
15746            when seconds is 0. */
15747         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
15748       } else {
15749         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
15750                  seconds, inc/1000.);
15751       }
15752     }
15753     SendToProgram(buf, cps);
15754
15755     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
15756     /* Orthogonally, limit search to given depth */
15757     if (sd > 0) {
15758       if (cps->sdKludge) {
15759         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
15760       } else {
15761         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
15762       }
15763       SendToProgram(buf, cps);
15764     }
15765
15766     if(cps->nps >= 0) { /* [HGM] nps */
15767         if(cps->supportsNPS == FALSE)
15768           cps->nps = -1; // don't use if engine explicitly says not supported!
15769         else {
15770           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
15771           SendToProgram(buf, cps);
15772         }
15773     }
15774 }
15775
15776 ChessProgramState *
15777 WhitePlayer ()
15778 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
15779 {
15780     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
15781        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
15782         return &second;
15783     return &first;
15784 }
15785
15786 void
15787 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
15788 {
15789     char message[MSG_SIZ];
15790     long time, otime;
15791
15792     /* Note: this routine must be called when the clocks are stopped
15793        or when they have *just* been set or switched; otherwise
15794        it will be off by the time since the current tick started.
15795     */
15796     if (machineWhite) {
15797         time = whiteTimeRemaining / 10;
15798         otime = blackTimeRemaining / 10;
15799     } else {
15800         time = blackTimeRemaining / 10;
15801         otime = whiteTimeRemaining / 10;
15802     }
15803     /* [HGM] translate opponent's time by time-odds factor */
15804     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
15805
15806     if (time <= 0) time = 1;
15807     if (otime <= 0) otime = 1;
15808
15809     snprintf(message, MSG_SIZ, "time %ld\n", time);
15810     SendToProgram(message, cps);
15811
15812     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
15813     SendToProgram(message, cps);
15814 }
15815
15816 int
15817 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15818 {
15819   char buf[MSG_SIZ];
15820   int len = strlen(name);
15821   int val;
15822
15823   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15824     (*p) += len + 1;
15825     sscanf(*p, "%d", &val);
15826     *loc = (val != 0);
15827     while (**p && **p != ' ')
15828       (*p)++;
15829     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15830     SendToProgram(buf, cps);
15831     return TRUE;
15832   }
15833   return FALSE;
15834 }
15835
15836 int
15837 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15838 {
15839   char buf[MSG_SIZ];
15840   int len = strlen(name);
15841   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15842     (*p) += len + 1;
15843     sscanf(*p, "%d", loc);
15844     while (**p && **p != ' ') (*p)++;
15845     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15846     SendToProgram(buf, cps);
15847     return TRUE;
15848   }
15849   return FALSE;
15850 }
15851
15852 int
15853 StringFeature (char **p, char *name, char loc[], ChessProgramState *cps)
15854 {
15855   char buf[MSG_SIZ];
15856   int len = strlen(name);
15857   if (strncmp((*p), name, len) == 0
15858       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
15859     (*p) += len + 2;
15860     sscanf(*p, "%[^\"]", loc);
15861     while (**p && **p != '\"') (*p)++;
15862     if (**p == '\"') (*p)++;
15863     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15864     SendToProgram(buf, cps);
15865     return TRUE;
15866   }
15867   return FALSE;
15868 }
15869
15870 int
15871 ParseOption (Option *opt, ChessProgramState *cps)
15872 // [HGM] options: process the string that defines an engine option, and determine
15873 // name, type, default value, and allowed value range
15874 {
15875         char *p, *q, buf[MSG_SIZ];
15876         int n, min = (-1)<<31, max = 1<<31, def;
15877
15878         if(p = strstr(opt->name, " -spin ")) {
15879             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15880             if(max < min) max = min; // enforce consistency
15881             if(def < min) def = min;
15882             if(def > max) def = max;
15883             opt->value = def;
15884             opt->min = min;
15885             opt->max = max;
15886             opt->type = Spin;
15887         } else if((p = strstr(opt->name, " -slider "))) {
15888             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
15889             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15890             if(max < min) max = min; // enforce consistency
15891             if(def < min) def = min;
15892             if(def > max) def = max;
15893             opt->value = def;
15894             opt->min = min;
15895             opt->max = max;
15896             opt->type = Spin; // Slider;
15897         } else if((p = strstr(opt->name, " -string "))) {
15898             opt->textValue = p+9;
15899             opt->type = TextBox;
15900         } else if((p = strstr(opt->name, " -file "))) {
15901             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15902             opt->textValue = p+7;
15903             opt->type = FileName; // FileName;
15904         } else if((p = strstr(opt->name, " -path "))) {
15905             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15906             opt->textValue = p+7;
15907             opt->type = PathName; // PathName;
15908         } else if(p = strstr(opt->name, " -check ")) {
15909             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
15910             opt->value = (def != 0);
15911             opt->type = CheckBox;
15912         } else if(p = strstr(opt->name, " -combo ")) {
15913             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
15914             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
15915             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
15916             opt->value = n = 0;
15917             while(q = StrStr(q, " /// ")) {
15918                 n++; *q = 0;    // count choices, and null-terminate each of them
15919                 q += 5;
15920                 if(*q == '*') { // remember default, which is marked with * prefix
15921                     q++;
15922                     opt->value = n;
15923                 }
15924                 cps->comboList[cps->comboCnt++] = q;
15925             }
15926             cps->comboList[cps->comboCnt++] = NULL;
15927             opt->max = n + 1;
15928             opt->type = ComboBox;
15929         } else if(p = strstr(opt->name, " -button")) {
15930             opt->type = Button;
15931         } else if(p = strstr(opt->name, " -save")) {
15932             opt->type = SaveButton;
15933         } else return FALSE;
15934         *p = 0; // terminate option name
15935         // now look if the command-line options define a setting for this engine option.
15936         if(cps->optionSettings && cps->optionSettings[0])
15937             p = strstr(cps->optionSettings, opt->name); else p = NULL;
15938         if(p && (p == cps->optionSettings || p[-1] == ',')) {
15939           snprintf(buf, MSG_SIZ, "option %s", p);
15940                 if(p = strstr(buf, ",")) *p = 0;
15941                 if(q = strchr(buf, '=')) switch(opt->type) {
15942                     case ComboBox:
15943                         for(n=0; n<opt->max; n++)
15944                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
15945                         break;
15946                     case TextBox:
15947                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
15948                         break;
15949                     case Spin:
15950                     case CheckBox:
15951                         opt->value = atoi(q+1);
15952                     default:
15953                         break;
15954                 }
15955                 strcat(buf, "\n");
15956                 SendToProgram(buf, cps);
15957         }
15958         return TRUE;
15959 }
15960
15961 void
15962 FeatureDone (ChessProgramState *cps, int val)
15963 {
15964   DelayedEventCallback cb = GetDelayedEvent();
15965   if ((cb == InitBackEnd3 && cps == &first) ||
15966       (cb == SettingsMenuIfReady && cps == &second) ||
15967       (cb == LoadEngine) ||
15968       (cb == TwoMachinesEventIfReady)) {
15969     CancelDelayedEvent();
15970     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
15971   }
15972   cps->initDone = val;
15973 }
15974
15975 /* Parse feature command from engine */
15976 void
15977 ParseFeatures (char *args, ChessProgramState *cps)
15978 {
15979   char *p = args;
15980   char *q;
15981   int val;
15982   char buf[MSG_SIZ];
15983
15984   for (;;) {
15985     while (*p == ' ') p++;
15986     if (*p == NULLCHAR) return;
15987
15988     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
15989     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
15990     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
15991     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
15992     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
15993     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
15994     if (BoolFeature(&p, "reuse", &val, cps)) {
15995       /* Engine can disable reuse, but can't enable it if user said no */
15996       if (!val) cps->reuse = FALSE;
15997       continue;
15998     }
15999     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
16000     if (StringFeature(&p, "myname", cps->tidy, cps)) {
16001       if (gameMode == TwoMachinesPlay) {
16002         DisplayTwoMachinesTitle();
16003       } else {
16004         DisplayTitle("");
16005       }
16006       continue;
16007     }
16008     if (StringFeature(&p, "variants", cps->variants, cps)) continue;
16009     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
16010     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
16011     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
16012     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
16013     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
16014     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
16015     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
16016     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
16017     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
16018     if (IntFeature(&p, "done", &val, cps)) {
16019       FeatureDone(cps, val);
16020       continue;
16021     }
16022     /* Added by Tord: */
16023     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
16024     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
16025     /* End of additions by Tord */
16026
16027     /* [HGM] added features: */
16028     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
16029     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
16030     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
16031     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
16032     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
16033     if (StringFeature(&p, "egt", cps->egtFormats, cps)) continue;
16034     if (StringFeature(&p, "option", buf, cps)) {
16035         FREE(cps->option[cps->nrOptions].name);
16036         cps->option[cps->nrOptions].name = malloc(MSG_SIZ);
16037         safeStrCpy(cps->option[cps->nrOptions].name, buf, MSG_SIZ);
16038         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
16039           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
16040             SendToProgram(buf, cps);
16041             continue;
16042         }
16043         if(cps->nrOptions >= MAX_OPTIONS) {
16044             cps->nrOptions--;
16045             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
16046             DisplayError(buf, 0);
16047         }
16048         continue;
16049     }
16050     /* End of additions by HGM */
16051
16052     /* unknown feature: complain and skip */
16053     q = p;
16054     while (*q && *q != '=') q++;
16055     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
16056     SendToProgram(buf, cps);
16057     p = q;
16058     if (*p == '=') {
16059       p++;
16060       if (*p == '\"') {
16061         p++;
16062         while (*p && *p != '\"') p++;
16063         if (*p == '\"') p++;
16064       } else {
16065         while (*p && *p != ' ') p++;
16066       }
16067     }
16068   }
16069
16070 }
16071
16072 void
16073 PeriodicUpdatesEvent (int newState)
16074 {
16075     if (newState == appData.periodicUpdates)
16076       return;
16077
16078     appData.periodicUpdates=newState;
16079
16080     /* Display type changes, so update it now */
16081 //    DisplayAnalysis();
16082
16083     /* Get the ball rolling again... */
16084     if (newState) {
16085         AnalysisPeriodicEvent(1);
16086         StartAnalysisClock();
16087     }
16088 }
16089
16090 void
16091 PonderNextMoveEvent (int newState)
16092 {
16093     if (newState == appData.ponderNextMove) return;
16094     if (gameMode == EditPosition) EditPositionDone(TRUE);
16095     if (newState) {
16096         SendToProgram("hard\n", &first);
16097         if (gameMode == TwoMachinesPlay) {
16098             SendToProgram("hard\n", &second);
16099         }
16100     } else {
16101         SendToProgram("easy\n", &first);
16102         thinkOutput[0] = NULLCHAR;
16103         if (gameMode == TwoMachinesPlay) {
16104             SendToProgram("easy\n", &second);
16105         }
16106     }
16107     appData.ponderNextMove = newState;
16108 }
16109
16110 void
16111 NewSettingEvent (int option, int *feature, char *command, int value)
16112 {
16113     char buf[MSG_SIZ];
16114
16115     if (gameMode == EditPosition) EditPositionDone(TRUE);
16116     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
16117     if(feature == NULL || *feature) SendToProgram(buf, &first);
16118     if (gameMode == TwoMachinesPlay) {
16119         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
16120     }
16121 }
16122
16123 void
16124 ShowThinkingEvent ()
16125 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
16126 {
16127     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
16128     int newState = appData.showThinking
16129         // [HGM] thinking: other features now need thinking output as well
16130         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
16131
16132     if (oldState == newState) return;
16133     oldState = newState;
16134     if (gameMode == EditPosition) EditPositionDone(TRUE);
16135     if (oldState) {
16136         SendToProgram("post\n", &first);
16137         if (gameMode == TwoMachinesPlay) {
16138             SendToProgram("post\n", &second);
16139         }
16140     } else {
16141         SendToProgram("nopost\n", &first);
16142         thinkOutput[0] = NULLCHAR;
16143         if (gameMode == TwoMachinesPlay) {
16144             SendToProgram("nopost\n", &second);
16145         }
16146     }
16147 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
16148 }
16149
16150 void
16151 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
16152 {
16153   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
16154   if (pr == NoProc) return;
16155   AskQuestion(title, question, replyPrefix, pr);
16156 }
16157
16158 void
16159 TypeInEvent (char firstChar)
16160 {
16161     if ((gameMode == BeginningOfGame && !appData.icsActive) || 
16162         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
16163         gameMode == AnalyzeMode || gameMode == EditGame || 
16164         gameMode == EditPosition || gameMode == IcsExamining ||
16165         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
16166         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
16167                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
16168                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
16169         gameMode == Training) PopUpMoveDialog(firstChar);
16170 }
16171
16172 void
16173 TypeInDoneEvent (char *move)
16174 {
16175         Board board;
16176         int n, fromX, fromY, toX, toY;
16177         char promoChar;
16178         ChessMove moveType;
16179
16180         // [HGM] FENedit
16181         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
16182                 EditPositionPasteFEN(move);
16183                 return;
16184         }
16185         // [HGM] movenum: allow move number to be typed in any mode
16186         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
16187           ToNrEvent(2*n-1);
16188           return;
16189         }
16190         // undocumented kludge: allow command-line option to be typed in!
16191         // (potentially fatal, and does not implement the effect of the option.)
16192         // should only be used for options that are values on which future decisions will be made,
16193         // and definitely not on options that would be used during initialization.
16194         if(strstr(move, "!!! -") == move) {
16195             ParseArgsFromString(move+4);
16196             return;
16197         }
16198
16199       if (gameMode != EditGame && currentMove != forwardMostMove && 
16200         gameMode != Training) {
16201         DisplayMoveError(_("Displayed move is not current"));
16202       } else {
16203         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
16204           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
16205         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
16206         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
16207           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
16208           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     
16209         } else {
16210           DisplayMoveError(_("Could not parse move"));
16211         }
16212       }
16213 }
16214
16215 void
16216 DisplayMove (int moveNumber)
16217 {
16218     char message[MSG_SIZ];
16219     char res[MSG_SIZ];
16220     char cpThinkOutput[MSG_SIZ];
16221
16222     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
16223
16224     if (moveNumber == forwardMostMove - 1 ||
16225         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16226
16227         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
16228
16229         if (strchr(cpThinkOutput, '\n')) {
16230             *strchr(cpThinkOutput, '\n') = NULLCHAR;
16231         }
16232     } else {
16233         *cpThinkOutput = NULLCHAR;
16234     }
16235
16236     /* [AS] Hide thinking from human user */
16237     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
16238         *cpThinkOutput = NULLCHAR;
16239         if( thinkOutput[0] != NULLCHAR ) {
16240             int i;
16241
16242             for( i=0; i<=hiddenThinkOutputState; i++ ) {
16243                 cpThinkOutput[i] = '.';
16244             }
16245             cpThinkOutput[i] = NULLCHAR;
16246             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
16247         }
16248     }
16249
16250     if (moveNumber == forwardMostMove - 1 &&
16251         gameInfo.resultDetails != NULL) {
16252         if (gameInfo.resultDetails[0] == NULLCHAR) {
16253           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
16254         } else {
16255           snprintf(res, MSG_SIZ, " {%s} %s",
16256                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
16257         }
16258     } else {
16259         res[0] = NULLCHAR;
16260     }
16261
16262     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16263         DisplayMessage(res, cpThinkOutput);
16264     } else {
16265       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
16266                 WhiteOnMove(moveNumber) ? " " : ".. ",
16267                 parseList[moveNumber], res);
16268         DisplayMessage(message, cpThinkOutput);
16269     }
16270 }
16271
16272 void
16273 DisplayComment (int moveNumber, char *text)
16274 {
16275     char title[MSG_SIZ];
16276
16277     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16278       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
16279     } else {
16280       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
16281               WhiteOnMove(moveNumber) ? " " : ".. ",
16282               parseList[moveNumber]);
16283     }
16284     if (text != NULL && (appData.autoDisplayComment || commentUp))
16285         CommentPopUp(title, text);
16286 }
16287
16288 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
16289  * might be busy thinking or pondering.  It can be omitted if your
16290  * gnuchess is configured to stop thinking immediately on any user
16291  * input.  However, that gnuchess feature depends on the FIONREAD
16292  * ioctl, which does not work properly on some flavors of Unix.
16293  */
16294 void
16295 Attention (ChessProgramState *cps)
16296 {
16297 #if ATTENTION
16298     if (!cps->useSigint) return;
16299     if (appData.noChessProgram || (cps->pr == NoProc)) return;
16300     switch (gameMode) {
16301       case MachinePlaysWhite:
16302       case MachinePlaysBlack:
16303       case TwoMachinesPlay:
16304       case IcsPlayingWhite:
16305       case IcsPlayingBlack:
16306       case AnalyzeMode:
16307       case AnalyzeFile:
16308         /* Skip if we know it isn't thinking */
16309         if (!cps->maybeThinking) return;
16310         if (appData.debugMode)
16311           fprintf(debugFP, "Interrupting %s\n", cps->which);
16312         InterruptChildProcess(cps->pr);
16313         cps->maybeThinking = FALSE;
16314         break;
16315       default:
16316         break;
16317     }
16318 #endif /*ATTENTION*/
16319 }
16320
16321 int
16322 CheckFlags ()
16323 {
16324     if (whiteTimeRemaining <= 0) {
16325         if (!whiteFlag) {
16326             whiteFlag = TRUE;
16327             if (appData.icsActive) {
16328                 if (appData.autoCallFlag &&
16329                     gameMode == IcsPlayingBlack && !blackFlag) {
16330                   SendToICS(ics_prefix);
16331                   SendToICS("flag\n");
16332                 }
16333             } else {
16334                 if (blackFlag) {
16335                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16336                 } else {
16337                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
16338                     if (appData.autoCallFlag) {
16339                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
16340                         return TRUE;
16341                     }
16342                 }
16343             }
16344         }
16345     }
16346     if (blackTimeRemaining <= 0) {
16347         if (!blackFlag) {
16348             blackFlag = TRUE;
16349             if (appData.icsActive) {
16350                 if (appData.autoCallFlag &&
16351                     gameMode == IcsPlayingWhite && !whiteFlag) {
16352                   SendToICS(ics_prefix);
16353                   SendToICS("flag\n");
16354                 }
16355             } else {
16356                 if (whiteFlag) {
16357                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16358                 } else {
16359                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
16360                     if (appData.autoCallFlag) {
16361                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
16362                         return TRUE;
16363                     }
16364                 }
16365             }
16366         }
16367     }
16368     return FALSE;
16369 }
16370
16371 void
16372 CheckTimeControl ()
16373 {
16374     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
16375         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
16376
16377     /*
16378      * add time to clocks when time control is achieved ([HGM] now also used for increment)
16379      */
16380     if ( !WhiteOnMove(forwardMostMove) ) {
16381         /* White made time control */
16382         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
16383         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
16384         /* [HGM] time odds: correct new time quota for time odds! */
16385                                             / WhitePlayer()->timeOdds;
16386         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
16387     } else {
16388         lastBlack -= blackTimeRemaining;
16389         /* Black made time control */
16390         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
16391                                             / WhitePlayer()->other->timeOdds;
16392         lastWhite = whiteTimeRemaining;
16393     }
16394 }
16395
16396 void
16397 DisplayBothClocks ()
16398 {
16399     int wom = gameMode == EditPosition ?
16400       !blackPlaysFirst : WhiteOnMove(currentMove);
16401     DisplayWhiteClock(whiteTimeRemaining, wom);
16402     DisplayBlackClock(blackTimeRemaining, !wom);
16403 }
16404
16405
16406 /* Timekeeping seems to be a portability nightmare.  I think everyone
16407    has ftime(), but I'm really not sure, so I'm including some ifdefs
16408    to use other calls if you don't.  Clocks will be less accurate if
16409    you have neither ftime nor gettimeofday.
16410 */
16411
16412 /* VS 2008 requires the #include outside of the function */
16413 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
16414 #include <sys/timeb.h>
16415 #endif
16416
16417 /* Get the current time as a TimeMark */
16418 void
16419 GetTimeMark (TimeMark *tm)
16420 {
16421 #if HAVE_GETTIMEOFDAY
16422
16423     struct timeval timeVal;
16424     struct timezone timeZone;
16425
16426     gettimeofday(&timeVal, &timeZone);
16427     tm->sec = (long) timeVal.tv_sec;
16428     tm->ms = (int) (timeVal.tv_usec / 1000L);
16429
16430 #else /*!HAVE_GETTIMEOFDAY*/
16431 #if HAVE_FTIME
16432
16433 // include <sys/timeb.h> / moved to just above start of function
16434     struct timeb timeB;
16435
16436     ftime(&timeB);
16437     tm->sec = (long) timeB.time;
16438     tm->ms = (int) timeB.millitm;
16439
16440 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
16441     tm->sec = (long) time(NULL);
16442     tm->ms = 0;
16443 #endif
16444 #endif
16445 }
16446
16447 /* Return the difference in milliseconds between two
16448    time marks.  We assume the difference will fit in a long!
16449 */
16450 long
16451 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
16452 {
16453     return 1000L*(tm2->sec - tm1->sec) +
16454            (long) (tm2->ms - tm1->ms);
16455 }
16456
16457
16458 /*
16459  * Code to manage the game clocks.
16460  *
16461  * In tournament play, black starts the clock and then white makes a move.
16462  * We give the human user a slight advantage if he is playing white---the
16463  * clocks don't run until he makes his first move, so it takes zero time.
16464  * Also, we don't account for network lag, so we could get out of sync
16465  * with GNU Chess's clock -- but then, referees are always right.
16466  */
16467
16468 static TimeMark tickStartTM;
16469 static long intendedTickLength;
16470
16471 long
16472 NextTickLength (long timeRemaining)
16473 {
16474     long nominalTickLength, nextTickLength;
16475
16476     if (timeRemaining > 0L && timeRemaining <= 10000L)
16477       nominalTickLength = 100L;
16478     else
16479       nominalTickLength = 1000L;
16480     nextTickLength = timeRemaining % nominalTickLength;
16481     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
16482
16483     return nextTickLength;
16484 }
16485
16486 /* Adjust clock one minute up or down */
16487 void
16488 AdjustClock (Boolean which, int dir)
16489 {
16490     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
16491     if(which) blackTimeRemaining += 60000*dir;
16492     else      whiteTimeRemaining += 60000*dir;
16493     DisplayBothClocks();
16494     adjustedClock = TRUE;
16495 }
16496
16497 /* Stop clocks and reset to a fresh time control */
16498 void
16499 ResetClocks ()
16500 {
16501     (void) StopClockTimer();
16502     if (appData.icsActive) {
16503         whiteTimeRemaining = blackTimeRemaining = 0;
16504     } else if (searchTime) {
16505         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16506         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16507     } else { /* [HGM] correct new time quote for time odds */
16508         whiteTC = blackTC = fullTimeControlString;
16509         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
16510         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
16511     }
16512     if (whiteFlag || blackFlag) {
16513         DisplayTitle("");
16514         whiteFlag = blackFlag = FALSE;
16515     }
16516     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
16517     DisplayBothClocks();
16518     adjustedClock = FALSE;
16519 }
16520
16521 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
16522
16523 /* Decrement running clock by amount of time that has passed */
16524 void
16525 DecrementClocks ()
16526 {
16527     long timeRemaining;
16528     long lastTickLength, fudge;
16529     TimeMark now;
16530
16531     if (!appData.clockMode) return;
16532     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
16533
16534     GetTimeMark(&now);
16535
16536     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16537
16538     /* Fudge if we woke up a little too soon */
16539     fudge = intendedTickLength - lastTickLength;
16540     if (fudge < 0 || fudge > FUDGE) fudge = 0;
16541
16542     if (WhiteOnMove(forwardMostMove)) {
16543         if(whiteNPS >= 0) lastTickLength = 0;
16544         timeRemaining = whiteTimeRemaining -= lastTickLength;
16545         if(timeRemaining < 0 && !appData.icsActive) {
16546             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
16547             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
16548                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
16549                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
16550             }
16551         }
16552         DisplayWhiteClock(whiteTimeRemaining - fudge,
16553                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16554     } else {
16555         if(blackNPS >= 0) lastTickLength = 0;
16556         timeRemaining = blackTimeRemaining -= lastTickLength;
16557         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
16558             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
16559             if(suddenDeath) {
16560                 blackStartMove = forwardMostMove;
16561                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
16562             }
16563         }
16564         DisplayBlackClock(blackTimeRemaining - fudge,
16565                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16566     }
16567     if (CheckFlags()) return;
16568
16569     if(twoBoards) { // count down secondary board's clocks as well
16570         activePartnerTime -= lastTickLength;
16571         partnerUp = 1;
16572         if(activePartner == 'W')
16573             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
16574         else
16575             DisplayBlackClock(activePartnerTime, TRUE);
16576         partnerUp = 0;
16577     }
16578
16579     tickStartTM = now;
16580     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
16581     StartClockTimer(intendedTickLength);
16582
16583     /* if the time remaining has fallen below the alarm threshold, sound the
16584      * alarm. if the alarm has sounded and (due to a takeback or time control
16585      * with increment) the time remaining has increased to a level above the
16586      * threshold, reset the alarm so it can sound again.
16587      */
16588
16589     if (appData.icsActive && appData.icsAlarm) {
16590
16591         /* make sure we are dealing with the user's clock */
16592         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
16593                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
16594            )) return;
16595
16596         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
16597             alarmSounded = FALSE;
16598         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
16599             PlayAlarmSound();
16600             alarmSounded = TRUE;
16601         }
16602     }
16603 }
16604
16605
16606 /* A player has just moved, so stop the previously running
16607    clock and (if in clock mode) start the other one.
16608    We redisplay both clocks in case we're in ICS mode, because
16609    ICS gives us an update to both clocks after every move.
16610    Note that this routine is called *after* forwardMostMove
16611    is updated, so the last fractional tick must be subtracted
16612    from the color that is *not* on move now.
16613 */
16614 void
16615 SwitchClocks (int newMoveNr)
16616 {
16617     long lastTickLength;
16618     TimeMark now;
16619     int flagged = FALSE;
16620
16621     GetTimeMark(&now);
16622
16623     if (StopClockTimer() && appData.clockMode) {
16624         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16625         if (!WhiteOnMove(forwardMostMove)) {
16626             if(blackNPS >= 0) lastTickLength = 0;
16627             blackTimeRemaining -= lastTickLength;
16628            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16629 //         if(pvInfoList[forwardMostMove].time == -1)
16630                  pvInfoList[forwardMostMove].time =               // use GUI time
16631                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
16632         } else {
16633            if(whiteNPS >= 0) lastTickLength = 0;
16634            whiteTimeRemaining -= lastTickLength;
16635            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16636 //         if(pvInfoList[forwardMostMove].time == -1)
16637                  pvInfoList[forwardMostMove].time =
16638                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
16639         }
16640         flagged = CheckFlags();
16641     }
16642     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
16643     CheckTimeControl();
16644
16645     if (flagged || !appData.clockMode) return;
16646
16647     switch (gameMode) {
16648       case MachinePlaysBlack:
16649       case MachinePlaysWhite:
16650       case BeginningOfGame:
16651         if (pausing) return;
16652         break;
16653
16654       case EditGame:
16655       case PlayFromGameFile:
16656       case IcsExamining:
16657         return;
16658
16659       default:
16660         break;
16661     }
16662
16663     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
16664         if(WhiteOnMove(forwardMostMove))
16665              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16666         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16667     }
16668
16669     tickStartTM = now;
16670     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16671       whiteTimeRemaining : blackTimeRemaining);
16672     StartClockTimer(intendedTickLength);
16673 }
16674
16675
16676 /* Stop both clocks */
16677 void
16678 StopClocks ()
16679 {
16680     long lastTickLength;
16681     TimeMark now;
16682
16683     if (!StopClockTimer()) return;
16684     if (!appData.clockMode) return;
16685
16686     GetTimeMark(&now);
16687
16688     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16689     if (WhiteOnMove(forwardMostMove)) {
16690         if(whiteNPS >= 0) lastTickLength = 0;
16691         whiteTimeRemaining -= lastTickLength;
16692         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
16693     } else {
16694         if(blackNPS >= 0) lastTickLength = 0;
16695         blackTimeRemaining -= lastTickLength;
16696         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
16697     }
16698     CheckFlags();
16699 }
16700
16701 /* Start clock of player on move.  Time may have been reset, so
16702    if clock is already running, stop and restart it. */
16703 void
16704 StartClocks ()
16705 {
16706     (void) StopClockTimer(); /* in case it was running already */
16707     DisplayBothClocks();
16708     if (CheckFlags()) return;
16709
16710     if (!appData.clockMode) return;
16711     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
16712
16713     GetTimeMark(&tickStartTM);
16714     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16715       whiteTimeRemaining : blackTimeRemaining);
16716
16717    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
16718     whiteNPS = blackNPS = -1;
16719     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
16720        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
16721         whiteNPS = first.nps;
16722     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
16723        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
16724         blackNPS = first.nps;
16725     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
16726         whiteNPS = second.nps;
16727     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
16728         blackNPS = second.nps;
16729     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
16730
16731     StartClockTimer(intendedTickLength);
16732 }
16733
16734 char *
16735 TimeString (long ms)
16736 {
16737     long second, minute, hour, day;
16738     char *sign = "";
16739     static char buf[32];
16740
16741     if (ms > 0 && ms <= 9900) {
16742       /* convert milliseconds to tenths, rounding up */
16743       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
16744
16745       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
16746       return buf;
16747     }
16748
16749     /* convert milliseconds to seconds, rounding up */
16750     /* use floating point to avoid strangeness of integer division
16751        with negative dividends on many machines */
16752     second = (long) floor(((double) (ms + 999L)) / 1000.0);
16753
16754     if (second < 0) {
16755         sign = "-";
16756         second = -second;
16757     }
16758
16759     day = second / (60 * 60 * 24);
16760     second = second % (60 * 60 * 24);
16761     hour = second / (60 * 60);
16762     second = second % (60 * 60);
16763     minute = second / 60;
16764     second = second % 60;
16765
16766     if (day > 0)
16767       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
16768               sign, day, hour, minute, second);
16769     else if (hour > 0)
16770       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
16771     else
16772       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
16773
16774     return buf;
16775 }
16776
16777
16778 /*
16779  * This is necessary because some C libraries aren't ANSI C compliant yet.
16780  */
16781 char *
16782 StrStr (char *string, char *match)
16783 {
16784     int i, length;
16785
16786     length = strlen(match);
16787
16788     for (i = strlen(string) - length; i >= 0; i--, string++)
16789       if (!strncmp(match, string, length))
16790         return string;
16791
16792     return NULL;
16793 }
16794
16795 char *
16796 StrCaseStr (char *string, char *match)
16797 {
16798     int i, j, length;
16799
16800     length = strlen(match);
16801
16802     for (i = strlen(string) - length; i >= 0; i--, string++) {
16803         for (j = 0; j < length; j++) {
16804             if (ToLower(match[j]) != ToLower(string[j]))
16805               break;
16806         }
16807         if (j == length) return string;
16808     }
16809
16810     return NULL;
16811 }
16812
16813 #ifndef _amigados
16814 int
16815 StrCaseCmp (char *s1, char *s2)
16816 {
16817     char c1, c2;
16818
16819     for (;;) {
16820         c1 = ToLower(*s1++);
16821         c2 = ToLower(*s2++);
16822         if (c1 > c2) return 1;
16823         if (c1 < c2) return -1;
16824         if (c1 == NULLCHAR) return 0;
16825     }
16826 }
16827
16828
16829 int
16830 ToLower (int c)
16831 {
16832     return isupper(c) ? tolower(c) : c;
16833 }
16834
16835
16836 int
16837 ToUpper (int c)
16838 {
16839     return islower(c) ? toupper(c) : c;
16840 }
16841 #endif /* !_amigados    */
16842
16843 char *
16844 StrSave (char *s)
16845 {
16846   char *ret;
16847
16848   if ((ret = (char *) malloc(strlen(s) + 1)))
16849     {
16850       safeStrCpy(ret, s, strlen(s)+1);
16851     }
16852   return ret;
16853 }
16854
16855 char *
16856 StrSavePtr (char *s, char **savePtr)
16857 {
16858     if (*savePtr) {
16859         free(*savePtr);
16860     }
16861     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
16862       safeStrCpy(*savePtr, s, strlen(s)+1);
16863     }
16864     return(*savePtr);
16865 }
16866
16867 char *
16868 PGNDate ()
16869 {
16870     time_t clock;
16871     struct tm *tm;
16872     char buf[MSG_SIZ];
16873
16874     clock = time((time_t *)NULL);
16875     tm = localtime(&clock);
16876     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
16877             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
16878     return StrSave(buf);
16879 }
16880
16881
16882 char *
16883 PositionToFEN (int move, char *overrideCastling)
16884 {
16885     int i, j, fromX, fromY, toX, toY;
16886     int whiteToPlay;
16887     char buf[MSG_SIZ];
16888     char *p, *q;
16889     int emptycount;
16890     ChessSquare piece;
16891
16892     whiteToPlay = (gameMode == EditPosition) ?
16893       !blackPlaysFirst : (move % 2 == 0);
16894     p = buf;
16895
16896     /* Piece placement data */
16897     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16898         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
16899         emptycount = 0;
16900         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16901             if (boards[move][i][j] == EmptySquare) {
16902                 emptycount++;
16903             } else { ChessSquare piece = boards[move][i][j];
16904                 if (emptycount > 0) {
16905                     if(emptycount<10) /* [HGM] can be >= 10 */
16906                         *p++ = '0' + emptycount;
16907                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16908                     emptycount = 0;
16909                 }
16910                 if(PieceToChar(piece) == '+') {
16911                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
16912                     *p++ = '+';
16913                     piece = (ChessSquare)(DEMOTED piece);
16914                 }
16915                 *p++ = PieceToChar(piece);
16916                 if(p[-1] == '~') {
16917                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
16918                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
16919                     *p++ = '~';
16920                 }
16921             }
16922         }
16923         if (emptycount > 0) {
16924             if(emptycount<10) /* [HGM] can be >= 10 */
16925                 *p++ = '0' + emptycount;
16926             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16927             emptycount = 0;
16928         }
16929         *p++ = '/';
16930     }
16931     *(p - 1) = ' ';
16932
16933     /* [HGM] print Crazyhouse or Shogi holdings */
16934     if( gameInfo.holdingsWidth ) {
16935         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
16936         q = p;
16937         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
16938             piece = boards[move][i][BOARD_WIDTH-1];
16939             if( piece != EmptySquare )
16940               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
16941                   *p++ = PieceToChar(piece);
16942         }
16943         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
16944             piece = boards[move][BOARD_HEIGHT-i-1][0];
16945             if( piece != EmptySquare )
16946               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
16947                   *p++ = PieceToChar(piece);
16948         }
16949
16950         if( q == p ) *p++ = '-';
16951         *p++ = ']';
16952         *p++ = ' ';
16953     }
16954
16955     /* Active color */
16956     *p++ = whiteToPlay ? 'w' : 'b';
16957     *p++ = ' ';
16958
16959   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
16960     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
16961   } else {
16962   if(nrCastlingRights) {
16963      q = p;
16964      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
16965        /* [HGM] write directly from rights */
16966            if(boards[move][CASTLING][2] != NoRights &&
16967               boards[move][CASTLING][0] != NoRights   )
16968                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
16969            if(boards[move][CASTLING][2] != NoRights &&
16970               boards[move][CASTLING][1] != NoRights   )
16971                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
16972            if(boards[move][CASTLING][5] != NoRights &&
16973               boards[move][CASTLING][3] != NoRights   )
16974                 *p++ = boards[move][CASTLING][3] + AAA;
16975            if(boards[move][CASTLING][5] != NoRights &&
16976               boards[move][CASTLING][4] != NoRights   )
16977                 *p++ = boards[move][CASTLING][4] + AAA;
16978      } else {
16979
16980         /* [HGM] write true castling rights */
16981         if( nrCastlingRights == 6 ) {
16982             int q, k=0;
16983             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
16984                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
16985             q = (boards[move][CASTLING][1] == BOARD_LEFT &&
16986                  boards[move][CASTLING][2] != NoRights  );
16987             if(gameInfo.variant == VariantSChess) { // for S-Chess, indicate all vrgin backrank pieces
16988                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_RGHT]; // count white held pieces
16989                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
16990                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
16991                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
16992             }
16993             if(q) *p++ = 'Q';
16994             k = 0;
16995             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
16996                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
16997             q = (boards[move][CASTLING][4] == BOARD_LEFT &&
16998                  boards[move][CASTLING][5] != NoRights  );
16999             if(gameInfo.variant == VariantSChess) {
17000                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_LEFT-1]; // count black held pieces
17001                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17002                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
17003                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17004             }
17005             if(q) *p++ = 'q';
17006         }
17007      }
17008      if (q == p) *p++ = '-'; /* No castling rights */
17009      *p++ = ' ';
17010   }
17011
17012   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17013      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
17014     /* En passant target square */
17015     if (move > backwardMostMove) {
17016         fromX = moveList[move - 1][0] - AAA;
17017         fromY = moveList[move - 1][1] - ONE;
17018         toX = moveList[move - 1][2] - AAA;
17019         toY = moveList[move - 1][3] - ONE;
17020         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
17021             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
17022             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
17023             fromX == toX) {
17024             /* 2-square pawn move just happened */
17025             *p++ = toX + AAA;
17026             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17027         } else {
17028             *p++ = '-';
17029         }
17030     } else if(move == backwardMostMove) {
17031         // [HGM] perhaps we should always do it like this, and forget the above?
17032         if((signed char)boards[move][EP_STATUS] >= 0) {
17033             *p++ = boards[move][EP_STATUS] + AAA;
17034             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17035         } else {
17036             *p++ = '-';
17037         }
17038     } else {
17039         *p++ = '-';
17040     }
17041     *p++ = ' ';
17042   }
17043   }
17044
17045     /* [HGM] find reversible plies */
17046     {   int i = 0, j=move;
17047
17048         if (appData.debugMode) { int k;
17049             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
17050             for(k=backwardMostMove; k<=forwardMostMove; k++)
17051                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
17052
17053         }
17054
17055         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
17056         if( j == backwardMostMove ) i += initialRulePlies;
17057         sprintf(p, "%d ", i);
17058         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
17059     }
17060     /* Fullmove number */
17061     sprintf(p, "%d", (move / 2) + 1);
17062
17063     return StrSave(buf);
17064 }
17065
17066 Boolean
17067 ParseFEN (Board board, int *blackPlaysFirst, char *fen)
17068 {
17069     int i, j;
17070     char *p, c;
17071     int emptycount, virgin[BOARD_FILES];
17072     ChessSquare piece;
17073
17074     p = fen;
17075
17076     /* [HGM] by default clear Crazyhouse holdings, if present */
17077     if(gameInfo.holdingsWidth) {
17078        for(i=0; i<BOARD_HEIGHT; i++) {
17079            board[i][0]             = EmptySquare; /* black holdings */
17080            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
17081            board[i][1]             = (ChessSquare) 0; /* black counts */
17082            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
17083        }
17084     }
17085
17086     /* Piece placement data */
17087     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17088         j = 0;
17089         for (;;) {
17090             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
17091                 if (*p == '/') p++;
17092                 emptycount = gameInfo.boardWidth - j;
17093                 while (emptycount--)
17094                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17095                 break;
17096 #if(BOARD_FILES >= 10)
17097             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
17098                 p++; emptycount=10;
17099                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17100                 while (emptycount--)
17101                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17102 #endif
17103             } else if (isdigit(*p)) {
17104                 emptycount = *p++ - '0';
17105                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
17106                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17107                 while (emptycount--)
17108                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17109             } else if (*p == '+' || isalpha(*p)) {
17110                 if (j >= gameInfo.boardWidth) return FALSE;
17111                 if(*p=='+') {
17112                     piece = CharToPiece(*++p);
17113                     if(piece == EmptySquare) return FALSE; /* unknown piece */
17114                     piece = (ChessSquare) (PROMOTED piece ); p++;
17115                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
17116                 } else piece = CharToPiece(*p++);
17117
17118                 if(piece==EmptySquare) return FALSE; /* unknown piece */
17119                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
17120                     piece = (ChessSquare) (PROMOTED piece);
17121                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
17122                     p++;
17123                 }
17124                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
17125             } else {
17126                 return FALSE;
17127             }
17128         }
17129     }
17130     while (*p == '/' || *p == ' ') p++;
17131
17132     /* [HGM] look for Crazyhouse holdings here */
17133     while(*p==' ') p++;
17134     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
17135         if(*p == '[') p++;
17136         if(*p == '-' ) p++; /* empty holdings */ else {
17137             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
17138             /* if we would allow FEN reading to set board size, we would   */
17139             /* have to add holdings and shift the board read so far here   */
17140             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
17141                 p++;
17142                 if((int) piece >= (int) BlackPawn ) {
17143                     i = (int)piece - (int)BlackPawn;
17144                     i = PieceToNumber((ChessSquare)i);
17145                     if( i >= gameInfo.holdingsSize ) return FALSE;
17146                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
17147                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
17148                 } else {
17149                     i = (int)piece - (int)WhitePawn;
17150                     i = PieceToNumber((ChessSquare)i);
17151                     if( i >= gameInfo.holdingsSize ) return FALSE;
17152                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
17153                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
17154                 }
17155             }
17156         }
17157         if(*p == ']') p++;
17158     }
17159
17160     while(*p == ' ') p++;
17161
17162     /* Active color */
17163     c = *p++;
17164     if(appData.colorNickNames) {
17165       if( c == appData.colorNickNames[0] ) c = 'w'; else
17166       if( c == appData.colorNickNames[1] ) c = 'b';
17167     }
17168     switch (c) {
17169       case 'w':
17170         *blackPlaysFirst = FALSE;
17171         break;
17172       case 'b':
17173         *blackPlaysFirst = TRUE;
17174         break;
17175       default:
17176         return FALSE;
17177     }
17178
17179     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
17180     /* return the extra info in global variiables             */
17181
17182     /* set defaults in case FEN is incomplete */
17183     board[EP_STATUS] = EP_UNKNOWN;
17184     for(i=0; i<nrCastlingRights; i++ ) {
17185         board[CASTLING][i] =
17186             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
17187     }   /* assume possible unless obviously impossible */
17188     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
17189     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
17190     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
17191                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
17192     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
17193     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
17194     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
17195                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
17196     FENrulePlies = 0;
17197
17198     while(*p==' ') p++;
17199     if(nrCastlingRights) {
17200       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
17201       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
17202           /* castling indicator present, so default becomes no castlings */
17203           for(i=0; i<nrCastlingRights; i++ ) {
17204                  board[CASTLING][i] = NoRights;
17205           }
17206       }
17207       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
17208              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess) &&
17209              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
17210              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
17211         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
17212
17213         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
17214             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
17215             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
17216         }
17217         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
17218             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
17219         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
17220                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
17221         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
17222                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
17223         switch(c) {
17224           case'K':
17225               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
17226               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
17227               board[CASTLING][2] = whiteKingFile;
17228               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
17229               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17230               break;
17231           case'Q':
17232               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
17233               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
17234               board[CASTLING][2] = whiteKingFile;
17235               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
17236               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17237               break;
17238           case'k':
17239               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
17240               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
17241               board[CASTLING][5] = blackKingFile;
17242               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
17243               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17244               break;
17245           case'q':
17246               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
17247               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
17248               board[CASTLING][5] = blackKingFile;
17249               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
17250               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17251           case '-':
17252               break;
17253           default: /* FRC castlings */
17254               if(c >= 'a') { /* black rights */
17255                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
17256                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17257                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
17258                   if(i == BOARD_RGHT) break;
17259                   board[CASTLING][5] = i;
17260                   c -= AAA;
17261                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
17262                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
17263                   if(c > i)
17264                       board[CASTLING][3] = c;
17265                   else
17266                       board[CASTLING][4] = c;
17267               } else { /* white rights */
17268                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
17269                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17270                     if(board[0][i] == WhiteKing) break;
17271                   if(i == BOARD_RGHT) break;
17272                   board[CASTLING][2] = i;
17273                   c -= AAA - 'a' + 'A';
17274                   if(board[0][c] >= WhiteKing) break;
17275                   if(c > i)
17276                       board[CASTLING][0] = c;
17277                   else
17278                       board[CASTLING][1] = c;
17279               }
17280         }
17281       }
17282       for(i=0; i<nrCastlingRights; i++)
17283         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
17284       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = virgin[i];
17285     if (appData.debugMode) {
17286         fprintf(debugFP, "FEN castling rights:");
17287         for(i=0; i<nrCastlingRights; i++)
17288         fprintf(debugFP, " %d", board[CASTLING][i]);
17289         fprintf(debugFP, "\n");
17290     }
17291
17292       while(*p==' ') p++;
17293     }
17294
17295     /* read e.p. field in games that know e.p. capture */
17296     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17297        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
17298       if(*p=='-') {
17299         p++; board[EP_STATUS] = EP_NONE;
17300       } else {
17301          char c = *p++ - AAA;
17302
17303          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
17304          if(*p >= '0' && *p <='9') p++;
17305          board[EP_STATUS] = c;
17306       }
17307     }
17308
17309
17310     if(sscanf(p, "%d", &i) == 1) {
17311         FENrulePlies = i; /* 50-move ply counter */
17312         /* (The move number is still ignored)    */
17313     }
17314
17315     return TRUE;
17316 }
17317
17318 void
17319 EditPositionPasteFEN (char *fen)
17320 {
17321   if (fen != NULL) {
17322     Board initial_position;
17323
17324     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
17325       DisplayError(_("Bad FEN position in clipboard"), 0);
17326       return ;
17327     } else {
17328       int savedBlackPlaysFirst = blackPlaysFirst;
17329       EditPositionEvent();
17330       blackPlaysFirst = savedBlackPlaysFirst;
17331       CopyBoard(boards[0], initial_position);
17332       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
17333       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
17334       DisplayBothClocks();
17335       DrawPosition(FALSE, boards[currentMove]);
17336     }
17337   }
17338 }
17339
17340 static char cseq[12] = "\\   ";
17341
17342 Boolean
17343 set_cont_sequence (char *new_seq)
17344 {
17345     int len;
17346     Boolean ret;
17347
17348     // handle bad attempts to set the sequence
17349         if (!new_seq)
17350                 return 0; // acceptable error - no debug
17351
17352     len = strlen(new_seq);
17353     ret = (len > 0) && (len < sizeof(cseq));
17354     if (ret)
17355       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
17356     else if (appData.debugMode)
17357       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
17358     return ret;
17359 }
17360
17361 /*
17362     reformat a source message so words don't cross the width boundary.  internal
17363     newlines are not removed.  returns the wrapped size (no null character unless
17364     included in source message).  If dest is NULL, only calculate the size required
17365     for the dest buffer.  lp argument indicats line position upon entry, and it's
17366     passed back upon exit.
17367 */
17368 int
17369 wrap (char *dest, char *src, int count, int width, int *lp)
17370 {
17371     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
17372
17373     cseq_len = strlen(cseq);
17374     old_line = line = *lp;
17375     ansi = len = clen = 0;
17376
17377     for (i=0; i < count; i++)
17378     {
17379         if (src[i] == '\033')
17380             ansi = 1;
17381
17382         // if we hit the width, back up
17383         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
17384         {
17385             // store i & len in case the word is too long
17386             old_i = i, old_len = len;
17387
17388             // find the end of the last word
17389             while (i && src[i] != ' ' && src[i] != '\n')
17390             {
17391                 i--;
17392                 len--;
17393             }
17394
17395             // word too long?  restore i & len before splitting it
17396             if ((old_i-i+clen) >= width)
17397             {
17398                 i = old_i;
17399                 len = old_len;
17400             }
17401
17402             // extra space?
17403             if (i && src[i-1] == ' ')
17404                 len--;
17405
17406             if (src[i] != ' ' && src[i] != '\n')
17407             {
17408                 i--;
17409                 if (len)
17410                     len--;
17411             }
17412
17413             // now append the newline and continuation sequence
17414             if (dest)
17415                 dest[len] = '\n';
17416             len++;
17417             if (dest)
17418                 strncpy(dest+len, cseq, cseq_len);
17419             len += cseq_len;
17420             line = cseq_len;
17421             clen = cseq_len;
17422             continue;
17423         }
17424
17425         if (dest)
17426             dest[len] = src[i];
17427         len++;
17428         if (!ansi)
17429             line++;
17430         if (src[i] == '\n')
17431             line = 0;
17432         if (src[i] == 'm')
17433             ansi = 0;
17434     }
17435     if (dest && appData.debugMode)
17436     {
17437         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
17438             count, width, line, len, *lp);
17439         show_bytes(debugFP, src, count);
17440         fprintf(debugFP, "\ndest: ");
17441         show_bytes(debugFP, dest, len);
17442         fprintf(debugFP, "\n");
17443     }
17444     *lp = dest ? line : old_line;
17445
17446     return len;
17447 }
17448
17449 // [HGM] vari: routines for shelving variations
17450 Boolean modeRestore = FALSE;
17451
17452 void
17453 PushInner (int firstMove, int lastMove)
17454 {
17455         int i, j, nrMoves = lastMove - firstMove;
17456
17457         // push current tail of game on stack
17458         savedResult[storedGames] = gameInfo.result;
17459         savedDetails[storedGames] = gameInfo.resultDetails;
17460         gameInfo.resultDetails = NULL;
17461         savedFirst[storedGames] = firstMove;
17462         savedLast [storedGames] = lastMove;
17463         savedFramePtr[storedGames] = framePtr;
17464         framePtr -= nrMoves; // reserve space for the boards
17465         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
17466             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
17467             for(j=0; j<MOVE_LEN; j++)
17468                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
17469             for(j=0; j<2*MOVE_LEN; j++)
17470                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
17471             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
17472             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
17473             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
17474             pvInfoList[firstMove+i-1].depth = 0;
17475             commentList[framePtr+i] = commentList[firstMove+i];
17476             commentList[firstMove+i] = NULL;
17477         }
17478
17479         storedGames++;
17480         forwardMostMove = firstMove; // truncate game so we can start variation
17481 }
17482
17483 void
17484 PushTail (int firstMove, int lastMove)
17485 {
17486         if(appData.icsActive) { // only in local mode
17487                 forwardMostMove = currentMove; // mimic old ICS behavior
17488                 return;
17489         }
17490         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
17491
17492         PushInner(firstMove, lastMove);
17493         if(storedGames == 1) GreyRevert(FALSE);
17494         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
17495 }
17496
17497 void
17498 PopInner (Boolean annotate)
17499 {
17500         int i, j, nrMoves;
17501         char buf[8000], moveBuf[20];
17502
17503         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
17504         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
17505         nrMoves = savedLast[storedGames] - currentMove;
17506         if(annotate) {
17507                 int cnt = 10;
17508                 if(!WhiteOnMove(currentMove))
17509                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
17510                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
17511                 for(i=currentMove; i<forwardMostMove; i++) {
17512                         if(WhiteOnMove(i))
17513                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
17514                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
17515                         strcat(buf, moveBuf);
17516                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
17517                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
17518                 }
17519                 strcat(buf, ")");
17520         }
17521         for(i=1; i<=nrMoves; i++) { // copy last variation back
17522             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
17523             for(j=0; j<MOVE_LEN; j++)
17524                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
17525             for(j=0; j<2*MOVE_LEN; j++)
17526                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
17527             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
17528             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
17529             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
17530             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
17531             commentList[currentMove+i] = commentList[framePtr+i];
17532             commentList[framePtr+i] = NULL;
17533         }
17534         if(annotate) AppendComment(currentMove+1, buf, FALSE);
17535         framePtr = savedFramePtr[storedGames];
17536         gameInfo.result = savedResult[storedGames];
17537         if(gameInfo.resultDetails != NULL) {
17538             free(gameInfo.resultDetails);
17539       }
17540         gameInfo.resultDetails = savedDetails[storedGames];
17541         forwardMostMove = currentMove + nrMoves;
17542 }
17543
17544 Boolean
17545 PopTail (Boolean annotate)
17546 {
17547         if(appData.icsActive) return FALSE; // only in local mode
17548         if(!storedGames) return FALSE; // sanity
17549         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
17550
17551         PopInner(annotate);
17552         if(currentMove < forwardMostMove) ForwardEvent(); else
17553         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
17554
17555         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
17556         return TRUE;
17557 }
17558
17559 void
17560 CleanupTail ()
17561 {       // remove all shelved variations
17562         int i;
17563         for(i=0; i<storedGames; i++) {
17564             if(savedDetails[i])
17565                 free(savedDetails[i]);
17566             savedDetails[i] = NULL;
17567         }
17568         for(i=framePtr; i<MAX_MOVES; i++) {
17569                 if(commentList[i]) free(commentList[i]);
17570                 commentList[i] = NULL;
17571         }
17572         framePtr = MAX_MOVES-1;
17573         storedGames = 0;
17574 }
17575
17576 void
17577 LoadVariation (int index, char *text)
17578 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
17579         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
17580         int level = 0, move;
17581
17582         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
17583         // first find outermost bracketing variation
17584         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
17585             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
17586                 if(*p == '{') wait = '}'; else
17587                 if(*p == '[') wait = ']'; else
17588                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
17589                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
17590             }
17591             if(*p == wait) wait = NULLCHAR; // closing ]} found
17592             p++;
17593         }
17594         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
17595         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
17596         end[1] = NULLCHAR; // clip off comment beyond variation
17597         ToNrEvent(currentMove-1);
17598         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
17599         // kludge: use ParsePV() to append variation to game
17600         move = currentMove;
17601         ParsePV(start, TRUE, TRUE);
17602         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
17603         ClearPremoveHighlights();
17604         CommentPopDown();
17605         ToNrEvent(currentMove+1);
17606 }
17607
17608 void
17609 LoadTheme ()
17610 {
17611     char *p, *q, buf[MSG_SIZ];
17612     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
17613         snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
17614         ParseArgsFromString(buf);
17615         ActivateTheme(TRUE); // also redo colors
17616         return;
17617     }
17618     p = nickName;
17619     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
17620     {
17621         int len;
17622         q = appData.themeNames;
17623         snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
17624       if(appData.useBitmaps) {
17625         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
17626                 appData.liteBackTextureFile, appData.darkBackTextureFile, 
17627                 appData.liteBackTextureMode,
17628                 appData.darkBackTextureMode );
17629       } else {
17630         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
17631                 Col2Text(2),   // lightSquareColor
17632                 Col2Text(3) ); // darkSquareColor
17633       }
17634       if(appData.useBorder) {
17635         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
17636                 appData.border);
17637       } else {
17638         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
17639       }
17640       if(appData.useFont) {
17641         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
17642                 appData.renderPiecesWithFont,
17643                 appData.fontToPieceTable,
17644                 Col2Text(9),    // appData.fontBackColorWhite
17645                 Col2Text(10) ); // appData.fontForeColorBlack
17646       } else {
17647         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
17648                 appData.pieceDirectory);
17649         if(!appData.pieceDirectory[0])
17650           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
17651                 Col2Text(0),   // whitePieceColor
17652                 Col2Text(1) ); // blackPieceColor
17653       }
17654       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
17655                 Col2Text(4),   // highlightSquareColor
17656                 Col2Text(5) ); // premoveHighlightColor
17657         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
17658         if(insert != q) insert[-1] = NULLCHAR;
17659         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
17660         if(q)   free(q);
17661     }
17662     ActivateTheme(FALSE);
17663 }